自定义类似Mybatis的持久层框架

1. 一个最基本的通过JDBC操作数据库示例:
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql:///mybatis-leanring?user=root&password=root";
Connection conn = DriverManager.getConnection(url);
Statement stmt = conn.createStatement();

sql = "SELECT * FROM USER";
stmt.executeUpdate(sql);
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
    System.out.println(rs.getString(1) + "\t" + rs.getString(2));
}

通过JDBC操作数据库有一些问题

  1. 数据库连接硬编码
  2. 频繁创建释放连接,对系统资源消耗较大
  3. JAVA代码与SQL代码耦合在一起,不便于维护
  4. 结果集提取很麻烦

如何来解决这些问题呢?

针对以上问题,我们可以做如下尝试:

  1. 数据库连接放到配置文件中
  2. 用连接池来管理connection
  3. sql文件放到配置文件中
  4. 用java 反射与内省机制来自动提取结果集
2. 框架设想
  • 用一个SqlMapConfig.xml来放置数据库连接信息配置文件
    配置文件解析出来,用一个类来存放配置信息,姑且叫他Configuration,如何解析呢,dom4j可以实现,我们用一个SqlSessionFactoryBuilder来解析
  • 连接池选用ComboPooledDataSource,这样连接我们就交由连接池来处理
  • sql相关代码放到xxMapper.xml中
    但是sql可能有很多,比如查询用户,新增商品,更新订单状态等,不如直接按照一个实体一个mapper分开放置,这样便于管理。所以我们应该有很多个xxxMapper.xml文件,那每个Mapper.xml搞一个namespace来区分吧。
    每个mapper.xml中就放置一个实体的处理,比如GoodsMapper.xml:selectList, selectOne, deleteByPrimaryKey, updateByPrimaryKey等都是操作商品的,用<select>,<update><delete><insert>分别来配置对应的sql,每个节点还应该有个id来区分不同的sql配置块。
    Sql执行可能有参数,那搞一个ParameterHandler来专门负责解析并设置参数吧
  • 结果集提取搞一个ResultSetHandler来处理
    再想想,我们操作数据库通常来说不外乎增删改查,我们能否提供一个通用工具来处理呢,比如sqlUtil.selectList,sqlUtil.selectOne, sqlUtil.update, sqlUtil.delete等,就叫做SqlSession吧
    于是:
src/main/resources:
    SqlMapConfig.xml —— 数据库配置文件
    UserMapper.xml —— SQL配置文件
java/main/java:
    config:
       Configuration.java
       XmlConfigBuilder.java  —— 解析SqlMapConfig.xml文件
       MappedStatement.java —— mapper.xml中的单个sql配置块对应的实体
       XmlMapperBuilder.java —— 解析mapper.xml文件
       SqlCommandType.java —— 用于区分不同的SQL类型,例如:SELECT,INSERT,UPDATE,DELETE
    sqlSession:
       SqlSessionFactoryBuilder.java —— 解析配置,并生成SessionSessionFactory
       SqlSessionFactory —— 生产SqlSession的工厂
       SqlSession —— 操作数据库的对象
   handler:
       ParameterHandler.java —— 解析并设置参数
       ResultSetHandler.java —— 提取结果集

如何查询呢,

通过[namespace].[id]就可以定位到具体的sql,然后执行即可。
例如查询用户

sqlSession.selectList("com.test.User.selectList")

每个操作都用sqlSession.(statmentId, params)来执行的话,又出现了硬编码,
Mapper.xml是已知的,Mapper.xml中的sql是已知的,也即是namespace.id是已知的,同时,执行sql不外乎crud,那不如搞个接口,来配置这些项目,然后通过动态代理来统一处理,解决这个问题。

UserMapper.xml

List<User> selectList(User user);
User selectOne(User user);
int update(User user);
int delete(int id);
void insert(User user);

SqlSession.java
<T> T getMapper(Class<T> t);
当使用动态代理时,对代理对象调用任意方法,将执行handler中的invoke方法,这样就可以通过反射来统一处理。

  public <T> T getMapper(Class<?> mapperClass) {
        // 使用JDK动态代理来为Dao接口生成代理对象,并返回

        Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(),
                new Class[] {mapperClass}, (proxy, method, args) -> {
                    String methodName = method.getName();
                    String className = method.getDeclaringClass().getName();
                    String statementId = className + "." + methodName;

                    MappedStatement mappedStatement =
                            configuration.getMappedStatementMap().get(statementId);
                    SqlCommandType commandType = mappedStatement.getSqlCommandType();
                    switch (commandType) {
                        case SELECT:
                            // 通过返回类型判断是否是List类型
                            Class returnType = method.getReturnType();
                            if (List.class.isAssignableFrom(returnType)) {
                                // 判断是否进行了 泛型类型参数化
                                List<Object> objects = selectList(statementId, args);
                                return objects;
                            }
                            return selectOne(statementId, args);
                        case INSERT:
                        case DELETE:
                        case UPDATE:
                            return update(statementId, args);
                        default:
                            return null;
                    }
                });

        return (T) proxyInstance;
    }

如何支持事务?
直接采用jdbc的事务管理

try {
    connection.setAutoCommit(false);
    // do something here
    connnection.commit();
}

当然,我们提供由SqlSession来操作事务的方法,这样使用方才能方便的使用事务。
SqlSession调用Executor的相关方法, Executor中有个单例获取的Connection,最终的事务操作落在connection上面。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容