原理:
解析及初始化过程
根据mybatis配置文件(xml,mapper),通过XPath将xml转化为Resource,将Resource转化为Document。将Document转化为Configuration。这个过程包括了对 Mapper 接口、Mapper.xml文件的解析,将当前映射文件所对应的DAO接口的Class对象,也就是
Configuration的addMapper方法用于注册对应的dao层接口类及动态代理工厂,实际上Configuration类里面通过MapperRegistry对象维护了所有要生成动态代理类的XxxMapper接口信息,可见Configuration类确实是相当重要一个类
public class Configuration {
...
protected MapperRegistry mapperRegistry = new MapperRegistry(this);
...
public void addMapper(Class type) {
mapperRegistry.addMapper(type);
}
public T getMapper(Class type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
...
}
通过addMapper注册dao层需要代理的类及工厂MapperProxyFactory到MapperRegistry 中
public void addMapper(Class type) {
// 这个class必须是一个接口,因为是使用JDK动态代理,所以需要是接口,否则不会针对其生成动态代理
if (type.isInterface()) {
...
try {
// 生成一个MapperProxyFactory,用于之后生成动态代理类
knownMappers.put(type, new MapperProxyFactory<>(type));
//以下代码片段用于解析我们定义的XxxMapper接口里面使用的注解,这主要是处理不使用xml映射文件的情况
...
}
MapperRegistry中有一个map叫knownMappers,类型为Map
public class MapperRegistry {
private final Configuration config;
private final Map, MapperProxyFactory>> knownMappers = new HashMap, MapperProxyFactory>>();
}
动态代理及执行过程:
当MyBatis初始化及解析完毕后,configuration对象中存储了所有DAO接口的Class对象和相应的MapperProxyFactory对象,接下来,就到了使用DAO接口中函数的阶段了
当调用dao对应的api时会调用sqlsession的getMapper内部是调用configuration中getMapper方法,将dao接口做为被代理对象传入内部的MapperRegistry中,MapperRegistry又会调用getMapper的方法,getMapper内部是会获取knownMappers中对应的MapperProxyFactory,然后调用MapperProxyFactory的newIntance方法,这里是使用jdk动态代理的方式为 dao层的Mapper接口生成 MapperProxy。
MapperProxy调用invoke()方法获取MapperMethod类,MapperMethod类是整个代理机制的核心类,MapperMethod主要是对SqlSession中的操作进行了封装和使用。 该类里有两个内部类SqlCommand和MethodSignature。 SqlCommand用来封装CRUD操作,也就是我们在xml中配置的操作的节点,每个节点都会生成一个MappedStatement类,MapperMethod类的execute()内部使用switch case语句根据SqlCommand的getType()方法,判断要执行的sql类型,比如INSET、UPDATE、DELETE、SELECT和FLUSH,然后分别调用SqlSession的增删改查等方法。MethodSignature用来封装方法的参数以及返回类型。MapperMethod类的execute()内部通过判断SqlCommand的操作类型调用sqlsession相应方法完成和数据库的交互,根据MethodSignature解析的参数和返回类型传入sqlsession相应方法中作为对应的参数。
获取到MapperProxy类的代理对象后,mapper接口调用相应的方法时其实就是调用MapperMethod类的execute()方法,execute()调用已经传入相应参数sqlsession的相应方法来完成对数据库的操作。
总结整个逻辑是:SqlSession#getMapper() ——> Configuration#getMapper() ——> MapperRegistry#getMapper() ——> MapperProxyFactory#newInstance() ——> MapperProxy#invoke()——> MapperMethod#execute()
sqlSession.getMapper()调用了configuration.getMapper(),那我们再看一下configuration.getMapper():
public T getMapper(Class type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
configuration.getMapper()又调用了mapperRegistry.getMapper(),根据dao层的接口类从knownMappers中获取对应的MapperProxyFactory,然后调用newInstance创建代理:
public T getMapper(Class type, SqlSession sqlSession) {
//根据dao层的接口类从knownMappers中获取对应的MapperProxyFactory
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
...
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
MapperProxyFactory中newInstance方法,这里就是典型的动态代理了,被代理接口是mapperInterface ,代理对象是MapperProxy
protected T newInstance(MapperProxy mapperProxy) {
//这里使用JDK动态代理,通过Proxy.newProxyInstance生成动态代理类
// newProxyInstance的参数:类加载器、接口类、InvocationHandler接口实现类
// 动态代理可以将所有接口的调用重定向到调用处理器InvocationHandler,调用它的invoke方法
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
InvocationHandler接口的实现类是MapperProxy,最后是调用了mapperMethod.execute(sqlSession, args),其源码如下:
public class MapperProxy implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
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;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//如果调用的是Object类中定义的方法,直接通过反射调用即可
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//调用XxxMapper接口自定义的方法,进行代理
//首先将当前被调用的方法Method构造成一个MapperMethod对象,然后掉用其execute方法真正的开始执行。
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
...
}
核心类是MapperMethod,是返回的代理对象最终要执行的是MapperMethod的execute方法
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
//insert语句的处理逻辑
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
//update语句的处理逻辑
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
//delete语句的处理逻辑
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
//select语句的处理逻辑
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
//调用sqlSession的selectOne方法
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
...
}
参考:https://www.cnblogs.com/hopeofthevillage/p/11384848.html,https://segmentfault.com/a/1190000019130222,https://www.cnblogs.com/qingchen521/p/10327440.html
- 用户程序调用 mybatis 中 API 的接口后,sqlsession 根据 statement ID 找到 mapperstatement。通过 exactuator 执行器对 mapperstatement 进行解析,包括动态 sql 拼接,参数转换,结果映射等,获得 jdbcstatement。jdbc 执行 jdbcstatement
- 最后根据 mapperstatement 中的结果映射对返回结果进行转换为对应的 map 或者 JAVAbean。
一级缓存和二级缓存
- Mybatis 一级缓存作用域是 session,session commit 之后缓存就失效了。
- Mybatis 二级缓存作用域是 sessionfactory,该缓存是以 namespace 为单位的(也就是一个 Mapper.xml 文件),不同 namespace 下的操作互不影响。
- 所有对数据表的改变操作都会刷新缓存。但是一般不要用二级缓存,例如在 UserMapper.xml 中有大多数针对 user 表的操作。但是在另一个 XXXMapper.xml 中,还有针对 user 单表的操作。这会导致 user 在两个命名空间下的数据不一致。
- 如果在 UserMapper.xml 中做了刷新缓存的操作,在 XXXMapper.xml 中缓存仍然有效,如果有针对 user 的单表查询,使用缓存的结果可能会不正确,读到脏数据。