嗯,这几天用稍稍闲散的时间模仿着mybatis写了一个简单的持久层框架。
目前只有一些简单的操作,不过足以用来学习一些mybatis简单的原理了。
这个项目的源码托管在github,地址为:
https://github.com/NemoMeng/NemoDao
可以先分析下我们在使用mybatis中做的一些工作:
1、配置处理。
2、写mapper。
3、写dao接口,在需要的地方调用dao接口。
这么着写这个框架的大致内容也出来了:
1、首先,我们在做的这个框架是用来操作数据库的,所以我们需要对jdbc做一些封装处理。
2、我们需要预定义一些配置,在程序执行的过程中加载配置,以便在程序中使用。
3、我们需要扫描配置的mapper的文件夹,验证mapper文件中填写的一些配置,把mapper的内容放到一个“容器”中方便使用。
4、我们需要根据调用的dao和方法找到相应的sql,并且传入参数,解析sql拼接参数后执行和得到返回结果。
所以这个简单框架要做的事情其实不多,咱们可以一步步来。
1、封装JDBC:
1.1、考虑使用数据源 + jdbc会好一些,所以咱们就先构建一个简单的数据源。
数据源可以直接实现javax.sql.DataSource接口,然后添加一些自己的初始化、连接获取等控制机制即可。
/**
* 简单的数据源相关
* Created by Nemo on 2017/12/19.
*/
public class SimpleDatasource implements DataSource {
//mysql数据库驱动
private static final String driver = PropertiesUtils.loadProp(PropKey.JDBC_DRIVER);
//数据库连接地址
private static final String url = PropertiesUtils.loadProp(PropKey.JDBC_URL);
//数据库的用户名
private static final String username = PropertiesUtils.loadProp(PropKey.JDBC_USERNAME);
//数据库的密码
private static final String password = PropertiesUtils.loadProp(PropKey.JDBC_PASSWORD);
//
private static final int maxSize = PropertiesUtils.loadIntValue(PropKey.DATASOURCE_MAX_SIZE);
//
private static final int initSize = PropertiesUtils.loadIntValue(PropKey.DATASOURCE_INIT_SIZE);
//连接池
private static LinkedList<Connection> pool = new LinkedList<Connection>();
private static SimpleDatasource instance = null;
//静态代码块负责加载驱动
static {
try {
Class.forName(driver);
} catch(Exception ex) {
ex.printStackTrace();
}
}
private SimpleDatasource(){
}
/**
* 获取数据源单例
* @return
*/
public static SimpleDatasource instance() {
if (instance == null) {
instance = new SimpleDatasource();
//初始化连接池
for(int i=0;i<initSize;i++){
try {
Connection connection = instance.getConnection(username, password);
pool.add(connection);
}catch (Exception e){
e.printStackTrace();
}
}
}
return instance;
}
/**
* 得到一个数据库连接
* @return
* @throws SQLException
*/
public Connection getConnection() throws SQLException {
synchronized (pool) {
if (pool.size() > 0) {
//如果连接池中还有剩余连接,则取即可,否则需要新建连接
return pool.removeFirst();
} else{
Connection connection = getConnection(username,password);
recover(connection);
return connection;
}
}
}
/**
* 回收连接
* @param connection
*/
public void recover (Connection connection){
synchronized(pool) {
//不超过最大的池连接量,新增到池中
if (pool.size() < maxSize) {
pool.add(connection);
}
}
}
/**
* 根据用户名 + 密码得到一个连接
* @param username
* @param password
* @return
* @throws SQLException
*/
public Connection getConnection(String username, String password) throws SQLException {
return DriverManager.getConnection(url,username,password);
}
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
public PrintWriter getLogWriter() throws SQLException {
return null;
}
public void setLogWriter(PrintWriter out) throws SQLException {
}
public void setLoginTimeout(int seconds) throws SQLException {
}
public int getLoginTimeout() throws SQLException {
return 0;
}
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
1.2、做完了数据源的封装后,就可以做JDBC的封装了。
其实在数据库操作中,无非也就是增、删、改、查四类的,而这四类按照操作上,又可以分作两大类:查看、修改。也就是说,一般只需要查看和修改这两个操作的入口就行。但是,考虑到数据在新增的时候,可能需要在之后得到当前新增的数据的信息,所以可能需要返回新增后的数据ID,所以这里新增和更新/删除的接口需要分开。新增后返回当前记录的ID,更新/删除后返回是否操作成功,查看则返回用户需要返回的数据内容。
直接上代码:
/**
* 获得数据库的连接
* @return
*/
private Connection getConnection(){
try {
if (datasource == null) {
datasource = SimpleDatasource.instance();
}
if (connection == null) {
connection = datasource.getConnection();
}
} catch (Exception e) {
e.printStackTrace();
}
return connection;
}
/**
* TODO 需要考虑事务
* 更新数据
* @param sql
* @param params
* @return 返回是否更新成功
* @throws SQLException
*/
public boolean update(String sql, List<Object>params) throws SQLException {
pstmt = getConnection().prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
int index = 1;
if(params != null && !params.isEmpty()){
for(int i=0; i<params.size(); i++){
pstmt.setObject(index++, params.get(i));
}
}
boolean result = pstmt.executeUpdate()>0;
releaseConn();
return result;
}
/**
* TODO 需要考虑事务
* 新增数据
* @param sql
* @param params
* @return 返回新增数据的ID
* @throws SQLException
*/
public Integer insert(String sql, List<Object>params) throws SQLException {
pstmt = getConnection().prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
int index = 1;
if(params != null && !params.isEmpty()){
for(int i=0; i<params.size(); i++){
pstmt.setObject(index++, params.get(i));;
}
}
//执行新增
pstmt.executeUpdate();
ResultSet rs = pstmt.getGeneratedKeys();
Integer key = null;
if(rs.next()) {
key = rs.getInt(1);
}
releaseConn();
return key;
}
/**
* 查询单条记录
* @param sql
* @param params
* @return
* @throws SQLException
*/
public Map<String, Object> findSimpleResult(String sql, List<Object> params) throws SQLException{
Map<String, Object> map = new HashMap<String, Object>();
int index = 1;
pstmt = getConnection().prepareStatement(sql);
if(params != null && !params.isEmpty()){
for(int i=0; i<params.size(); i++){
pstmt.setObject(index++, params.get(i));
}
}
resultSet = pstmt.executeQuery();//返回查询结果
ResultSetMetaData metaData = resultSet.getMetaData();
int col_len = metaData.getColumnCount();
while(resultSet.next()){
for(int i=0; i<col_len; i++ ){
String cols_name = metaData.getColumnName(i+1);
Object cols_value = resultSet.getObject(cols_name);
if(cols_value == null){
cols_value = "";;
}
map.put(cols_name, cols_value);
}
}
releaseConn();
return map;
}
/**查询多条记录
* @param sql
* @param params
* @return
* @throws SQLException
*/
public List<Map<String, Object>> findModeResult(String sql, List<Object> params) throws SQLException{
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
int index = 1;
pstmt = getConnection().prepareStatement(sql);
if(params != null && !params.isEmpty()){
for(int i = 0; i<params.size(); i++){
pstmt.setObject(index++, params.get(i));
}
}
resultSet = pstmt.executeQuery();
ResultSetMetaData metaData = resultSet.getMetaData();
int cols_len = metaData.getColumnCount();
while(resultSet.next()){
Map<String, Object> map = new HashMap<String, Object>();
for(int i=0; i<cols_len; i++){
String cols_name = metaData.getColumnName(i+1);
Object cols_value = resultSet.getObject(cols_name);;
if(cols_value == null){
cols_value = "";;
}
map.put(cols_name, cols_value);
}
list.add(map);
}
releaseConn();
return list;
}
/**通过反射机制查询单条记录
* @param sql
* @param params
* @param cls
* @return
* @throws Exception
*/
public <T> T findSimpleRefResult(String sql, List<Object> params,
Class<T> cls )throws Exception{
T resultObject = null;
int index = 1;
pstmt = getConnection().prepareStatement(sql);
if(params != null && !params.isEmpty()){
for(int i = 0; i<params.size(); i++){
pstmt.setObject(index++, params.get(i));
}
}
resultSet = pstmt.executeQuery();
ResultSetMetaData metaData = resultSet.getMetaData();
int cols_len = metaData.getColumnCount();
while(resultSet.next()){
//通过反射机制创建一个实例
resultObject = cls.newInstance();
for(int i = 0; i<cols_len; i++){
String cols_name = metaData.getColumnName(i+1);
Object cols_value = resultSet.getObject(cols_name);
if(cols_value == null){
cols_value = "";;
}
Field field = cls.getDeclaredField(cols_name);
field.setAccessible(true); //打开javabean的访问权限
field.set(resultObject, cols_value);
}
}
releaseConn();
return resultObject;
}
/**通过反射机制查询多条记录
* @param sql
* @param params
* @param cls
* @return
* @throws Exception
*/
public <T> List<T> findMoreRefResult(String sql, List<Object> params,
Class<T> cls )throws Exception {
List<T> list = new ArrayList<T>();
int index = 1;
pstmt = getConnection().prepareStatement(sql);
if(params != null && !params.isEmpty()){
for(int i = 0; i<params.size(); i++){
pstmt.setObject(index++, params.get(i));
}
}
resultSet = pstmt.executeQuery();
ResultSetMetaData metaData = resultSet.getMetaData();
int cols_len = metaData.getColumnCount();
while(resultSet.next()){
//通过反射机制创建一个实例
T resultObject = cls.newInstance();
for(int i = 0; i<cols_len; i++){
String cols_name = metaData.getColumnName(i+1);
Object cols_value = resultSet.getObject(cols_name);
if(cols_value == null){
cols_value = "";
}
Field field = cls.getDeclaredField(cols_name);
field.setAccessible(true); //打开javabean的访问权限
field.set(resultObject, cols_value);
}
list.add(resultObject);
}
releaseConn();
return list;
}
/**
* 释放数据库连接
*/
public void releaseConn(){
if(resultSet != null){
try{
resultSet.close();
}catch(SQLException e){
e.printStackTrace();
}
}
if(pstmt != null){
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection!=null && datasource!=null){
datasource.recover(connection);
connection = null;
}
}
2、然后就是对配置文件的读取:
考虑下我们需要到的配置相关:
1、数据库连接相关。
2、数据源相关。
3、mapper/dao包相关。
最好把配置都预先统一定义:
/**
* 系统配置相关定义
* Created by Nemo on 2017/12/22.
*/
public class PropKey {
/**
* 配置文件名称
*/
public static String PROPERTIES_FILE_NAME = "config.properties";
/**
* mapper基本路径配置定义
*/
public static String MAPPER_BASE = "mapper.base";
/**
* dao基本配置定义
*/
public static String DAO_BASE = "dao.base";
/**
* 数据源初始化大小配置
*/
public static String DATASOURCE_INIT_SIZE = "datasource.init.size";
/**
* 数据源允许最大连接数配置
*/
public static String DATASOURCE_MAX_SIZE = "datasource.max.size";
/**
* 数据库驱动定义
*/
public static String JDBC_DRIVER= "jdbc.driver";
/**
* 数据库用户名定义
*/
public static String JDBC_USERNAME = "jdbc.username";
/**
* 数据库连接地址定义
*/
public static String JDBC_URL = "jdbc.url";
/**
* 数据库密码定义