【简写Mybatis-02】注册机的实现以及SqlSession处理

mybatis,sqlsession · 浏览次数 : 0

小编点评

前言 注意:学习源码一定一定不要太关注代码的编写,而是注意代码实现思想:通过设问方式来体现代码中的思想; 方法:5W+1H源代码:https://gitee.com/xbhog/mybatis-xbhog ; https://github.com/xbhog/mybatis-xbhog ;交个朋友,有价值欢迎star。 回顾&分析 上一局实现Mapper接口和映射器通过代理类的方式进行链接。 上一局测试类:【简写MyBatis】01-简单映射器;虽然基本功能实现了,但是还不智能,可以发现该测试类中的映射器代理工厂只能实现单一的接口代理,SqlSession也需要规范化处理;将映射器代理和方法的调用类似服务进行包装。 目的 根据包路径实现接口的扫描和注册SqlSession规范化处理实现项目结构: 铺垫结束,开始上代码。 先扫描包下所有的Class类,然后保存到Map中。 将上一局的接口和代理工厂操作封装进方法中。 public interface SqlSession { T selectOne(String statement,Object parameter); default T getMapper(Class type){ //代理对象创建逻辑 return null; } } 接口实现方式: package com.xbhog.session; import com.xbhog.binding.MapperRegistry; /*** * @author xbhog * @describe: * @date 2024/2/25 */ public class DefaultSqlSession implements SqlSession { private MapperRegistry mapperRegistry; public DefaultSqlSession(MapperRegistry mapperRegistry){ this.mapperRegistry = mapperRegistry; } @Override public T selectOne(String statement,Object parameter){ //代理后的查询逻辑 return null; } @Override public T getMapper(Class type){ //代理对象创建逻辑 return null; } } 测试一下: package com.xbhog; import com.xbhog.binding.MapperProxyFactory; import com.xbhog.binding.MapperRegistry; import com.xbhog.session.DefaultSqlSession; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import java.util.HashMap; import java.util.Map; /*** * Unit test for simple App. */ public class AppTest extends TestCase { public void testApp() { MapperRegistry mapperRegistry = new MapperRegistry(); mapperRegistry.addMapper("com.xbhog"); DefaultSqlSession sqlSession = new DefaultSqlSession(mapperRegistry); IUserDao user = sqlSession.getMapper(IUserDao.class); String userName = user.getUserName("xbhog"); System.out.println("输出的信息:" + userName); } } 到这里其实已经可以满足需求了,但是看了下源码发现还是不行,它最外层又封装了一层代理工厂;应该是为了后续的代码扩展,简单工厂模式有助于代码的模块性和可维护性,功能上后续会有配置管理、资源管理、执行器选择和插件等需求;走一步看三步的老狐狸(┬┬﹏┬┬)。 先抄作业。 package com.xbhog.session; import com.xbhog.binding.MapperRegistry; /*** * @author xbhog * @describe: * @date 2024/2/25 */ public class DefaultSqlSessionFactory implements SqlSessionFactory { private final MapperRegistry mapperRegistry; public DefaultSqlSessionFactory(MapperRegistry mapperRegistry){ this.mapperRegistry = mapperRegistry; } @Override public SqlSession openSession() { return new DefaultSqlSession(mapperRegistry); } } 测试 package com.xbhog; import com.xbhog.binding.MapperProxyFactory; import com.xbhog.binding.MapperRegistry; import com.xbhog.session DefaultSqlSession; import com.xbhog.session DefaultSqlSessionFactory; import com.xbhog.session.SqlSession; import com.xbhog.session.SqlSessionFactory; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import java.util.HashMap; import java.util.Map; /*** * Unit test for simple App. */ public class AppTest extends TestCase { public void testApp() { MapperRegistry mapperRegistry = new MapperRegistry(); mapperRegistry.addMapper("com.xbhog"); DefaultSqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(mapperRegistry); SqlSession sqlSession = sqlSessionFactory.openSession(); IUserDao user = sqlSession.getMapper(IUserDao.class); String userName = user.getUserName("xbhog"); System.out.println("输出的信息:" + userName); } } 总结 What(什么) :MapperRegistry是一个注册表,用于存储映射器接口(Mapper Interface)和对应的MapperProxyFactory。它负责管理映射器接口的生命周期。 Why(为什么) :MapperRegistry的存在是为了确保映射器接口能够被MyBatis框架识别和管理,以便在运行时为这些接口创建代理对象,实现数据库操作的动态绑定。 Who(谁) :MapperRegistry的使用者是MyBatis框架自身,它内部使用MapperRegistry来处理映射器接口的注册和代理对象的创建。 Where(在哪里) :MapperRegistry是MyBatis框架的一部分,通常在MyBatis配置初始化时创建,并在整个应用程序的生命周期中存在。 When(何时) :MapperRegistry的注册发生在MyBatis应用程序启动时,特别是在构建SqlSessionFactory的过程中。 How(如何) :MapperRegistry通过扫描指定包下的映射器接口,并将它们与对应的MapperProxyFactory关联起来。当需要执行映射器接口中的方法时,MapperRegistry会使用MapperProxyFactory来创建一个MapperProxy代理对象。DefaultSqlSessionFactory通过解析MyBatis的配置文件(如mybatis-config.xml,下一节的操作)来创建。它提供了openSession()方法,用于创建SqlSession实例。开发者可以通过SqlSession实例来执行映射器接口中定义的数据库操作。 需要注意的是:通过这两节可以看到mybatis中运用了大量的工厂模式;对外提供统一的方法,屏蔽细节以及上下文的关联关系,最终目的服务于用户,简化使用。 参考:https://mp.weixin.qq.com/s/o6lnWJqU_6FNO8HpxAs9gAChatGPT问答。

正文

前言

注意:

学习源码一定一定不要太关注代码的编写,而是注意代码实现思想:

通过设问方式来体现代码中的思想;方法:5W+1H

源代码: https://gitee.com/xbhog/mybatis-xbhog https://github.com/xbhog/mybatis-xbhog ;交个朋友,有价值欢迎star。

回顾&分析

上一局实现Mapper接口和映射器通过代理类的方式进行链接。

上一局测试类:【简写MyBatis】01-简单映射器;虽然基本功能实现了,但是还不智能,可以发现该测试类中的映射器代理工厂只能实现单一的接口代理,SqlSession也需要规范化处理;将映射器代理和方法的调用类似服务进行包装。

 @Test
 public void test_MapperProxyFactory() {
     MapperProxyFactory<IUserDao> factory = new MapperProxyFactory<>(IUserDao.class);
 ​
     Map<String, String> sqlSession = new HashMap<>();
     sqlSession .put("com.xbhog.IUserDao.getUserName","模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户姓名");
     IUserDao userDao = factory.newInstance(sqlSession);
 ​
     String res = userDao.queryUserName("10001");
     logger.info("测试结果:{}", res);
 }

目的

  1. 根据包路径实现接口的扫描和注册
  2. SqlSession规范化处理

实现

项目结构:

 └─src 
   ├─main 
   │ └─java 
   │   └─com 
   │     └─xbhog 
   │       ├─binding 
   │       │ ├─MapperProxy.java 
   │       │ ├─MapperProxyFactory.java 
   │       │ └─MapperRegistry.java 
   │       └─session 
   │         ├─DefaultSqlSession.java 
   │         ├─DefaultSqlSessionFactory.java 
   │         ├─SqlSession.java 
   │         └─SqlSessionFactory.java 
   └─test 
     └─java 
       └─com 
         └─xbhog 
           ├─AppTest.java 
           └─IUserDao.java 

项目类图

img

根据包路径实现接口的扫描和注册

可以通过自定义MapperRegistry这个类实现该功能,当然你也可以叫其他的名字(zhangsan、lisi😅);我们只需要将上一局的MapperProxyFactory封装到MapperRegistry,把需要扫描和注册的接口保存到Map中进行预处理,最后代理进行随时使用就可以了;铺垫结束,开始上代码。

先扫描包下所有的Class类,然后保存到Map中。

 package com.xbhog;
 ​
 import cn.hutool.core.lang.ClassScanner;
 ​
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 ​
 /**
  * @author xbhog
  * @describe: 接口注册器
  * @date 2024/2/25
  */
 public class MapperRegistry {
 ​
     private final Map<Class<?>,MapperProxyFactory<?>> interfaceMaps = new HashMap<>();
     public void addMapper(String packageName){
         Set<Class<?>> scanPackage = ClassScanner.scanPackage(packageName);
         scanPackage.forEach(clazz -> {
             addMappers(clazz);
         });
     }
 ​
     private void addMappers(Class<?> clazz) {
         if(clazz.isInterface()){
             //判断是否重复添加
             if(haveInterface(clazz)){
                 throw new RuntimeException("Type " + clazz + " is already known to the MapperRegistry.");
             }
         }
         // 注册映射器代理工厂
         interfaceMaps.put(clazz, new MapperProxyFactory<>(clazz));
     }
 ​
     private boolean haveInterface(Class<?> clazz) {
         return interfaceMaps.containsKey(clazz);
     }
 ​
 }

然后将上一局的接口和代理工厂操作封装进方法中。

 public <T> T getMapper(Class<T> type, SqlSession sqlSession){
     MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) interfaceMaps.get(type);
     if(Objects.isNull(mapperProxyFactory)){
         throw new RuntimeException("Type " + type + " is not known to the MapperRegistry.");
     }
     return (T)mapperProxyFactory.newInstance(sqlSession);
 }

SqlSession规范化处理

先定义一个执行Sql、获取映射器的标准接口:

 /**
  * @author xbhog
  * @describe: 定义简单的Mapper操作方法
  * @date 2024/2/25
  */
 public interface SqlSession {
 ​
     <T> T selectOne(String statement,Object parameter);
 ​
     /**
      *得到接口映射器
      * @param type 接口类型
      * @return
      */
     <T> T getMapper(Class<T> type);
 }

接口实现方式:

 package com.xbhog.session;
 ​
 import com.xbhog.binding.MapperRegistry;
 ​
 /**
  * @author xbhog
  * @describe:
  * @date 2024/2/25
  */
 public class DefaultSqlSession implements SqlSession{
 ​
     private MapperRegistry mapperRegistry;
 ​
     public DefaultSqlSession(MapperRegistry mapperRegistry) {
         this.mapperRegistry = mapperRegistry;
     }
 ​
     @Override
     public <T> T  selectOne(String statement,Object parameter) {
         return (T) ("你被代理了!" + "方法:" + statement + " 入参:" + parameter);
     }
 ​
     @Override
     public <T> T getMapper(Class<T> type) {
         return mapperRegistry.getMapper(type,this);
     }
 }

测试一下:

 package com.xbhog;
 ​
 import com.xbhog.binding.MapperProxyFactory;
 import com.xbhog.binding.MapperRegistry;
 import com.xbhog.session.DefaultSqlSession;
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
 ​
 import java.util.HashMap;
 import java.util.Map;
 ​
 /**
  * Unit test for simple App.
  */
 public class AppTest extends TestCase {
     /**
      * Rigourous Test :-)
      */
     public void testApp() {
         MapperRegistry mapperRegistry = new MapperRegistry();
         mapperRegistry.addMapper("com.xbhog");
         DefaultSqlSession sqlSession = new DefaultSqlSession(mapperRegistry);
         IUserDao user = sqlSession.getMapper(IUserDao.class);
         String userName = user.getUserName("xbhog");
         System.out.println("输出的信息:"+userName);
     }
 }

img

到这里其实已经可以满足需求了,但是看了下源码发现还是不行,它最外层又封装了一层代理工厂;应该是为了后续的代码扩展,简单工厂模式有助于代码的模块性和可维护性,功能上后续会有配置管理、资源管理、执行器选择和插件等需求;走一步看三步的老狐狸(┬┬﹏┬┬)。先抄作业。

 package com.xbhog.session;
 ​
 import com.xbhog.binding.MapperRegistry;
 ​
 /**
  * @author xbhog
  * @describe:
  * @date 2024/2/25
  */
 public class DefaultSqlSessionFactory implements SqlSessionFactory{
 ​
     private final MapperRegistry mapperRegistry;
 ​
     public DefaultSqlSessionFactory(MapperRegistry mapperRegistry) {
         this.mapperRegistry = mapperRegistry;
     }
 ​
     @Override
     public SqlSession openSession() {
         return new DefaultSqlSession(mapperRegistry);
     }
 }

测试

 package com.xbhog;
 ​
 import com.xbhog.binding.MapperProxyFactory;
 import com.xbhog.binding.MapperRegistry;
 import com.xbhog.session.DefaultSqlSession;
 import com.xbhog.session.DefaultSqlSessionFactory;
 import com.xbhog.session.SqlSession;
 import com.xbhog.session.SqlSessionFactory;
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
 ​
 import java.util.HashMap;
 import java.util.Map;
 ​
 /**
  * Unit test for simple App.
  */
 public class AppTest extends TestCase {
     /**
      * Rigourous Test :-)
      */
     public void testApp() {
         MapperRegistry mapperRegistry = new MapperRegistry();
         mapperRegistry.addMapper("com.xbhog");
         DefaultSqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(mapperRegistry);
         SqlSession sqlSession = sqlSessionFactory.openSession();
         IUserDao user = sqlSession.getMapper(IUserDao.class);
         String userName = user.getUserName("xbhog");
         System.out.println("输出的信息:"+userName);
     }
 }

img

总结

  1. What(什么)
  • MapperRegistry是一个注册表,用于存储映射器接口(Mapper Interface)和对应的MapperProxyFactory。它负责管理映射器接口的生命周期。
  • DefaultSqlSessionFactory是MyBatis框架中用于创建SqlSession的工厂类。SqlSession是MyBatis的核心接口,用于执行SQL命令和获取映射结果。
  1. Why(为什么)
  • MapperRegistry的存在是为了确保映射器接口能够被MyBatis框架识别和管理,以便在运行时为这些接口创建代理对象,实现数据库操作的动态绑定。
  • DefaultSqlSessionFactory的目的是为了提供一个统一的入口点,用于创建和管理SqlSession实例。这样可以保证SqlSession的创建和关闭遵循统一的规范,同时提供了会话管理的能力。
  1. Who(谁)
  • MapperRegistry的使用者是MyBatis框架自身,它内部使用MapperRegistry来处理映射器接口的注册和代理对象的创建。
  • DefaultSqlSessionFactory的使用者是应用程序的开发者,他们通过SqlSessionFactory来获取SqlSession实例,进而执行数据库操作。
  1. Where(在哪里)
  • MapperRegistry是MyBatis框架的一部分,通常在MyBatis配置初始化时创建,并在整个应用程序的生命周期中存在。
  • DefaultSqlSessionFactory通常在应用程序启动时创建,并保存在一个全局的变量中,以便在需要时获取SqlSession实例。
  1. When(何时)
  • MapperRegistry的注册发生在MyBatis应用程序启动时,特别是在构建SqlSessionFactory的过程中。
  • DefaultSqlSessionFactory的创建也是在应用程序启动时,通常是在初始化阶段,用于后续的数据库操作。
  1. How(如何)
  • MapperRegistry通过扫描指定包下的映射器接口,并将它们与对应的MapperProxyFactory关联起来。当需要执行映射器接口中的方法时,MapperRegistry会使用MapperProxyFactory来创建一个MapperProxy代理对象。
  • DefaultSqlSessionFactory通过解析MyBatis的配置文件(如mybatis-config.xml,下一节的操作)来创建。它提供了openSession()方法,用于创建SqlSession实例。开发者可以通过SqlSession实例来执行映射器接口中定义的数据库操作。

需要注意的是:通过这两节可以看到mybatis中运用了大量的工厂模式;对外提供统一的方法,屏蔽细节以及上下文的关联关系,最终目的服务于用户,简化使用。

参考

https://mp.weixin.qq.com/s/o6lnWJqU_6FNO8HpxAs9gA

ChatGPT问答

与【简写Mybatis-02】注册机的实现以及SqlSession处理相似的内容:

【简写Mybatis-02】注册机的实现以及SqlSession处理

学习源码一定一定不要太关注代码的编写,而是注意代码实现思想:通过设问方式来体现代码中的思想;方法:5W+1H

聊聊Mybatis框架原理

好久没有写博客了。最近工作中封装了一个类似ORM框架的东西。大概的原理就是将Excel数据初始化到本地sqlite数据库后,通过json配置文件,对数据库的数据做增删改查等操作。 其实大概的思考了下,就是半ORM框架mybatis的逻辑,只是我们自己封装的简陋蛮多。想想有现成的轮子没用,反而是自己写

MyBatis实现动态SQL更新

博主记得在一个周五快下班的下午,产品找到我(为什么总感觉周五快下班就来活 😂),跟我说有几个业务列表查询需要加上时间条件过滤数据,这个条件可能会变,不保证以后不修改,这个改动涉及到多个列表查询,于是博主思考了一会想了几种实现方案, 1. 最简单,直接将时间条件写死,由 Service 层传递给 D

推荐一款优秀电商开源项目

一、简介 本文给大家推荐博主自己开源的电商项目newbee-mall-pro。在newbee-mall项目的基础上搭建而来, 使用 mybatis-plus 作为 orm 层框架,并添加了一系列高级功能以及代码优化并且已经升级至 Spring Boot3.0,项目功能特性如下: 商城首页 【为你推荐

代码审计——基础(JAVAEE)

JAVAEE 目录JAVAEE常见框架Struct2(控制层)Hibernate(持久层(与数据库交互)(不用再写简单的sql语句,但是需要一些列复杂的配置文件))(全ORM模型)Spring(大管家)Spring MVC(控制层)Mybatis(半ORM)(目前的主流框架)SpringAOPIOC

多数据源管理:掌握@DS注解的威力

大家在日常后端开发过程,不可避免的会接触到需要用到配置多个数据源的场景,在这里,小编介绍一种简单方便的,只需要简单的配置和一个@DS注解就能实现动态数据源的方式,这种动态数据源底层原理是基于Mybatis-plus来实现的。

Angular cli 组件和服务的创建, 父传子,子传父,服务的简单使用

1:Angular cli 创建组件component ng g component components\right ng g c wave 简写 需要定位到根路径下即可创建组件 Could not find an NgModule. Use the skip-import option to s

[转帖]LEFT JOIN(左连接)、RIGHT JOIN(右连接)、FULL JOIN(全连接)、INNER JOIN(内连接)、CROSS JOIN(笛卡尔积)详解

标签: Mysql left outer join 和left join表达的是同一个意思,left join可看作是left outer join的简写; 同理right outer join 、full outer join也是一样的。 1 2 (1)left join(左连接)在两张表进行连接

MQTT(EMQX) - Linux CentOS Docker 安装

Linux CentOS Docker 安装 MQTT(EMQX), el7-amd64 => EL 是 Red Hat Enterprise Linux 的简写, “el7” 表示的是 centos7/redhat7, amd64 一般指:x86-64

SpringMVC-01-回顾MVC架构

1、什么是MVC MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件架构模式。 它通过将业务逻辑、页面控制、显示视图分离的方法来组织代码。 主要作用是降低了视图与业务逻辑间的双向偶合。 它不是一种设计模式,而是一种架构模式。当然不同的MVC存在差异。 Mo