一、Mybatis源码调用过程
对于下面一段常用的代码,分析其调用过程
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
SqlSession sqlSession = factory.openSession(true);
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
System.out.println(studentMapper.getStudentById(1));
sqlSession.close();
- 1、根据mybatis-config.xml构建SqlSessionFactory接口实例,默认是DefaultSqlSessionFactory实现类
- 2、调用SqlSessionFactory.openSession方法创建SqlSession接口实例,默认是DefaultSqlSession实现类,该实例里面持有Executor对象
- 3、调用SqlSession.getMapper方法,获取用户定义的Mapper接口代理类,通过操作代理类的用户定义方法,来操作底层数据库,Mapper代理类使用JDK动态代理生成,其中的MapperProxy作为InvocationHandler角色出现
- 4、代理类中MapperProxy根据不同的SQL调用sqlSession不同的方法,比如selectOne,insert,update等等,也就是形如
sqlSession.selectOne(statementId, params); - 5、调用执行器Executor的相应方法,比如query、update等,Executor中处理一级缓存和二级缓存相关的逻辑,如果从缓存中查询到了数据,就不用再查数据库,也就是不用再执行下面的步骤
- 6、执行器创建StatementHandler实例,通过StatementHandler创建真正的JDBC标准的Statement对象
- 7、执行StatementHandler的query或update方法,底层执行的是JDBC标准的方法
- 8、使用ResultSetHandler处理结果集并返回
- 9、关闭sqlSession,归还connection连接
二、Mybatis一级缓存和二级缓存
缓存原理
无论是一级缓存还是二级缓存,底层都是map数据结构的支持,根据key来获取value,这个key在mybatis中是个复合值,用CacheKey对象表示,里面包含statementId、分页大小、分页offset、原生sql、查询参数五个条件,当且仅当这五个条件都命中时,才会匹配到对应的key。
注意:这里的statementId形如mapper.StudentMapper.getStudentById
一级缓存
mybatis默认开启的是一级缓存,一级缓存范围默认是SESSION,也就是说一级缓存只在同一个SqlSession中生效,要想关闭一级缓存,在mybatis-config.xml中设置localCacheScope为STATEMENT即可
一级缓存是sqlSession级别的,因为一级缓存保存在baseExecutor中,而baseExecutor生命周期同sqlSession
二级缓存
mybatis二级缓存的全局配置默认也是开启的,但是还需要在mapper映射文件中分别配置开启缓存才会最终生效,当然也可以在mybatis-config.xml文件中关闭二级缓存全局配置
二级缓存是全局的,因为每个statementId对应的MappedStatement是单例,每个MappedStatement对象都持有一个Cache,相同namespace下的MappedStatement共享同一个Cache,CachingExecutor获取到MappedStatement对象的Cache后,根据cacheKey获取缓存中的值。
三、Mybatis拦截器
Mybatis支持对Executor、StatementHandler、PameterHandler和ResultSetHandler 接口进行拦截,也就是说会对这4种对象进行代理,其核心原理是暴露扩展接口(plugin方法)给用户,对上述4种对象进行动态代理,因此,plugin方法返回的对象必须是代理对象或者原始对象,可用使用Plugin.wrap方法生成该代理对象,代理对象中会调用拦截器的intercept方法,可以通过@Intercepts注解来告知哪些方法需要被拦截。当然,你也可以使用自己的方式去创建代理对象,并通过自己的方式去判断方法是否需要被拦截。
比如下面这个拦截器是我自定义的,目的是对SQL语句SELECT id, name, age FROM student WHERE id = ? 进行分表,分表策略为id % 2
package interceptor;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import java.sql.Connection;
import java.util.Properties;
@Intercepts({
@Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class}
)
})
public class TableShardInterceptor implements Interceptor {
private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
private static final ReflectorFactory REFLECTOR_FACTORY = new DefaultReflectorFactory();
@Override
public Object intercept(Invocation invocation) throws Throwable {
if (invocation.getTarget() instanceof RoutingStatementHandler) {
try {
RoutingStatementHandler statementHandler = (RoutingStatementHandler) invocation.getTarget();
BoundSql boundSql = statementHandler.getBoundSql();
String originSql = boundSql.getSql();
Object parameter = statementHandler.getParameterHandler().getParameterObject();
int tIdx = Integer.parseInt(parameter.toString()) % 2;
String newSql = originSql.replace("student", "student_" + tIdx);
MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, REFLECTOR_FACTORY);
metaStatementHandler.setValue("delegate.boundSql.sql", newSql);
} catch (Exception e) {
e.printStackTrace();
}
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return (target instanceof RoutingStatementHandler) ? Plugin.wrap(target, this) : target;
}
@Override
public void setProperties(Properties properties) {
}
}
|