《手写Mybatis渐进式源码实践》实践笔记 (第三章 映射器的注册和使用)

文章目录

  • 第3章 映射器的注册和使用
    • 背景
      • 特点
      • 结构
      • 代码示例(Java)
      • 适用场景
      • 优点
      • 缺点
    • 目标
    • 设计
    • 实现
      • 工程代码
      • 类图
      • 实现步骤
        • 1.映射器注册机
        • 2. SqlSession 标准定义和实现
      • 3.SqlSessionFactory 工厂定义和实现
    • 测试
      • 事先准备
      • 测试用例
      • 测试结果
    • 总结


第3章 映射器的注册和使用

《手写Mybatis渐进式源码实践》实践笔记 (第三章 映射器的注册和使用)_第1张图片

背景

工厂模式(Factory Method Pattern)是一种创建型设计模式,它定义了一个用于创建对象的接口,但允许子类决定实例化哪一个类。工厂模式让类的实例化推迟到子类中进行。

特点

  1. 封装性:隐藏了对象创建的细节。
  2. 扩展性:新增产品类时,只需新增一个具体的工厂类,无需修改已有代码。
  3. 解耦:客户端代码与具体产品类解耦,只需与抽象产品和工厂接口交互。

结构

工厂方法模式通常包含以下几个角色:

  1. 抽象工厂(Creator):声明一个工厂方法,返回一个产品。
  2. 具体工厂(Concrete Creator):实现工厂方法,创建具体产品。
  3. 抽象产品(Product):声明一个接口或抽象类,定义产品共有的接口。
  4. 具体产品(Concrete Product):实现抽象产品的接口或继承抽象类的具体类。

代码示例(Java)

// 抽象产品
interface Product {
    void use();
}

// 具体产品A
class ConcreteProductA implements Product {
    @Override
    public void use() {
        System.out.println("Using ConcreteProductA");
    }
}

// 具体产品B
class ConcreteProductB implements Product {
    @Override
    public void use() {
        System.out.println("Using ConcreteProductB");
    }
}

// 抽象工厂
abstract class Creator {
    abstract Product factoryMethod();
}

// 具体工厂A
class ConcreteCreatorA extends Creator {
    @Override
    Product factoryMethod() {
        return new ConcreteProductA();
    }
}

// 具体工厂B
class ConcreteCreatorB extends Creator {
    @Override
    Product factoryMethod() {
        return new ConcreteProductB();
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        Creator creatorA = new ConcreteCreatorA();
        Product productA = creatorA.factoryMethod();
        productA.use();

        Creator creatorB = new ConcreteCreatorB();
        Product productB = creatorB.factoryMethod();
        productB.use();
    }
}

适用场景

  • 当一个系统不需要知道它所创建的对象的具体类时。
  • 当创建对象需要大量重复的代码时。
  • 当类将被多次实例化时。
  • 当类的实例化过程需要被封装时。

优点

  • 代码的可扩展性好,增加新的产品类和工厂类不需要修改已有代码。
  • 将产品对象的创建和使用分离,使得系统更加灵活。

缺点

  • 每增加一个产品类,就需要增加一个具体的工厂类,这可能会导致类的个数急剧增加。

工厂方法模式是实现对象创建的一种非常灵活的方式,它提供了一种封装机制,使得代码能够独立于具体类的创建。这种模式在实际开发中非常常见,特别是在需要根据不同情况创建不同实例时。

目标

为了方便用户对接框架,我们要实现扫描指定包路径下的数据库操作DAO接口和Mapper映射器的关联,并且提供一个工厂,用来对外统一提供SqlSession的数据库交互服务.

设计

为了用户方便对接框架, 我们想把整个工程包下关于数据库操作的 DAO 接口与 Mapper 映射器关联起来,就需要包装一个可以扫描包路径的完成映射的注册器类。

完善上一章节简化的 SqlSession,由 SqlSession 定义数据库处理接口和获取 Mapper 对象的操作,并把它交给映射器代理类进行使用。

有了 SqlSession 以后,你可以把它理解成一种功能服务,还需要这个功能服务提供一个工厂,来对外统一提供这类服务。比如我们在 Mybatis 中非常常见的操作,开启一个 SqlSession。

整体设计如图 :

  • 设计映射器注册机 MapperRegistry,自动扫描包下接口并把每个接口类映射的代理类全部存入映射器代理的 HashMap 缓存中。
  • SqlSession、SqlSessionFactory 是在此注册映射器代理的上次层使用标准定义和对外服务提供的封装,便于用户使用。
  • 我们把使用方当成用户 经过这样的封装就就可以更加方便我们后续在框架上功能的继续扩展了,后续工作中可以参考这样的设计,将业务功能领域服务包装,屏蔽功能内部细节,方便同事对接。

实现

工程代码

《手写Mybatis渐进式源码实践》实践笔记 (第三章 映射器的注册和使用)_第2张图片

类图

  • MapperRegistry 映射器注册机,提供包路径的扫描和映射器代理类注册机服务,完成接口对象的代理类注册处理。
  • SqlSession、DefaultSqlSession 用于定义执行 SQL 标准、获取映射器以及将来管理事务等方面的操作。基本我们平常使用 Mybatis 的 API 接口也都是从这个接口类定义的方法进行使用的。
  • SqlSessionFactory 是一个简单工厂模式,用于提供 SqlSession 服务,屏蔽创建细节,延迟创建过程。

实现步骤

1.映射器注册机
  • MapperRegistry 映射器注册类的核心主要在于提供了 ClassScanner.scanPackage 扫描包路径,调用 addMapper 方法,给接口类创建 MapperProxyFactory 映射器代理类,并写入到 knownMappers 的 HashMap 缓存中。
  • 提供getMapper 获取映射器代理类的方法,这个方法包装了我们上一章节手动操作实例化的过程,使用更加方便,在 DefaultSqlSession 中获取 Mapper 时调用。
public class MapperRegistry {


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


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

    /**
     * 扫描指定路径,注册映射器代理.
     */
    public <T> void addMapper(String packageName){
        Set<Class<?>> mapperSet = ClassScanner.scanPackage(packageName);
        for(Class<?> mapperClass : mapperSet){
            addMapper(mapperClass);
        }
    }

    /**
     * 注册映射器代理.
     */
    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            if (hasMapper(type)) {
                throw new RuntimeException("Type " + type + " is already known to the MapperRegistry.");
            }
            knownMappers.put(type, new MapperProxyFactory<>(type));
        }
    }

    /**
     * 判断是否已经注册.
     * @param type
     * @return
     * @param 
     */
    public <T> boolean hasMapper(Class<T> type){
        return knownMappers.containsKey(type);
    }

}

2. SqlSession 标准定义和实现

SqlSession

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

    /**
     * 根据给定的执行SQL获取一条记录的封装对象.
     * @param statement
     * @return
     * @param 
     */
    <T> T selectOne(String statement);


    /**
     * 根据给定的执行SQL获取一条记录的封装对象,带参数.
     * @param statement
     * @param parameter
     * @return
     * @param 
     */
    <T> T selectOne(String statement, Object parameter);

    /**
     * 获取映射器.
     * 这里使用了泛型,使得类型安全.
     * @param type
     * @param 
     * @return
     */
    <T> T getMapper(Class<T> type);
}

DefaultSqlSession

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

3.SqlSessionFactory 工厂定义和实现

SqlSessionFactory

  • 简单工厂的定义,在工厂中提供接口实现类的能力,也就是 SqlSessionFactory 工厂中提供的开启 SqlSession 的能力。
public interface SqlSessionFactory {

    /**
     * 打开一个会话.
     */
    SqlSession openSession();
}

DefaultSqlSessionFactory

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

    private final MapperRegistry mapperRegistry;

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

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

测试

事先准备

定义一个数据库接口 IUserDao

IUserDao

public interface IUserDao {

    String queryUserName(String uid);

    Integer queryUserAge(String uid);
}

测试用例

  • 在单元测试中,实现的步骤为:
    1. 注册Mapper
    2. 从SqlSessionFactory获取SqlSession
    3. 获取映射器对象
    4. 调用Dao方法
public class ApiTest {

    private Logger logger = LoggerFactory.getLogger(ApiTest.class);


    // 测试mapperProxyFactory
    @Test
    public void testMapperProxyFactory(){
        // 1.注册Mapper
        MapperRegistry registry = new MapperRegistry();
        registry.addMapper("cn.suwg.mybatis.test.dao");

        // 2.从SqlSessionFactory获取Session
        DefaultSqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(registry);
        SqlSession sqlSession = sqlSessionFactory.openSession();

         // 3.获取映射器对象
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        // 4.测试验证
        String result = userDao.queryUserName("10001");
        logger.info("测试结果:{}, 年龄:{}",result, 28);

    }


}

测试结果

image-20241210114707796

从输出的结果来看,我们实现的Mybatis手写 ORM 框架中,已经完成了代理类的注册和使用过程。

总结

  • 首先要从设计结构上了解工厂模式对具体功能结构的封装,屏蔽过程细节,限定上下文关系,把对外的使用减少耦合。
  • 可以参考本节使用 SqlSessionFactory 的工厂实现类包装了 SqlSession 的标准定义实现类,并由 SqlSession 完成对映射器对象的注册和使用。
  • 本章学习要扩展学习几个重要的知识点,包括:映射器、代理类、注册机、接口标准、工厂模式、上下文。这些工程开发的技巧都是在手写 Mybatis 的过程中非常重要的部分,了解和熟悉才能更好的在自己的业务中进行使用。

参考书籍:《手写Mybatis渐进式源码实践》

书籍源代码:https://github.com/fuzhengwei/book-small-mybatis

你可能感兴趣的:(手写mybatis,mybatis,笔记,java,数据库)