大学后再也没用过mybatis,最近看到动态代理了,顺带了解一下mapper的原理。
不清楚代理模式的,可以先看下这篇:
代理模式
对配置熟悉的可直接跳到第二步
1.测试代码
(1)mybatis-config.xml
(2)TMapper.xml
(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
Map mappedStatements;//
...//为了好理解,省去其他代码
}
(2)从 SqlSessionFactory 中获取 SqlSession
SqlSession session = sessionFactory.openSession();
从Configuration 类中拿到Environment封装的数据源连接数据库
(3)获取mapper接口的代理对象
TMapper mapper = session.getMapper(TMapper.class);
里面调用的其实是Configuration的getMapper(..)
方法:
public T getMapper(Class type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
public T getMapper(Class type, SqlSession sqlSession) {
MapperProxyFactory 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 {
private final Class mapperInterface;
private final Map methodCache = new ConcurrentHashMap();
public MapperProxyFactory(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
protected T newInstance(MapperProxy mapperProxy) {//生成代理对象
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
MapperProxy 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 implements InvocationHandler, Serializable {
private final SqlSession sqlSession;
private final Class mapperInterface;
private final Map methodCache;
public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map 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类型的(同样是被封装进MappedStatement了)。
参考:
[1]mybatis – MyBatis 3 | 入门