手敲Mybatis(一)-实现Mapper代理注册和使用

Mybatis使用最多的就是代理模式,要学会Mybatis必须会代理模式的使用,所以需要先了解java中的动态代理,也可以看我之前的博文,传送门如下:

理解代理模式MyBatis就掌握了一半(详解动态代理原理)

本章节学习来源是小傅哥博客,传送门:bugstack 虫洞栈,通过此博客学习Mybatis来学习它的思路,经历手敲、推敲、琢磨、来形成自己思路语言总结。

此章节主要是处理我们对于Dao接口,我们没有实现Dao却能够使用此接口反馈实现结果,原因是Mybatis的代理模式,所以我们要实现对于Mybatis的代理对象工厂和代理注册器的使用,以及模拟打开会话操作,与SqlSession中代理器关系的处理使用。

1.xml类图关系

1. MapperRegistry

Mapper的注册器,3个功能,第一个是将Mapper代理工厂注册到map容器中,第二个是获取Mapper代理工厂,第三个是判断是否map容器中存储了此Mapper代理工厂数据

2.MapperProxyFactory

Mapper代理类的工厂,主要是处理生成代理类功能

3.MapperProxy

Mapper代理类对象调用目标方法类

4.SqlSessionFactory

SqlSession工厂类接口,主要是开启SqlSession会话(获取SqlSession),由DefaultSqlSessionFactory实例化SqlSession

5.SqlSession

SqlSession接口主要是用来定义基本Sql处理的地方,由DefaultSqlSession来作为其的实现类,此处就暂时模拟方法实现反馈数据

之所以这么设计,个人理解,方便用户处理,这样全处理生命周期归封装去管理,所谓知道的越多越好办事情,这样设计后期也很好维护,该存储容器的存储容器里,各个类处理自身的职责和职能

2.代码实现

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.bugstack.mybatis</groupId>
    <artifactId>mybatis-step-02</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.75</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.dom4j/dom4j -->
        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.3</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.7</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.5.0</version>
        </dependency>
        <!-- LOGGING begin -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.5</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>1.7.5</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.0.9</version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-api</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- LOGGING end -->
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <compilerVersion>1.8</compilerVersion>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

 df.middleware.mybatis.binding.MapperProxy:此处是动态代理使用,当用户调用dao里定义的方法时,就会触发invoke方法,去调用SqlSession的实现方法

public class MapperProxy<T> implements InvocationHandler, Serializable {

    private static final long serialVersionUID = -6424540398559729838L;

    private SqlSession sqlSession;
    private final Class<T> mapperInterface;

    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // method.getDeclaringClass()获取class类对象名词
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else {
            return sqlSession.selectOne(method.getName(), args);
        }
    }

}

df.middleware.mybatis.binding.MapperProxyFactory:此类主要处理生成代理对象

public class MapperProxyFactory<T> {

    // 原对象类
    private final Class<T> mapperInterface;

    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

        // 第一个参数:用哪个类加载器去加载代理对象
        // 第二个参数:动态代理类需要实现的接口
        // 第三个参数:动态代理方法在执行时,会调用第三个参数里面的invoke方法去执行
        // 方法返回的对象
        // Proxy.newProxyInstance代理的是接口
    @SuppressWarnings("unchecked")
    public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface);
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
    }

}

df.middleware.mybatis.binding.MapperRegistry:一个容器存储定义,getMapper,addMapper、hasMapper都比较简单一看就明白。

public class MapperRegistry {

    /**
     * 存储代理器工厂容器
     */
    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 <T> boolean hasMapper(Class<T> type) {
        return knownMappers.containsKey(type);
    }

    public void addMappers(String packageName) {
        // 生成class对象,包下有几个类对象生成几个类对象
        Set<Class<?>> mapperSet = ClassScanner.scanPackage(packageName);
        for (Class<?> mapperClass : mapperSet) {
            addMapper(mapperClass);
        }
    }

}
df.middleware.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);

}

 df.middleware.mybatis.session,default.DefaultSqlSession实现SqlSession,最终调用invoke对象会调入到此方法中

public class DefaultSqlSession implements SqlSession {

    /**
     * 映射器注册机
     */
    private MapperRegistry mapperRegistry;

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

    @Override
    public <T> T selectOne(String statement) {
        return (T) ("你被代理了!" + statement);
    }

    @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);
    }

}

 df.middleware.mybatis.session.SqlSessionFactory工厂类,定义openSession方法

public interface SqlSessionFactory {

    /**
     * 打开一个 session
     * @return SqlSession
     */
   SqlSession openSession();

}

 df.middleware.mybatis.session.default.DefaultSqlSessionFactory实现类,实现openSession,此处就是实例化DefaultSqlSession

3.测试准备

df.middleware.mybatis.test.dao.IUserDao

public interface IUserDao {

    String queryUserName(String uId);

    Integer queryUserAge(String uId);

}

df.middleware.mybatis.test.dao.ISchoolDao

public interface ISchoolDao {

    String querySchoolName(String uId);

}

2.单元测试测试类

public class ApiTest {

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

    @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);
        ISchoolDao schoolDao = sqlSession.getMapper(ISchoolDao.class);

        // 4. 测试验证
        String res = userDao.queryUserName("10001");

        // 4. 测试验证
        String school = schoolDao.querySchoolName("10001");


        logger.info("测试结果:{}", res);
        logger.info("school测试结果:{}", school);
    }

}

2.测试结果

创建和获取 MyBatis-Plus 的 Mapper 对象,可以通过以下两种方式进行: 1. 使用 Spring Boot 自动装配 如果您的项目使用了 Spring Boot,可以直接使用 Spring Boot 提供的自动装配机制来创建和获取 MyBatis-Plus 的 Mapper 对象。具体来说,您需要完成以下两个步骤: 1. 在 `application.properties` 或 `application.yml` 中配置 MyBatis-Plus 的相关参数,包括数据源配置、Mapper 扫描路径等。例如: ```yaml # 数据源配置 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase spring.datasource.username=root spring.datasource.password=123456 # MyBatis-Plus 配置 mybatis-plus.mapper-locations=classpath:mapper/**/*.xml mybatis-plus.type-aliases-package=com.example.entity ``` 2. 在需要使用 Mapper 对象的地方,通过 `@Autowired` 注解注入对应的 Mapper 对象即可。例如: ```java @RestController public class MyController { @Autowired private MyMapper myMapper; @GetMapping("/getVariable") public String getVariable() { return myMapper.getVariable(); } } ``` 在上述代码中,我们使用 `@Autowired` 注解将 `MyMapper` 对象注入到了 `MyController` 控制器中,并在 `getVariable` 方法中使用该对象查询数据库并返回结果。 2. 动创建 Mapper 对象 如果您的项目没有使用 Spring Boot,或者需要动创建 Mapper 对象,可以通过以下步骤进行: 1. 在 `pom.xml` 中引入 MyBatis-Plus 的依赖: ```xml <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis-plus.version}</version> </dependency> ``` 2. 在需要使用 Mapper 对象的地方,通过 `SqlSessionFactory` 和 `MapperRegistry` 对象动创建 Mapper 对象。例如: ```java public class MyBatisPlusUtils { // 获取 SqlSessionFactory 对象 private static SqlSessionFactory getSqlSessionFactory() { // 创建数据源对象 DataSource dataSource = new PooledDataSource( "com.mysql.jdbc.Driver", "jdbc:mysql://localhost:3306/mydatabase", "root", "123456" ); // 创建 MyBatis 配置对象 MybatisConfiguration configuration = new MybatisConfiguration(); configuration.setMapUnderscoreToCamelCase(true); // 创建 SqlSessionFactory 对象 SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder() .build(configuration, dataSource); return sessionFactory; } // 获取 Mapper 对象 public static <T> T getMapper(Class<T> mapperClass) { // 创建 SqlSessionFactory 对象 SqlSessionFactory sessionFactory = getSqlSessionFactory(); // 创建 MapperRegistry 对象 MapperRegistry mapperRegistry = new MapperRegistry(sessionFactory.getConfiguration()); // 注册 Mapper 对象 mapperRegistry.addMapper(mapperClass); // 获取 Mapper 对象 return sessionFactory.openSession().getMapper(mapperClass); } } ``` 在上述代码中,我们定义了一个 `getSqlSessionFactory` 方法,用于创建 `SqlSessionFactory` 对象;并定义了一个 `getMapper` 方法,用于动创建 Mapper 对象。在 `getMapper` 方法中,我们首先通过 `getSqlSessionFactory` 方法获取到了 `SqlSessionFactory` 对象,然后创建了一个 `MapperRegistry` 对象,并将需要创建的 Mapper 对象注册到了该对象中;最后通过 `SqlSessionFactory` 对象的 `openSession` 方法获取到了一个 `SqlSession` 对象,并调用该对象的 `getMapper` 方法获取到了需要的 Mapper 对象。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值