Nemo

Nemo 关注TA

路漫漫其修远兮,吾将上下而求索。

Nemo

Nemo

关注TA

路漫漫其修远兮,吾将上下而求索。

  •  普罗旺斯
  • 负责帅就完事了
  • 写了1,496,113字

该文章投稿至Nemo社区   Java  板块 复制链接


NemoDao - 简单模仿mybatis实现一个持久层框架

发布于 2017/12/22 18:39 2,428浏览 2回复 11,619

嗯,这几天用稍稍闲散的时间模仿着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";

/**
* 数据库密码定义
本文标签
 {{tag}}
点了个评