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

  • Post author:
  • Post category:其他


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.测试结果



版权声明:本文为dfBeautifulLive原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。