Mybatis使用最多的就是代理模式,要学会Mybatis必须会代理模式的使用,所以需要先了解java中的动态代理,也可以看我之前的博文,传送门如下:
本章节学习来源是小傅哥博客,传送门: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.测试结果