大学后再也没用过mybatis,最近看到动态代理了,顺带了解一下mapper的原理。
不清楚代理模式的,可以先看下这篇:
代理模式
对配置熟悉的可直接跳到第二步
1.测试代码
(1)mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- environment 元素体中包含了事务管理和连接池的配置 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mytest?serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="12345"/>
</dataSource>
</environment>
</environments>
<!-- mappers 元素则包含了一组映射器(mapper),这些映射器的 XML 映射文件包含了 SQL 代码和映射定义信息。 -->
<mappers>
<mapper resource="mapper/TMapper.xml"/>
</mappers>
</configuration>
(2)TMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="framework.mybatis1.mapper.TMapper">
<select id="selectT" parameterType="int" resultType="framework.mybatis1.domain.T">
select * from t where id = #{id}
</select>
</mapper>
(3)T.java
package framework.mybatis1.domain;
public class T {
private int id;
private int a;
private int b;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
public int getB() {
return b;
}
public void setB(int b) {
this.b = b;
}
@Override
public String toString() {
return "T{" +
"id=" + id +
", a=" + a +
", b=" + b +
'}';
}
}
(4) TMapper.java
package framework.mybatis1.mapper;
public interface TMapper {
T selectT(int id);
}
(5)测试
package framework.mybatis1;
public class MyBatisTest {
public static void main(String[] args) throws IOException {
//mybatis的配置文件
String resource = "config/mybatis-config";
//使用MyBatis提供的Resources类加载mybatis的配置文件
Reader reader = Resources.getResourceAsReader(resource);
//构建sqlSession的工厂
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sessionFactory.openSession();
TMapper mapper = session.getMapper(TMapper.class);//获取自定义的 Mapper 接口的代理对象
T user = mapper.selectT(1);
System.out.println(user);
}
}
2.原理分析
主要是下面几步:
(1)构建 SqlSessionFactory
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
它做了什么呢?
我们知道它传入的参数reader就是mybatis-config.xml
配置文件的字符流,
mybatis-config.xml
最终会被解析放进Configuration实例(也会解析TMapper.xml
):
public class Configuration {
Environment environment;//数据库连接配置
MapperRegistry mapperRegistry;//mapperRegistry.knownMappers<namespace,MapperProxyFactory>
Map<String, MappedStatement> mappedStatements;//<namespace.id,MappedStatement>
...//为了好理解,省去其他代码
}
(2)从 SqlSessionFactory 中获取 SqlSession
SqlSession session = sessionFactory.openSession();
从Configuration 类中拿到Environment封装的数据源连接数据库
(3)获取mapper接口的代理对象
TMapper mapper = session.getMapper(TMapper.class);
里面调用的其实是Configuration的getMapper(..)
方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
//调用MapperProxyFactory创建代理
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
MapperProxyFactory在第一步已经放进配置中了,它是生成代理类的工厂,来看下具体做了什么:
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
protected T newInstance(MapperProxy<T> mapperProxy) {//生成代理对象
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
}
重点在:Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
可以看到这个生成的Proxy实现了mapper接口(对应上面例子就是实现了TMapper.java
接口)
来看下MapperProxy:
public class MapperProxy<T> implements InvocationHandler, Serializable {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//执行mapper.java中的方法继承自Object的方法,比如toString(),equals()
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
if (this.isDefaultMethod(method)) {
return this.invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
MapperMethod mapperMethod = this.cachedMapperMethod(method);
//最终调用了这里,MapperMethod 会交给sqlSession去执行并返回结果
return mapperMethod.execute(this.sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
this.methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
MapperProxy实现了InvocationHandler接口,我们知道,java动态代理调用方法后实际调用的是InvocationHandler的invoke方法,MapperProxy.invoke()中最终起作用的是mapperMethod.execute(this.sqlSession, args)
:
public Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
switch(this.command.getType()) {
case INSERT:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}
可以看到实际是调用sqlSession来执行具体的sql命令。command的类型前面解析TMapper.xml时,通过元素<select/>
已经确认了是SELECT类型的(同样是被封装进MappedStatement了)。
参考:
[1]mybatis – MyBatis 3 | 入门