关于后端:带码农手写Mybatis进度3实现映射器的注册和使用

2次阅读

共计 6372 个字符,预计需要花费 16 分钟才能阅读完成。

作者:小傅哥
博客:https://bugstack.cn

积淀、分享、成长,让本人和别人都能有所播种!😄

一、前言

如何面对简单零碎的设计?

咱们能够把 Spring、Mybatis、Dubbo 这样的大型框架或者一些公司外部的较外围我的项目,都能够称为简单的零碎。这样的工程也不在是初学编程手里的玩具我的项目,没有所谓的 CRUD,更多时候要面对的都是对系统分层的结构设计和聚合逻辑性能的实现,再通过层层转换进行实现和调用。

这对于很多刚上道的小码农来说,会感觉十分好受,不晓得要从哪下手,但又想着能够一口吃个瘦子。其实这是不事实的,因为这些简单零碎中的框架中有太多的内容你还没用理解和相熟,越是硬搞越好受,信念越受打击。

其实对于解决这类简单的我的项目问题,外围在于要将骨干问题点放大,具体的伎俩包含:分治、形象和常识。使用设计模式和设计准则等相干常识,把问题空间正当切割为若干子问题,问题越小也就越容易了解和解决。就像你能够把很多内容做成单个独立的案例一样,最终在进行聚合应用。

二、指标

在上一章节咱们初步的理解了怎么给一个接口类生成对应的映射器代理,并在代理中实现一些用户对接口办法的调用解决。尽管咱们曾经看到了一个外围逻辑的解决形式,但在应用上还是有些刀耕火种的,包含:须要编码告知 MapperProxyFactory 要对哪个接口进行代理,以及本人编写一个假的 SqlSession 解决理论调用接口时的返回后果。

那么联合这两块问题点,咱们本章节要对映射器的注册提供注册机解决,满足用户能够在应用的时候提供一个包的门路即可实现扫描和注册。与此同时须要对 SqlSession 进行规范化解决,让它能够把咱们的映射器代理和办法调用进行包装,建设一个生命周期模型构造,便于后续的内容的增加。

三、设计

鉴于咱们心愿把整个工程包下对于数据库操作的 DAO 接口与 Mapper 映射器关联起来,那么就须要包装一个能够扫描包门路的实现映射的注册器类。

当然咱们还要把上一章节中简化的 SqlSession 进行欠缺,由 SqlSession 定义数据库解决接口和获取 Mapper 对象的操作,并把它交给映射器代理类进行应用。这一部分是对上一章节内容的欠缺

有了 SqlSession 当前,你能够把它了解成一种性能服务,有了性能服务当前还须要给这个性能服务提供一个工厂,来对外对立提供这类服务。比方咱们在 Mybatis 中十分常见的操作,开启一个 SqlSession。整个设计能够如图 3-1

  • 以包装接口提供映射器代理类为指标,补全映射器注册机 MapperRegistry,主动扫描包下接口并把每个接口类映射的代理类全副存入映射器代理的 HashMap 缓存中。
  • 而 SqlSession、SqlSessionFactory 是在此注册映射器代理的上次层应用规范定义和对外服务提供的封装,便于用户应用。咱们把应用方当成用户 通过这样的封装就就能够更加不便咱们后续在框架上性能的持续扩大了,也心愿大家能够在学习的过程中对这样的设计构造有一些思考,它能够帮忙你解决一些业务性能开发过程中的畛域服务包装。

四、实现

1. 工程构造

mybatis-step-02
└── src
    ├── main
    │   └── java
    │       └── cn.bugstack.mybatis
    │           ├── binding
    │           │   ├── MapperProxy.java
    │           │   ├── MapperProxyFactory.java
    │           │   └── MapperRegistry.java
    │           └── session
    │               ├── defaults
    │               │   ├── DefaultSqlSession.java
    │               │   └── DefaultSqlSessionFactory.java
    │               ├── SqlSession.java
    │               └── SqlSessionFactory.java
    └── test
        └── java
            └── cn.bugstack.mybatis.test.dao
                ├── dao
                │   ├── ISchoolDao.java
                │   └── IUserDao.java
                └── ApiTest.java

工程源码:https://t.zsxq.com/bmqNFQ7

映射器规范定义实现关系,如图 3-2

  • MapperRegistry 提供包门路的扫描和映射器代理类注册机服务,实现接口对象的代理类注册解决。
  • SqlSession、DefaultSqlSession 用于定义执行 SQL 规范、获取映射器以及未来治理事务等方面的操作。根本咱们平时应用 Mybatis 的 API 接口也都是从这个接口类定义的办法进行应用的。
  • SqlSessionFactory 是一个简略工厂模式,用于提供 SqlSession 服务,屏蔽创立细节,提早创立过程。

2. 映射器注册机

源码详见cn.bugstack.mybatis.binding.MapperRegistry

public class MapperRegistry {

    /**
     * 将已增加的映射器代理退出到 HashMap
     */
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null) {throw new RuntimeException("Type" + type + "is not known to the MapperRegistry.");
        }
        try {return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {throw new RuntimeException("Error getting mapper instance. Cause:" + e, e);
        }
    }

    public <T> void addMapper(Class<T> type) {
        /* Mapper 必须是接口才会注册 */
        if (type.isInterface()) {if (hasMapper(type)) {
                // 如果反复增加了,报错
                throw new RuntimeException("Type" + type + "is already known to the MapperRegistry.");
            }
            // 注册映射器代理工厂
            knownMappers.put(type, new MapperProxyFactory<>(type));
        }
    }

    public void addMappers(String packageName) {Set<Class<?>> mapperSet = ClassScanner.scanPackage(packageName);
        for (Class<?> mapperClass : mapperSet) {addMapper(mapperClass);
        }
    }

}
  • MapperRegistry 映射器注册类的外围次要在于提供了 ClassScanner.scanPackage 扫描包门路,调用 addMapper 办法,给接口类创立 MapperProxyFactory 映射器代理类,并写入到 knownMappers 的 HashMap 缓存中。
  • 另外就是这个类也提供了对应的 getMapper 获取映射器代理类的办法,其实这步就包装了咱们上一章节手动操作实例化的过程,更加不便在 DefaultSqlSession 中获取 Mapper 时进行应用。

3. SqlSession 规范定义和实现

源码详见cn.bugstack.mybatis.session.SqlSession

public interface SqlSession {

    /**
     * Retrieve a single row mapped from the statement key
     * 依据指定的 SqlID 获取一条记录的封装对象
     *
     * @param <T>       the returned object type 封装之后的对象类型
     * @param statement sqlID
     * @return Mapped object 封装之后的对象
     */
    <T> T selectOne(String statement);

    /**
     * Retrieve a single row mapped from the statement key and parameter.
     * 依据指定的 SqlID 获取一条记录的封装对象,只不过这个办法答应咱们能够给 sql 传递一些参数
     * 个别在理论应用中,这个参数传递的是 pojo,或者 Map 或者 ImmutableMap
     *
     * @param <T>       the returned object type
     * @param statement Unique identifier matching the statement to use.
     * @param parameter A parameter object to pass to the statement.
     * @return Mapped object
     */
    <T> T selectOne(String statement, Object parameter);

    /**
     * Retrieves a mapper.
     * 失去映射器,这个奇妙的应用了泛型,使得类型平安
     *
     * @param <T>  the mapper type
     * @param type Mapper interface class
     * @return a mapper bound to this SqlSession
     */
    <T> T getMapper(Class<T> type);

}
  • 在 SqlSession 中定义用来执行 SQL、获取映射器对象以及后续治理事务操作的标准接口。
  • 目前这个接口中对于数据库的操作仅仅只提供了 selectOne,后续还会有相应其余办法的定义。

源码详见cn.bugstack.mybatis.session.defaults

public class DefaultSqlSession implements SqlSession {

    /**
     * 映射器注册机
     */
    private 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);
    }

}
  • 通过 DefaultSqlSession 实现类对 SqlSession 接口进行实现。
  • getMapper 办法中获取映射器对象是通过 MapperRegistry 类进行获取的,后续这部分会被配置类进行替换。
  • 在 selectOne 中是一段简略的内容返回,目前还没有与数据库进行关联,这部分在咱们渐进式的开发过程中逐渐实现。

4. SqlSessionFactory 工厂定义和实现

源码详见cn.bugstack.mybatis.session.SqlSessionFactory

public interface SqlSessionFactory {

    /**
     * 关上一个 session
     * @return SqlSession
     */
   SqlSession openSession();}
  • 这其实就是一个简略工厂的定义,在工厂中提供接口实现类的能力,也就是 SqlSessionFactory 工厂中提供的开启 SqlSession 的能力。

源码详见cn.bugstack.mybatis.session.defaults.DefaultSqlSessionFactory

public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private final MapperRegistry mapperRegistry;

    public DefaultSqlSessionFactory(MapperRegistry mapperRegistry) {this.mapperRegistry = mapperRegistry;}

    @Override
    public SqlSession openSession() {return new DefaultSqlSession(mapperRegistry);
    }

}
  • 默认的简略工厂实现,解决开启 SqlSession 时,对 DefaultSqlSession 的创立以及传递 mapperRegistry,这样就能够在应用 SqlSession 时获取每个代理类的映射器对象了。

五、测试

1. 当时筹备

在同一个包门路下,提供 2 个以上的 Dao 接口:

public interface ISchoolDao {String querySchoolName(String uId);

}

public interface IUserDao {String queryUserName(String uId);

    Integer queryUserAge(String uId);

}

2. 单元测试

@Test
public void test_MapperProxyFactory() {
    // 1. 注册 Mapper
    MapperRegistry registry = new MapperRegistry();
    registry.addMappers("cn.bugstack.mybatis.test.dao");
    
    // 2. 从 SqlSession 工厂获取 Session
    SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(registry);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    
    // 3. 获取映射器对象
    IUserDao userDao = sqlSession.getMapper(IUserDao.class);
    
    // 4. 测试验证
    String res = userDao.queryUserName("10001");
    logger.info("测试后果:{}", res);
}
  • 在单元测试中通过注册机扫描包门路注册映射器代理对象,并把注册机传递给 SqlSessionFactory 工厂,这样实现一个链接过程。
  • 之后通过 SqlSession 获取对应 DAO 类型的实现类,并进行办法验证。

测试后果

22:43:23.254 [main] INFO  cn.bugstack.mybatis.test.ApiTest - 测试后果:你被代理了!办法:queryUserName 入参:[Ljava.lang.Object;@50cbc42f

Process finished with exit code 0
  • 通过测试大家能够看到,目前咱们曾经在一个有 Mybatis 影子的手写 ORM 框架中,实现了代理类的注册和应用过程。

六、总结

  • 首先要从设计构造上理解工厂模式对具体性能构造的封装,屏蔽过程细节,限定上下文关系,把对外的应用缩小耦合。
  • 从这个过程上读者搭档也能发现,应用 SqlSessionFactory 的工厂实现类包装了 SqlSession 的规范定义实现类,并由 SqlSession 实现对映射器对象的注册和应用。
  • 本章学习要留神几个重要的知识点,包含:映射器、代理类、注册机、接口标准、工厂模式、上下文。这些工程开发的技巧都是在手写 Mybatis 的过程中十分重要的局部,理解和相熟能力更好的在本人的业务中进行应用。
正文完
 0