• 【手写Mybatis】step02:实现映射器的注册和使用


    一、设计

    思考:1、映射器的注册提供注册机处理,满足用户可以在使用的时候提供一个包的路径即可完成扫描和注册,2、对 SqlSession 进行规范化处理,让它可以把我们的映射器代理和方法调用进行包装
    在这里插入图片描述

    sqlsessionFactory:sqlSession的创建工厂,通过openSession()创建出sqlsession
    sqlsession:规范化处理,其中selectOne是数据库的查询操作,getMapper获取对应的映射器
    MapperRegister:扫描包路径下的所有接口,并对其进行代理,注册到mapperProxyMap上
    MapperProxyFactory:上一步有说明,映射器MapperProxy工厂,newInstance()方法创建映射器
    MapperProxy:代理对象,对数据库进行操作

    二、实现

    在这里插入图片描述

    MapperRegistry 提供包路径的扫描和映射器代理类注册机服务,完成接口对象的代理类注册处理。

    SqlSession、DefaultSqlSession 用于定义执行 SQL 标准获取映射器以及将来管理事务等方面的操作。基本我们平常使用 Mybatis 的 API 接口也都是从这个接口类定义的方法进行使用的。

    SqlSessionFactory 是一个简单工厂模式,用于提供 SqlSession 服务,屏蔽创建细节,延迟创建过程。

    2.1代码结构

    
    mybatis-step-02
    └── src
        ├── main
        │   └── java
        │       └── com.qf.mybatis
        │           ├── bind
        │           │   ├── MapperProxy.java
        │           │   ├── MapperProxyFactory.java
        │           │   └── MapperRegistry.java
        │           └── session
        │               ├── defaults
        │               │   ├── DefaultSqlSession.java
        │               │   └── DefaultSqlSessionFactory.java
        │               ├── SqlSession.java
        │               └── SqlSessionFactory.java
        └── test
            └── java
                └──com.qf.mybatis
                    ├── dao
                    │   ├── ISchoolDao.java
                    │   └── IUserDao.java
                    └── ApiTest.java
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    2.2映射器注册机类

    package com.qf.mybatis.bind;
    
    import cn.hutool.core.lang.ClassScanner;
    import com.qf.mybatis.session.SqlSession;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    
    /**
     * 扫描指定路径下面的所有接口,并以map的形式储存(接口,实现的mapperProxyFactory)
     */
    public class MapperRegister {
        /**
         * 将已添加的映射器代理加入到 HashMap
         */
        private Map<Class<?>,MapperProxyFactory<?>> mapperProxyMap=new HashMap<Class<?>, MapperProxyFactory<?>>();
    
        /**
         * 扫描指定路径下所有的包(添加mapper)
         */
        public void addMapper(String packageName){
            //注册所有的mapper到hashMap
            Set<Class<?>> classes = ClassScanner.scanPackage(packageName);
            for (Class<?> aClass : classes) {
               addMapper(aClass);
            }
        }
    
        /**
         * 获取代理对象(映射器)缓存设计
         * @param
         * @param 
         */
        public <T> T getMapper(Class<T> type, SqlSession sqlSession){
            MapperProxyFactory<?> tMapperProxyFactory = mapperProxyMap.get(type);
            if (tMapperProxyFactory==null){
                throw new RuntimeException("Type"+type+"is not yet register to the known map");
            }
            try{
                return (T) tMapperProxyFactory.newInstance(sqlSession);
            }catch (Exception e){
                throw new RuntimeException("Error in getting mapper instance:"+e);
            }
        }
    
        public <T> void addMapper(Class<T> aClass){
            //判断是否为接口
            if (aClass.isInterface()){
                if (!hasRegister(aClass)){
                    throw new RuntimeException("Type"+aClass+"is already register to know map!");
                }
                mapperProxyMap.put(aClass,new MapperProxyFactory(aClass));
            }
        }
    
        public boolean hasRegister(Class<?> aClass){
            return mapperProxyMap.get(aClass)==null;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    MapperRegistry 映射器注册类的核心主要在于提供了 ClassScanner.scanPackage 扫描包路径,调用 addMapper 方法,给接口类创建 MapperProxyFactory 映射器代理类,并写入到 knownMappers 的 HashMap 缓存中。

    另外就是这个类也提供了对应的 getMapper 获取映射器代理类的方法,其实这步就包装了我们上一章节手动操作实例化的过程,更加方便在 DefaultSqlSession 中获取 Mapper 时进行使用

    2.3SqlSession的标准定义

    package com.qf.mybatis.session;
    
    public interface SqlSession {
    
        /**
         *先完善查找一个selectone,后续会有更多
         */
       <T> T selectOne(String statement);
    
        /**
         * 据指定的SqlID获取一条记录的封装对象,只不过这个方法容许我们可以给sql传递一些参数
         *      * 一般在实际使用中,这个参数传递的是pojo,或者Map或者ImmutableMap
         */
        <T> T selectOne(String statement,Object param);
    
        /**
         * 获取映射器,代理的实例对象
         */
         <T> T getMapper(Class<T> type);
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    目前只定义了selectOne查询,后续会新增

    getMapper获取已注册在MapperRegistry 的代理对象

    2.3SqlSession标准实现

    package com.qf.mybatis.session.bind;
    
    import com.qf.mybatis.bind.MapperRegister;
    import com.qf.mybatis.session.SqlSession;
    
    public class DefaultSqlSession implements SqlSession {
        private MapperRegister mapperRegister;
        public DefaultSqlSession(MapperRegister mapperRegister) {
            this.mapperRegister=mapperRegister;
        }
    
        /**
         * 目前是返回方法
         * @param statement
         * @param 
         * @return
         */
    
        public <T> T selectOne(String statement) {
            return (T) ("你被代理了"+statement);
        }
    
        public <T> T selectOne(String statement, Object param) {
            return (T) ("你被代理了"+"方法:"+statement+"入参:"+param);
        }
    
        public <T> T getMapper(Class<T> type) {
            return mapperRegister.getMapper(type,this);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    MapperRegistry 获取代理对象

    2.4SqlSessionFactory 工厂定义

    package com.qf.mybatis.session;
    
    
    public interface SqlSessionFactory {
    
        /**
         * 开启session,获取session对象
         */
        SqlSession openSession();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    简单工厂的接口,定义了开启sqlSession的能力

    package com.qf.mybatis.session.bind;
    
    import com.qf.mybatis.bind.MapperRegister;
    import com.qf.mybatis.session.SqlSession;
    import com.qf.mybatis.session.SqlSessionFactory;
    
    public class DefaultSqlSessionFactory implements SqlSessionFactory {
    
        private MapperRegister mapperRegister;
        public DefaultSqlSessionFactory(MapperRegister mapperRegister) {
            this.mapperRegister=mapperRegister;
        }
        public SqlSession openSession() {
            return new DefaultSqlSession(mapperRegister);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    简单工厂实现,处理开启 SqlSession 时,对 DefaultSqlSession 的创建以及传递 mapperRegistry,这样就可以在使用 SqlSession 时获取每个代理类的映射器对象了

    3.、测试

    定义的接口

    package com.qf.mybatis.dao;
    
    public interface UserDao {
        String queryName(String uid);
        String queryUser(String uid);
    }
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    package com.qf.mybatis.dao;
    
    import java.math.BigDecimal;
    
    public interface ProductDao {
        BigDecimal calPrice();
        String productName();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    ApiTest

    package com.qf.mybatis.test;
    
    import com.qf.mybatis.bind.MapperRegister;
    import com.qf.mybatis.dao.UserDao;
    import com.qf.mybatis.session.SqlSession;
    import com.qf.mybatis.session.SqlSessionFactory;
    import com.qf.mybatis.session.bind.DefaultSqlSessionFactory;
    import org.junit.Test;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class ApiTest {
        private Logger logger=LoggerFactory.getLogger(ApiTest.class);
    
        @Test
        public void sqlSessionTest(){
            //1、注册指定包路径下的dao接口,注册
            MapperRegister mapperRegister=new MapperRegister();
            mapperRegister.addMapper("com.qf.mybatis.dao");
    
            //2、从sqlSession工厂里面获取sqlSession
            SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(mapperRegister);
            SqlSession sqlSession = sqlSessionFactory.openSession();
    
            //3、获取映射器
            UserDao userDao = sqlSession.getMapper(UserDao.class);
    
            //4、验证结果
            String queryName = userDao.queryName("111L");
            logger.info("结果:{}",queryName);
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    结果:

    "C:\Program Files\Java\jdk1.8.0_301\bin\java.exe" -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:D:\idea2020\IntelliJ IDEA 2020.2.4\lib\idea_rt.jar=27634:D:\idea2020\IntelliJ IDEA 2020.2.4\bin" -Dfile.encoding=UTF-8 -classpath "D:\idea2020\IntelliJ IDEA 2020.2.4\lib\idea_rt.jar;D:\idea2020\IntelliJ IDEA 2020.2.4\plugins\junit\lib\junit5-rt.jar;D:\idea2020\IntelliJ IDEA 2020.2.4\plugins\junit\lib\junit-rt.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\rt.jar;D:\zhoutao\handwrite-mybatis\step-02-mapperregistery\target\test-classes;D:\zhoutao\handwrite-mybatis\step-02-mapperregistery\target\classes;C:\Users\Administrator\.m2\repository\com\alibaba\fastjson\1.2.75\fastjson-1.2.75.jar;C:\Users\Administrator\.m2\repository\org\dom4j\dom4j\2.1.3\dom4j-2.1.3.jar;C:\Users\Administrator\.m2\repository\junit\junit\4.7\junit-4.7.jar;C:\Users\Administrator\.m2\repository\cn\hutool\hutool-all\5.5.0\hutool-all-5.5.0.jar;C:\Users\Administrator\.m2\repository\org\slf4j\slf4j-api\1.7.5\slf4j-api-1.7.5.jar;C:\Users\Administrator\.m2\repository\org\slf4j\jcl-over-slf4j\1.7.5\jcl-over-slf4j-1.7.5.jar;C:\Users\Administrator\.m2\repository\ch\qos\logback\logback-classic\1.0.9\logback-classic-1.0.9.jar;C:\Users\Administrator\.m2\repository\ch\qos\logback\logback-core\1.0.9\logback-core-1.0.9.jar" com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit4 com.qf.mybatis.test.ApiTest,sqlSessionTest
    09:40:22.578 [main] INFO  com.qf.mybatis.test.ApiTest - 结果:你被代理了方法:queryName入参:[Ljava.lang.Object;@5fe5c6f
    
    Process finished with exit code 0
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    从结果来看,我们自定义的接口已经被MapperRegister 扫描到,完成了简单注册和代理功能

    4、总结

    SqlSessionFactory 的工厂实现类包装了 SqlSession 的标准定义实现类,并由 SqlSession 完成对映射器对象的注册和使用。

    本章学习要注意几个重要的知识点,包括:映射器、代理类、注册机、接口标准、工厂模式、上下文。这些工程开发的技巧都是在手写 Mybatis 的过程中非常重要的部分,了解和熟悉才能更好的在自己的业务中进行使用。

  • 相关阅读:
    按键精灵中的函数使用
    C++中sort()函数的greater<int>()参数
    【Java 进阶篇】Java会话技术之Cookie的存活时间
    【云原生之Docker实战】使用docker部署Jellyfin个人影音服务器
    探寻容器的本质
    SQL Server创建链接服务器的方法
    数据结构-链表的简单操作代码实现3-LinkedList【Java版】
    .NET 与Java 常见技术名词与抽象概念对照
    K8S知识点(三)
    基于STM32单片机设计指纹考勤机+上位机管理
  • 原文地址:https://blog.csdn.net/cativen/article/details/126942759