mybatis源码解读(3.4.3)
- Last Published: 09 March 2020
- Version: 3.5.4
简约步骤截图及理解
SqlSessionFactory获取
官网是这么说的 : 一旦被创建,SqlSessionFactory 应该在你的应用执行期间都存在。没有理由来处理或重 新创建它。
使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次。 这样的 操作将被视为是
非常糟糕的。 因此 SqlSessionFactory 的最佳范围是应用范围。 有很多方法可 以做到, 最简单的
就是使用单例模式或者静态单例模式。
SqlSessionFactory与SqlSession的获取:
SqlSession获取
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不能被共享,也是线程 不安全的。
因此最佳的范围是请求或方法范围。绝对不能将 SqlSession 实例的引用放在一个 类的静态字段甚至
是实例字段中。 也绝不能将 SqlSession 实例的引用放在任何类型的管理范 围中, 比如 Serlvet
架构中的 HttpSession。 如果你现在正用任意的 Web 框架, 要考虑 SqlSession 放在一个和 HTTP
扫描二维码关注公众号,回复: 10961564 查看本文章请求对象相似的范围内。换句话说,基于收到的 HTTP 请求,你可以打开 了一个 SqlSession,然后返回
响应,就可以关闭它了。关闭 Session 很重要,你应该确保使 用 finally 块来关闭它。下面的示例就
是一个确保 SqlSession 关闭的基本模式:
SqlSession session = sqlSessionFactory.openSession();
try {
// do work
} finally {
session.close();
}
在你的代码中一贯地使用这种模式, 将会保证所有数据库资源都正确地关闭 (假设你没 有通过你自己的
连接关闭,这会给 MyBatis 造成一种迹象表明你要自己管理连接资源) 。
动态Dao的获取
sql的执行
我们知道Mapper,通过MapperProxy代理类执行他的接口方法,当mapper方法被调用的时候对应的MapperProxy会生成相应的MapperMethod并且会缓存起来,这样当多次调用同一个mapper方法时候只会生成一个MapperMethod,提高了时间和内存效率;
MapperProxy:
//这里会拦截Mapper接口的所有方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) { //如果是Object中定义的方法,直接执行。如toString(),hashCode()等
try {
return method.invoke(this, args);//
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method); //其他Mapper接口定义的方法交由mapperMethod来执行
return mapperMethod.execute(sqlSession, args);
}
最后2句关键,我们执行所调用Mapper的每一个接口方法,最后返回的是MapperMethod.execute方法。每一个MapperMethod对应了一个mapper文件中配置的一个sql语句或FLUSH配置,对应的sql语句通过mapper对应的class文件名+方法名从Configuration对象中获得。
紧接着:
/**
* MapperMethod代理Mapper所有方法
*/
public class MapperMethod {
//一个内部封 封装了SQL标签的类型 insert update delete select
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, method);
}
/**
* 这个方法是对SqlSession的包装,对应insert、delete、update、select四种操作
*/
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;//返回结果
//INSERT操作
if (SqlCommandType.INSERT == command.getType()) {
//处理参数
Object param = method.convertArgsToSqlCommandParam(args);
//调用sqlSession的insert方法
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
//UPDATE操作 同上
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
//DELETE操作 同上
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
//如果返回void 并且参数有resultHandler ,则调用 void select(String statement, Object parameter, ResultHandler handler);方法
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
//如果返回多行结果,executeForMany这个方法调用 <E> List<E> selectList(String statement, Object parameter);
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
//如果返回类型是MAP 则调用executeForMap方法
result = executeForMap(sqlSession, args);
} else {
//否则就是查询单个对象
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
//接口方法没有和sql命令绑定
throw new BindingException("Unknown execution method for: " + command.getName());
}
//如果返回值为空 并且方法返回值类型是基础类型 并且不是VOID 则抛出异常
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;
}
private Object rowCountResult(int rowCount) {
final Object result;
if (method.returnsVoid()) {
result = null;
} else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
result = rowCount;
} else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
result = (long) rowCount;
} else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
result = (rowCount > 0);
} else {
throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
}
return result;
}
private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
if (void.class.equals(ms.getResultMaps().get(0).getType())) {
throw new BindingException("method " + command.getName()
+ " needs either a @ResultMap annotation, a @ResultType annotation,"
+ " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
}
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
} else {
sqlSession.select(command.getName(), param, method.extractResultHandler(args));
}
}
//返回多行结果 调用sqlSession.selectList方法
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
//如果参数含有rowBounds则调用分页的查询
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
//没有分页则调用普通查询
result = sqlSession.<E>selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
Object collection = config.getObjectFactory().create(method.getReturnType());
MetaObject metaObject = config.newMetaObject(collection);
metaObject.addAll(list);
return collection;
}
@SuppressWarnings("unchecked")
private <E> E[] convertToArray(List<E> list) {
E[] array = (E[]) Array.newInstance(method.getReturnType().getComponentType(), list.size());
array = list.toArray(array);
return array;
}
private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
Map<K, V> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds);
} else {
result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey());
}
return result;
}
public static class ParamMap<V> extends HashMap<String, V> {
private static final long serialVersionUID = -2212268410512043556L;
@Override
public V get(Object key) {
if (!super.containsKey(key)) {
throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet());
}
return super.get(key);
}
}
//封装了具体执行的动作
public static class SqlCommand {
//xml标签的id
private final String name;
//insert update delete select的具体类型
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) throws BindingException {
//拿到全名 比如 org.mybatis.example.UserMapper.selectByPrimaryKey
String statementName = mapperInterface.getName() + "." + method.getName();
//MappedStatement对象,封装一个Mapper接口对应的sql操作
MappedStatement ms = null;
if (configuration.hasStatement(statementName)) {
//从Configuration对象查找是否有这个方法的全限定名称,如果有则根据方法的全限定名称获取MappedStatement
ms = configuration.getMappedStatement(statementName);
} else if (!mapperInterface.equals(method.getDeclaringClass().getName())) { // issue #35
//如果没有在Configuration对象中找到这个方法,则向上父类中获取全限定方法名
String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
if (configuration.hasStatement(parentStatementName)) {
ms = configuration.getMappedStatement(parentStatementName);
}
}
if (ms == null) {
throw new BindingException("Invalid bound statement (not found): " + statementName);
}
//这个ms.getId,其实就是我们在mapper.xml配置文件中配置一条sql语句设置的id属性的值
name = ms.getId();
//sql的类型(insert、update、delete、select)
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
//判断SQL标签类型 未知就抛异常
throw new BindingException("Unknown execution method for: " + name);
}
}
public String getName() {
return name;
}
public SqlCommandType getType() {
return type;
}
}
/**
* 方法签名,封装了接口当中方法的 参数类型 返回值类型 等信息
*/
public static class MethodSignature {
private final boolean returnsMany;//是否返回多条结果
private final boolean returnsMap; //返回值是否是MAP
private final boolean returnsVoid;//返回值是否是VOID
private final Class<?> returnType; //返回值类型
private final String mapKey;
private final Integer resultHandlerIndex;//resultHandler类型参数的位置
private final Integer rowBoundsIndex; //rowBound类型参数的位置
private final SortedMap<Integer, String> params;//用来存放参数信息
private final boolean hasNamedParameters; //是否存在命名参数
public MethodSignature(Configuration configuration, Method method) throws BindingException {
this.returnType = method.getReturnType();
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
this.mapKey = getMapKey(method);
this.returnsMap = (this.mapKey != null);
this.hasNamedParameters = hasNamedParams(method);
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));
}
/**
* 创建SqlSession对象需要传递的参数逻辑
* args是用户mapper所传递的方法参数列表, 如果方法没有参数,则返回null.
* 如果方法只包含一个参数并且不包含命名参数, 则返回传递的参数值。
* 如果包含多个参数或包含命名参数,则返回包含名字和对应值的map对象、
*/
public Object convertArgsToSqlCommandParam(Object[] args) {
final int paramCount = params.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasNamedParameters && paramCount == 1) {
return args[params.keySet().iterator().next()];
} else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : params.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// issue #71, add param names as param1, param2...but ensure backward compatibility
final String genericParamName = "param" + String.valueOf(i + 1);
if (!param.containsKey(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
------
MapperRegistry
MapperRegistry的功能就是注册和获取Mapper对象的代理。注册Mapper和获取Mapper都是在一个Map对象中存取Mapper的代理对象MapperProxyFactory。
SqlSession:
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
添加和获取Mapper实例都使用到了同一个类MapperRegistry,在Configuration中的声明如下:
protected MapperRegistry mapperRegistry = new MapperRegistry(this);
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
如何看出用的是同一个?可以debug看,也可以从SqlSessionFactory看起:
DefaultSqlSessionFactory:
SqlSessionFactory是创建SqlSession的工厂,但是创建过程中需要反复加载全局配置文件,这一点是十分耗时的,为了优化项目,最好通过单例模式来管理它,使它只能创建一个对象,配置文件加载一次就可以了。
private final Configuration configuration;
...
...
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
//这里返回的SqlSesion是 DefaultSqlSessionFactory的私有对象,而整个应用最好DefaultSqlSessionFactory是单例的
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
而再configuration类里面,MapperRegistry是只创建一次的,所以可见,全局的MapperRegistry是公用一份的;
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
public MapperRegistry(Configuration config) {
this.config = config;
}
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
public <T> boolean hasMapper(Class<T> type) {
return knownMappers.containsKey(type);
}
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
/**
* @since 3.2.2
*/
public Collection<Class<?>> getMappers() {
return Collections.unmodifiableCollection(knownMappers.keySet());
}
/**
* @since 3.2.2
*/
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
/**
* @since 3.2.2
*/
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
}
MapperProxy MapperProxyFactory
现来看DefaultSqlSession的getMapper方法,由上面的分析我们可以知道,最后是通过MapperRegistry对象获得Mapper实例:
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//试图从MapperProxy得出 Mapper的代理工厂
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
来看看,knownMappers是什么东西:
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
是一个key 为 字节码 ,即对应参数 例如DeptDao.class的二进制对象,value 则为MapperProxyFactory
所以,接下来得要看这个getmapper 对应的addmapper方法:
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
而mybatis在解析完xml后,绑定命名空间bindMapperForNamespace()方法(类: bindMapperForNamespace)
其实从以上就可以看出,一个xxxDao对应一个MapperProxyFactory;
//绑定到命名空间
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
//调用的归根结底还是MapperRegisty 的addMapper方法
configuration.addMapper(boundType);
}
}
}
}
我们在回过头来看getMapper是如何获得Mapper对象的:
1.先根据mapper.class类型来获取MapperProxyFactory
2.再调用MapperProxyFactory对象的newInstance方法获得Mapper。
我们看MapperProxyFactory类代码:
public T newInstance(SqlSession sqlSession) {
//创建一个Mapperxy对象,这个方法实现了JDK动态代理中的InvocationHandler接口
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
//mapperInterface,说明Mapper接口被代理了,这样子返回的对象就是Mapper接口的子类,方法被调用时会被mapperProxy拦截,也就是执行mapperProxy.invoke()方法
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
有上可见,最后返回的其实是一个MapperProxy对象,就是mybatis自己实现的JDK动态代理的一个代理类
所以
List<Dept> deptList = deptDao.findAll();
以上代码可以直接到MapperProxy对象里面去看,调用的就是MapperProxy.invoke()方法;
再来看看MapperProxy,
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
//Mapper接口
private final Class<T> mapperInterface;
/*
* Mapper接口中的每个方法都会生成一个MapperMethod对象, methodCache维护着他们的对应关系
*/
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;
}
//这里会拦截Mapper接口的所有方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) { //如果是Object中定义的方法,直接执行。如toString(),hashCode()等
try {
return method.invoke(this, args);//
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method); //其他Mapper接口定义的方法交由mapperMethod来执行
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
MapperMethod (内部命令模式)
现在一条龙下来了吧,就要来看看MapperMethod 对应的是什么层次的东西了.由上我们看见invoke方法返回的是一个mapperMethod.execute(sqlSession, args)方法
//这里会拦截Mapper接口的所有方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) { //如果是Object中定义的方法,直接执行。如toString(),hashCode()等
try {
return method.invoke(this, args);//
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
//这里查询以下缓存
final MapperMethod mapperMethod = cachedMapperMethod(method); //其他Mapper接口定义的方法交由mapperMethod来执行
return mapperMethod.execute(sqlSession, args);
}
最后2句关键,我们执行所调用Mapper的每一个接口方法,最后返回的是MapperMethod.execute方法。每一个MapperMethod对应了一个mapper文件中配置的一个sql语句或FLUSH配置,对应的sql语句通过mapper对应的class文件名+方法名从Configuration对象中获得。
而当执行MapperMethod的execute方法的时候,根据当前MapperMethod对应的mapper配置会执行Session的insert, update, delete, select, selectList, selectMap, selectCursor, selectOne或flushStatements方法。
这里只截取一部分的MapperMethod,这就是一个典型的命令模式了;
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
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);
result = sqlSession.selectOne(command.getName(), param);
}
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;
}
最后在这个里面的XXXexecuteFor方法调用又要回到SqlSession了
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
//这里查看是否有分页参数
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
//最后gogogo,sqlSession 参数
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.<E>selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
再来看看:DefaultSqlSession类里面的selectList方法
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
//真正执行方法的在 executor.query里面 但是我门得来看看 configuration是从那里来的
List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
return result;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
DefaultSqlSession类的成员executor是在构造函数里面给他赋值的。所以我们又要回头去查看一下是在什么时候实例化了DefaultSqlSession类。
DefaultSqlSession在SqlSessionFactory的实现类DefaultSqlSessionFactory中被创建:以下是DefaultSqlSessionFactory的部分代码
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//在这里哦 调用的是configuration的 newExecutor
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
try {
boolean autoCommit;
try {
autoCommit = connection.getAutoCommit();
} catch (SQLException e) {
// Failover to true, as most poor drivers
// or databases won't support transactions
autoCommit = true;
}
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
final Transaction = transactionFactory.newTransaction(connection);
//这里哦 ,configuration.newExecutor
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
然后再来看看Configuration的newExecutor方法:
Configuration的构造过程
这里还是得说一下:Configuration实例来在与读xml文件 算了,我截取出来:
SqlSessionFactoryBuilder.build
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//构造出一个解析 xml的Builder类
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// build xmlConfigBuilder的parse方法
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
接下来看XMLConfigBuilder:
//先到这个
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {//然后适配器以下,委托给XPathParser 去解析xml文件
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
//看
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
//Configuration 就是这个时候new出来的
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
....
.....看super是啥
是 abstract BaseBuilder
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
好new出来一个XMLConfigBuilder之后呢? 调用它的parse()
public Configuration parse() {
//查看是否加载过,以免重复加载
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//让后解析,封装xml里的数据到configuration里面 封装之后的就不看了 不是很重要了 /configuration 这个parser 是XPathParser
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
回到 Configuration的newExecutor:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//如果是空就调用defaultExecutorType protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
//判断以下下 executorType 创建相应的Executor
if (ExecutorType.BATCH == executorType) {
//批处理
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
//
executor = new ReuseExecutor(this, transaction);
} else {
//
executor = new SimpleExecutor(this, transaction);
}
//缓存哦,缓存是否开启 protected boolean cacheEnabled = true; 默认开启
//所以不管上面是个啥 都回开启这个 executor 然后把这个封装到CachingExecutor
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
//放入执行链里面
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
接下来就要看下面的 Executor类了
Executor
每一个SqlSession对象都被分配一个Executor,主要负责connection获取和statement对象管理方案。
Mybatis对外统一提供了一个操作接口类Executor,提供的接口方法有update、query、flushStatements、commit、rollback等接口函数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O2mUX78M-1586691066721)(…/%E6%BA%90%E7%A0%81/assets/1584509043528.png)]
简单执行器SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。(可以是Statement或PrepareStatement对象)
重用执行器ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。(可以是Statement或PrepareStatement对象)
批量执行器BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理的;BatchExecutor相当于维护了多个桶,每个桶里都装了很多属于自己的SQL,就像苹果蓝里装了很多苹果,番茄蓝里装了很多番茄,最后,再统一倒进仓库。(可以是Statement或PrepareStatement对象)
缓存执行器CachingExecutor:装饰设计模式典范,先从缓存中获取查询结果,存在就返回,不存在,再委托给Executor delegate去数据库取,delegate可以是上面任一的SimpleExecutor、ReuseExecutor、BatchExecutor。
无用执行器ClosedExecutor:毫无用处,读者可自行查看其源码,仅作为一种标识,和Serializable标记接口作用相当。
作用范围:以上这五个执行器的作用范围,都严格限制在SqlSession生命周期范围内。
BaseExecutor 是一个抽象类,其只实现了一些公共的封装,而把真正的核心实现都通过方法抽象出来给子类实现,如doUpdate(),doQuery();CachingExecutor 只是在Executor的基础上加入了缓存的功能,底层还是通过执行程序调用的,所以真正有作用的执行器只有SimpleExecutor ,ReuseExecutor 和批处理。它们都是自己实现的执行器核心功能,没有借助任何其它的执行程序实现,它们是实现不同也就注定了它们的功能也是不一样的。执行人是跟一个SqlSession 绑定在一起的,每一个SqlSession的都拥有一个新的执行官对象,由配置创建。
BaseExecutor源码,很重要哦
好像就是做了一层结果缓存的查询,先查询缓存
1、先查本地缓存,没有再去查数据库,注意这里的本地缓存是同一个session内的缓存,也就是同一个opensession内。
2、通过configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT来看,可以设置参数,将本地缓存去掉,不使用本地缓存。
清除本地缓存,做更新操作,具体更新操作在具体执行器中。
public abstract class BaseExecutor implements Executor {
private static final Log log = LogFactory.getLog(BaseExecutor.class);
//事务
protected Transaction transaction;
//执行器的包装对象
protected Executor wrapper;
//线程安全队列
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
//本地缓存
protected PerpetualCache localCache;
protected PerpetualCache localOutputParameterCache;
//mybatis的配置信息
protected Configuration configuration;
//查询堆栈
protected int queryStack = 0;
private boolean closed;
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
public Transaction getTransaction() {
if (closed) throw new ExecutorException("Executor was closed.");
return transaction;
}
public void close(boolean forceRollback) {
try {
try {
rollback(forceRollback);
} finally {
if (transaction != null) transaction.close();
}
} catch (SQLException e) {
// Ignore. There's nothing that can be done at this point.
log.warn("Unexpected exception on closing transaction. Cause: " + e);
} finally {
transaction = null;
deferredLoads = null;
localCache = null;
localOutputParameterCache = null;
closed = true;
}
}
public boolean isClosed() {
return closed;
}
//SqlSession的update/insert/delete会调用此方法
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) throw new ExecutorException("Executor was closed.");
//先清局部缓存,再更新,如何更新由子类实现,模板方法模式
clearLocalCache();
return doUpdate(ms, parameter);
}
public List<BatchResult> flushStatements() throws SQLException {
return flushStatements(false);
}
public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
if (closed) throw new ExecutorException("Executor was closed.");
return doFlushStatements(isRollBack);
}
//SqlSession.selectList会调用此方法
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);//得到绑定sql
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);//创建缓存key
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);//查询
}
@SuppressWarnings("unchecked")
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) throw new ExecutorException("Executor was closed.");
//先清局部缓存,再查询,但仅仅查询堆栈为0才清,为了处理递归调用
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;//加一,这样递归调用到上面的时候就不会再清局部缓存了
//根据cachekey从localCache去查
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
//如果查到localCache缓存,处理localOutputParameterCache
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//从数据库查
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
//延迟加载队列中所有元素
deferredLoad.load();
}
//清空延迟加载队列
deferredLoads.clear(); // issue #601
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
//如果是statement,清本地缓存
clearLocalCache(); // issue #482
}
}
return list;
}
//延迟加载
public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
if (closed) throw new ExecutorException("Executor was closed.");
DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
if (deferredLoad.canLoad()) {//如果能加载则立即加载,否则加入到延迟加载队列中
deferredLoad.load();
} else {
deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
}
}
//创建缓存key
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) throw new ExecutorException("Executor was closed.");
CacheKey cacheKey = new CacheKey();
//MyBatis 对于其 Key 的生成采取规则为:[mappedStementId + offset + limit + SQL + queryParams + environment]生成一个哈希码
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
for (int i = 0; i < parameterMappings.size(); i++) { // mimic DefaultParameterHandler logic
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
return cacheKey;
}
public boolean isCached(MappedStatement ms, CacheKey key) {
return localCache.getObject(key) != null;
}
public void commit(boolean required) throws SQLException {
if (closed) throw new ExecutorException("Cannot commit, transaction is already closed");
clearLocalCache();
flushStatements();
if (required) {
transaction.commit();
}
}
public void rollback(boolean required) throws SQLException {
if (!closed) {
try {
clearLocalCache();
flushStatements(true);
} finally {
if (required) {
transaction.rollback();
}
}
}
}
//清空本地缓存,一个map结构
public void clearLocalCache() {
if (!closed) {
localCache.clear();
localOutputParameterCache.clear();
}
}
protected abstract int doUpdate(MappedStatement ms, Object parameter)
throws SQLException;
protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
throws SQLException;
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException;
protected void closeStatement(Statement statement) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
// ignore
}
}
}
//处理存储过程的out参数
private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
if (ms.getStatementType() == StatementType.CALLABLE) {
final Object cachedParameter = localOutputParameterCache.getObject(key);
if (cachedParameter != null && parameter != null) {
final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
final MetaObject metaParameter = configuration.newMetaObject(parameter);
for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
if (parameterMapping.getMode() != ParameterMode.IN) {
final String parameterName = parameterMapping.getProperty();
final Object cachedValue = metaCachedParameter.getValue(parameterName);
metaParameter.setValue(parameterName, cachedValue);
}
}
}
}
}
//从数据库中查
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);//向缓存中放入占位符
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);//清除占位符
}
localCache.putObject(key, list);//加入缓存
if (ms.getStatementType() == StatementType.CALLABLE) {//如果是存储过程,OUT参数也加入缓存
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
public void setExecutorWrapper(Executor wrapper) {
this.wrapper = wrapper;
}
//延迟加载
private static class DeferredLoad {
private final MetaObject resultObject;
private final String property;
private final Class<?> targetType;
private final CacheKey key;
private final PerpetualCache localCache;
private final ObjectFactory objectFactory;
private final ResultExtractor resultExtractor;
public DeferredLoad(MetaObject resultObject,
String property,
CacheKey key,
PerpetualCache localCache,
Configuration configuration,
Class<?> targetType) { // issue #781
this.resultObject = resultObject;
this.property = property;
this.key = key;
this.localCache = localCache;
this.objectFactory = configuration.getObjectFactory();
this.resultExtractor = new ResultExtractor(configuration, objectFactory);
this.targetType = targetType;
}
//缓存中找到,且不为占位符,代表可以加载
public boolean canLoad() {
return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
}
//加载
public void load() {
@SuppressWarnings( "unchecked" ) // we suppose we get back a List
List<Object> list = (List<Object>) localCache.getObject(key);
Object value = resultExtractor.extractObjectFromList(list, targetType);
resultObject.setValue(property, value);
}
}
}
executor.query
以上是基本概念,接下来接着看:从上面的DefaultSqlSession.selectList中的executor.query看
dubug调用的时候它是默认调用CachingExecutor的,我们来看看它经历了些什么:
为什么会经过CachingExecutor 呢?
我门来找一下:我们找到了嘿嘿 还是
DefaultSqlSessionFactory的openSessionFromDataSource ->
final Executor executor = configuration.newExecutor(tx, execType);->
if (cacheEnabled) {
//默认开启 装饰者模式 把配置文件里的executor 封装进去
executor = new CachingExecutor(executor);
}
好了,接着看
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//显示得到MappedStatement 绑定的sql
BoundSql boundSql = ms.getBoundSql(parameterObject);
//
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
//然后再调用 query()
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//从MappedStatement里还是找缓存 不存在缓存
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
//如果没有缓存对象,就委托给delegate对象去执行
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
我们接着看delegate 是个啥:
private Executor delegate;
public CachingExecutor(Executor delegate) {
this.delegate = delegate;
//把CachingExecutor 引用传递进去,其实再delegate里面并没有怎么用到这个CachingExecutor
delegate.setExecutorWrapper(this);
}
是一个Executor,又得来看它的来龙去脉了:
请回忆到,或者往上翻一翻我就知道了:
原来是从配置文件里面读出来的相应的Executor:
这里有点疑惑了,SimpleExecutor里面没有query方法啊,别急
public class SimpleExecutor extends BaseExecutor //他是基础了BaseExecutor的 点进去看看
...
...
//它是要调用父类的query方法的 BaseExecutor的
@SuppressWarnings("unchecked")
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) throw new ExecutorException("Executor was closed.");
//先清局部缓存,再查询,但仅仅查询堆栈为0才清,为了处理递归调用
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;//加一,这样递归调用到上面的时候就不会再清局部缓存了
//根据cachekey从localCache去查
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
//如果查到localCache缓存,处理localOutputParameterCache
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//从数据库查 ************* 重要哦
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
//延迟加载队列中所有元素
deferredLoad.load();
}
//清空延迟加载队列
deferredLoads.clear(); // issue #601
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
//如果是statement,清本地缓存
clearLocalCache(); // issue #482
}
}
return list;
}
//从数据库中查
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);//向缓存中放入占位符
try {
// 看看这个doQuery 又是个啥
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);//清除占位符
}
localCache.putObject(key, list);//加入缓存
if (ms.getStatementType() == StatementType.CALLABLE) {//如果是存储过程,OUT参数也加入缓存
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
//哇,是个抽象方法,那么就得留给SimpleExecutor了,终于到子类了
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException;
SimpleExecutor.query
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
//得到配置信息
Configuration configuration = ms.getConfiguration();
//通过配置信息 得到StatementHandler 里面有statement,来处理
//注意哦,这个wrapper就是CacheExecutor
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
来看看 这个StatementHandler 是咋创建出来的? Configuration的
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//它会根据Executor的类型去创建对应具体的statementHandler对象(SimpleStatementHandler,PreparedStatementHandler和CallableStatementHandler)。
//然后放入interceptorChain 拦截器链里面
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
再点进去看一看:
public class RoutingStatementHandler implements StatementHandler {
//注意这个delegate,我们拦截statementHandler的时候就要常常访问它了
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//这个new出来的对象放在delegate里面,等到再SimpleExecutor里面其实是委托给delegate去执行
//又是一个显而易见的装饰者模式
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
//Routing其实不做其他的事
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
return delegate.<E>query(statement, resultHandler);
}
接着SimpleExecutor再来看看这个prepareStatement()方法干了些啥,注意哈,这个handler是RoutingStatementHandler
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//创建一个连接
Connection connection = getConnection(statementLog);
//获取Statement
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
进入到BaseStatementHandler的prepare()
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
//可见是调用的delegate.prepare,我们的是SimpleS,而Simple调用的是其抽象父类的模板方法
//instantiateStatement 是调用子类的
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
instantiateStatement():
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
if (mappedStatement.getResultSetType() != null) {
//创建了Statement
//这个方法非常简单,我们可以看到它主要是根据上下文来预编译SQL,这是我们还没有设置参数。设置参数的任务是交由,statement接口的parameterize方法来实现的。
return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.createStatement();
}
}
所以现在是不是很明显了?得到StatementHandler了,有一点得提一下:4个Excecutor执行sql操作的最终都调用了StatementHandler 来执行
StatementHandler
在MyBatis实现了StatementHandler 的有四个类:
RoutingStatementHandler,这是一个封装类,它不提供具体的实现,只是根据Executor的类型,创建不同的类型StatementHandler。
SimpleStatementHandler,这个类对应于JDBC的Statement对象,用于没有预编译参数的SQL的运行。
PreparedStatementHandler 这个用于预编译参数SQL的运行。
CallableStatementHandler 它将实存储过程的调度。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JQkhAyLq-1586691066723)(assets/1584600319817.png)]
这里接着SimpleExecutor.query方法:就会去调用handler.query.而这个handler截取的是SimpleStatementHandler:
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.<E>handleResultSets(statement);
}
由上面的一阵操作得到了statement,然后执行sql最后交给resultSetHandler去处理结果
当我们需要改变sql的时候,显然我们要在预编译SQL(prepare方法前加入修改的逻辑)。
当我们需要修改参数的时候我们可以在调用parameterize方法前修改逻辑。或者使用ParameterHandler来改造设置参数。
我们需要控制组装结果集的时候,也可以在query方法前后加入逻辑,或者使用ResultHandler来改造组装结果。
懂的这些方法,才能理解我需要拦截什么对象,如何处理插件,这是MyBatis的核心内容。
ParameterHandler
在StatementHandler使用prepare()方法后,接下来就是使用ParameterHandler来设置参数,让我们看看它的定义:
public class DefaultParameterHandler implements ParameterHandler {
private final TypeHandlerRegistry typeHandlerRegistry;
private final MappedStatement mappedStatement;
private final Object parameterObject; //所有的参数值
private BoundSql boundSql;
private Configuration configuration;
public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
this.mappedStatement = mappedStatement;
this.configuration = mappedStatement.getConfiguration();
this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
this.parameterObject = parameterObject;
this.boundSql = boundSql;
}
@Override
public Object getParameterObject() {
return parameterObject;
}
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
//获取所有参数,ParameterMapping是java类型和jdbc类型的对应关系
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
//参数值
Object value;
//获取参数名称
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
//获取参数值
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass()))
//如果是单个值则直接赋值
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
//获取参数值对应的jdbc类型
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
//设置参数值和jdbc类型的对应关系
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
getParameterObject是获取参数,这个参数值就是你传递进来的值,可能是个实体、map或单个基本类型数据。
重点看setParameters(),首先它读取了ParameterObject参数对象,然后用typeHandler对参数进行设置,而typeHandler里面需要对jdbcType和javaType进行处理,然后就设置参数了。也很好理解。所以当我们使用TypeHandler的时候完全可以控制如何设置SQL参数。设置参数,其实就是你在sql语句中配置的java对象和jdbc类型对应的关系,例如#{id,jdbcType=INTEGER},id默认类型是javaType=class java.lang.Integer。
ResultHandler
ResultSetHandler负责处理两件事:
(1)处理Statement执行后产生的结果集,生成结果列表
(2)处理存储过程执行后的输出参数
ResultSetHandler的具体实现类是DefaultResultSetHandler,其实现的步骤就是将Statement执行后的结果集,按照Mapper文件中配置的ResultType或ResultMap来封装成对应的对象,最后将封装的对象返回。
SqlSessionManager
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// Configuration configuration = sqlSessionFactory.getConfiguration();
// SqlSession sqlSession = sqlSessionFactory.openSession();
// DeptDao deptDao = sqlSession.getMapper(DeptDao.class);
SqlSessionManager sqlSessionManager = SqlSessionManager.newInstance(inputStream);
DeptDao deptDao = sqlSessionManager.getMapper(DeptDao.class);
List<Dept> deptList = deptDao.findAll();
System.out.println(deptList.toString());
SqlSessionFactory 和SqlSession都不见了
public class SqlSessionManager implements SqlSessionFactory, SqlSession {
private final SqlSessionFactory sqlSessionFactory;
private final SqlSession sqlSessionProxy;
private ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<SqlSession>();
private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[]{SqlSession.class},
new SqlSessionInterceptor());
}
...
与DefaultSqlSessionFactory不同的是,SqlSessionManager提供了一个本地线程变量(ThreadLocal localSqlSession),每当通过startManagedSession()获得session实例的时候,会在本地线程保存session实例。这是其一不同。
public void startManagedSession() {
this.localSqlSession.set(openSession());
}
插件原理
https://www.cnblogs.com/xrq730/p/6984982.html
1、原理
Mybatis的拦截器实现机制跟上面最后优化后的代码非常的相似。它也有个代理类Plugin
(就是上面的HWInvocationHandler)这个类同样也会实现了InvocationHandler接口
,
当我们调用ParameterHandler,ResultSetHandler,StatementHandler,Executor的对象的时候,就会执行Plugin的invoke方法
,Plugin在invoke方法中根据
@Intercepts
的配置信息(方法名,参数等)动态判断是否需要拦截该方法.再然后使用需要拦截的方法Method封装成Invocation,并调用Interceptor的proceed
方法。
这样我们就达到了拦截目标方法的结果。例如Executor的执行大概是这样的流程:
拦截器代理类对象->拦截器->目标方法
Executor.Method->Plugin.invoke->Interceptor.intercept->Invocation.proceed->method.invoke。
2、如何自定义拦截器?
1) Interceptor接口
首先Mybatis官方早就想到我们开发会有这样的需求,所以开放了一个org.apacheibatis.plugin.Interceptor
这样一个接口。这个接口就是和上面Interceptor性质是一样的
public interface Interceptor {
//当plugin函数返回代理,就可以对其中的方法进行拦截来调用intercept方法
Object intercept(Invocation invocation) throws Throwable;
//plugin方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理。
Object plugin(Object target);
//在Mybatis配置文件中指定一些属性
void setProperties(Properties properties);
}
2)自定义拦截器
这里的ExamplePlugin
和上面的LogInterceptor和TransactionInterceptor
性质是一样的
@Intercepts({@Signature( type= Executor.class, method = "update", args ={MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
return invocation.proceed();
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
}
}
3)、全局xml配置
最后如果你使用的是Mybatis.xml
也就是Mybatis本身单独的配置,你可以需要在这里配置相应的拦截器名字等。
如果你使用的是spring管理的Mybatis,那么你需要在Spring配置文件
里面配置注册相应的拦截器。
这样一个自定义mybatis插件流程大致就是这样了。
3、Mybatis四大接口
竟然Mybatis是对四大接口进行拦截的,那我们要先要知道Mybatis的四大接口对象 Executor
, StatementHandle
, ResultSetHandler
, ParameterHandler
。
1.Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) MyBatis的执行器,用于执行增删改查操作;
2.ParameterHandler (getParameterObject, setParameters) 处理SQL的参数对象;
3.ResultSetHandler (handleResultSets, handleOutputParameters) 处理SQL的返回结果集;
4.StatementHandler (prepare, parameterize, batch, update, query) 拦截Sql语法构建的处理
上图Mybatis框架的整个执行过程。
Mybatis Plugin 插件源码
经过上面的分析,再去看Mybastis Plugin 源码的时候就很轻松了。
这几个也就对应上面的几个,只不过添加了注解,来判断是否拦截指定方法。
1、拦截器链InterceptorChain
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
public Object pluginAll(Object target) {
//循环调用每个Interceptor.plugin方法
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
这个就和我们上面实现的是一样的。定义了拦截器链
2、Configuration
通过初始化配置文件把所有的拦截器添加到拦截器链中。
public class Configuration {
protected final InterceptorChain interceptorChain = new InterceptorChain();
//创建参数处理器
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
//创建ParameterHandler
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
//插件在这里插入
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
//创建结果集处理器
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
//创建DefaultResultSetHandler
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
//插件在这里插入
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
//创建语句处理器
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//创建路由选择语句处理器
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
//插件在这里插入
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public Executor newExecutor(Transaction transaction) {
return newExecutor(transaction, defaultExecutorType);
}
//产生执行器
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
//这句再做一下保护,囧,防止粗心大意的人将defaultExecutorType设成null?
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
//然后就是简单的3个分支,产生3种执行器BatchExecutor/ReuseExecutor/SimpleExecutor
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
//如果要求缓存,生成另一种CachingExecutor(默认就是有缓存),装饰者模式,所以默认都是返回CachingExecutor
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
//此处调用插件,通过插件可以改变Executor行为
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
}
从代码可以看出Mybatis 在实例化Executor、ParameterHandler、ResultSetHandler、StatementHandler四大接口对象的时候调用interceptorChain.pluginAll()
方法插入
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-viOe1yLM-1586691150620)(assets/1584513934498.png)]
进去的。其实就是循环执行拦截器链所有的拦截器的plugin() 方法,mybatis官方推荐的plugin方法是Plugin.wrap() 方法,这个类就是我们上面的TargetProxy类。
3、Plugin
这里的Plugin就是我们上面的自定义代理类TargetProxy类
public class Plugin implements InvocationHandler {
public static Object wrap(Object target, Interceptor interceptor) {
//从拦截器的注解中获取拦截的类名和方法信息
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
//取得要改变行为的类(ParameterHandler|ResultSetHandler|StatementHandler|Executor)
Class<?> type = target.getClass();
//取得接口
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
//产生代理,是Interceptor注解的接口的实现类才会产生代理
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//获取需要拦截的方法
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
//是Interceptor实现类注解的方法才会拦截处理
if (methods != null && methods.contains(method)) {
//调用Interceptor.intercept,也即插入了我们自己的逻辑
return interceptor.intercept(new Invocation(target, method, args));
}
//最后还是执行原来逻辑
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
//取得签名Map,就是获取Interceptor实现类上面的注解,要拦截的是那个类(Executor,ParameterHandler, ResultSetHandler,StatementHandler)的那个方法
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
//取Intercepts注解,例子可参见ExamplePlugin.java
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
// issue #251
//必须得有Intercepts注解,没有报错
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
//value是数组型,Signature的数组
Signature[] sigs = interceptsAnnotation.value();
//每个class里有多个Method需要被拦截,所以这么定义
Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
for (Signature sig : sigs) {
Set<Method> methods = signatureMap.get(sig.type());
if (methods == null) {
methods = new HashSet<Method>();
signatureMap.put(sig.type(), methods);
}
try {
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
} catch (NoSuchMethodException e) {
throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
}
}
return signatureMap;
}
//取得接口
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<Class<?>>();
while (type != null) {
for (Class<?> c : type.getInterfaces()) {
//拦截其他的无效
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class<?>[interfaces.size()]);
}
}
4、Interceptor接口
public interface Interceptor {
//拦截
Object intercept(Invocation invocation) throws Throwable;
//插入
Object plugin(Object target);
//设置属性(扩展)
void setProperties(Properties properties);
}
Mybatis用到的设计模式
#一级缓存&二级缓存
##Mybatis缓存的作用
每当我们使用 MyBatis 开启一次和数据库的会话,MyBatis 会创建出一个 SqlSession 对象表示一次数据库会话。
在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,如果不采取一些措施的话,每一次查询都会查询一次数据库,而我们在极短的时间内做了完全相同的查询,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。
为了解决这一问题,减少资源的浪费,MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了。
mybatis的缓存有一级缓存和二级缓存。
##一级缓存
一级缓存是默认开启的,作用域是session级别的,缓存的key格式如下:
cache key: id + sql + limit + offset
在commit之前,第一次查询结果换以key value的形式存起来,如果有相同的key进来,直接返回value,这样有助于减轻数据的压力。
相关源码:
org.apache.ibatis.executor.BaseExecutor#createCacheKey
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
for(int i = 0; i < parameterMappings.size(); ++i) {
ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
String propertyName = parameterMapping.getProperty();
Object value;
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = this.configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
if (this.configuration.getEnvironment() != null) {
cacheKey.update(this.configuration.getEnvironment().getId());
}
return cacheKey;
}
}
查询数据库并存入一级缓存的语句
org.apache.ibatis.executor.BaseExecutor#queryFromDatabase
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
List list;
try {
list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
this.localCache.removeObject(key);
}
//将查询出来的结果存入一级缓存
this.localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
//如果是存储过程把参数存入localOutputParameterCache
this.localOutputParameterCache.putObject(key, parameter);
}
return list;
}
并且当commit或者rollback的时候会清除缓存,并且当执行insert、update、delete的时候也会清除缓存。
相关源码:
org.apache.ibatis.executor.BaseExecutor#update
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
//删除一级缓存
this.clearLocalCache();
return this.doUpdate(ms, parameter);
}
}
org.apache.ibatis.executor.BaseExecutor#commit
public void commit(boolean required) throws SQLException {
if (this.closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
} else {
//删除一级缓存
this.clearLocalCache();
this.flushStatements();
if (required) {
this.transaction.commit();
}
}
}
org.apache.ibatis.executor.BaseExecutor#rollback
public void rollback(boolean required) throws SQLException {
if (!this.closed) {
try {
//删除一级缓存
this.clearLocalCache();
this.flushStatements(true);
} finally {
if (required) {
this.transaction.rollback();
}
}
}
}
##二级缓存
二级缓存是手动开启的,作用域为sessionfactory(也可以说MapperStatement级缓存,也就是一个namespace就会有一个缓存),因为二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的,也就是要求实现Serializable接口,如果存储在内存中的话,实测不序列化也可以的。
如果开启了二级缓存的话,你的Executor将会被装饰成CachingExecutor,缓存是通过CachingExecutor来操作的,查询出来的结果会存在statement中的cache中,若有更新,删除类的操作默认就会清空该MapperStatement的cache(也可以通过修改xml中的属性,让它不执行),不会影响其他的MapperStatement。
相关源码:
org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? this.defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Object executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
//是否开启缓存,传入的参数为SimpleExecutor
if (this.cacheEnabled) {
executor = new CachingExecutor((Executor)executor);
}
//责任链模式拦截器
Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
return executor;
}
query
org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
//获得该MappedStatement的cache
Cache cache = ms.getCache();
//如果缓存不为空
if (cache != null) {
//看是否需要清除cache(在xml中可以配置flushCache属性决定何时清空cache)
this.flushCacheIfRequired(ms);
//若开启了cache且resultHandler 为空
if (ms.isUseCache() && resultHandler == null) {
this.ensureNoOutParams(ms, parameterObject, boundSql);
//从TransactionalCacheManager中取cache
List<E> list = (List)this.tcm.getObject(cache, key);
//若取出来list是空的
if (list == null) {
//查询数据库
list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//将结果存入cache中
this.tcm.putObject(cache, key, list);
}
return list;
}
}
//如果缓存为空,去查询数据库
return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
对于
this.tcm.getObject(cache, key);
因同一个namespace下的MappedStatement的cache是同一个,而TransactionalCacheManager中统一管理cache是里面的属性transactionalCaches,该属性以MappedStatement中的Cache为key,TransactionalCache对象为Value。即一个namespace对应一个TransactionalCache。
相关源码:
TransactionalCacheManager
org.apache.ibatis.cache.TransactionalCacheManager
public class TransactionalCacheManager {
private Map<Cache, TransactionalCache> transactionalCaches = new HashMap();
...
}
TransactionalCache
org.apache.ibatis.cache.decorators.TransactionalCache
public class TransactionalCache implements Cache {
private static final Log log = LogFactory.getLog(TransactionalCache.class);
//namespace中的cache
private Cache delegate;
//提交的时候清除cache的标志位
private boolean clearOnCommit;
//待提交的集合
private Map<Object, Object> entriesToAddOnCommit;
//未查到的key存放的集合
private Set<Object> entriesMissedInCache;
...
}
update
//更新
org.apache.ibatis.executor.CachingExecutor#update
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
//看是否需要清除cache(在xml中可以配置flushCache属性决定何时清空cache)
this.flushCacheIfRequired(ms);
return this.delegate.update(ms, parameterObject);
}
org.apache.ibatis.executor.CachingExecutor#flushCacheIfRequired
private void flushCacheIfRequired(MappedStatement ms) {
//获得cache
Cache cache = ms.getCache();
//若isFlushCacheRequired为true,则清除cache
if (cache != null && ms.isFlushCacheRequired()) {
this.tcm.clear(cache);
}
}
##一级、二级缓存测试
因为一级缓存是默认生效的,下面是二级缓存开启步骤。
mybatis-config.xml
<settings>
<!--这个配置使全局的映射器(二级缓存)启用或禁用缓存-->
<setting name="cacheEnabled" value="true" />
</settings>
在mapper.xml可以进行如下的配置
<mapper>
<!--开启本mapper的namespace下的二级缓存-->
<!--
eviction:代表的是缓存回收策略,目前MyBatis提供以下策略。
(1) LRU,最近最少使用的,一处最长时间不用的对象
(2) FIFO,先进先出,按对象进入缓存的顺序来移除他们
(3) SOFT,软引用,移除基于垃圾回收器状态和软引用规则的对象
(4) WEAK,弱引用,更积极的移除基于垃圾收集器状态和弱引用规则的对象。这里采用的是LRU,
移除最长时间不用的对形象
flushInterval:刷新间隔时间,单位为毫秒,这里配置的是100秒刷新,如果你不配置它,那么当
SQL被执行的时候才会去刷新缓存。
size:引用数目,一个正整数,代表缓存最多可以存储多少个对象,不宜设置过大。设置过大会导致内存溢出。
这里配置的是1024个对象
readOnly:只读,意味着缓存数据只能读取而不能修改,这样设置的好处是我们可以快速读取缓存,缺点是我们没有
办法修改缓存,他的默认值是false,不允许我们修改
-->
<cache eviction="LRU" flushInterval="100000" readOnly="true" size="1024"/>
<!--刷新二级缓存-->
<update id="updateByPrimaryKey" parameterType="com.demo.mybatis.pojo.User" flushCache="true">
update user
set name = #{name,jdbcType=VARCHAR},
age = #{age,jdbcType=INTEGER}
where id = #{id,jdbcType=INTEGER}
</update>
<!--可以通过设置useCache来规定这个sql是否开启缓存,ture是开启,false是关闭-->
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap" useCache="true" >
select
<include refid="Base_Column_List" />
from user
where id = #{id,jdbcType=INTEGER}
</select>
</mapper>
其中仅仅添加下面这个也可以
<cache/>
如果我们配置了二级缓存就意味着:
- 映射语句文件中的所有select语句将会被缓存。
- 映射语句文件中的所欲insert、update和delete语句会刷新缓存。
- 缓存会使用默认的Least Recently Used(LRU,最近最少使用的)算法来收回。
- 根据时间表,比如No Flush Interval,(CNFI没有刷新间隔),缓存不会以任何时间顺序来刷新。
- 缓存会存储列表集合或对象(无论查询方法返回什么)的1024个引用。
- 缓存会被视为是read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全的被调用者修改,不干扰其他调用者或线程所做的潜在修改。
User.java
public class User implements Serializable {
private Integer id;
private String name;
private Integer age;
private static final long serialVersionUID = 1L;
...
set/get
...
}
UserMapper.java
User selectByPrimaryKey(Integer id);
测试方法
@Test
public void test03() throws IOException {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//第一次
User user = userMapper.selectByPrimaryKey(1);
System.out.println("user1 => " + user.toString());
//第二次
User user2 = userMapper.selectByPrimaryKey(1);
System.out.println("user2 => " + user2.toString());
//session提交
sqlSession.commit();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
//第三次
User user3 = userMapper2.selectByPrimaryKey(1);
System.out.println("user3 => " + user3.toString());
//第四次
User user4 = userMapper2.selectByPrimaryKey(1);
System.out.println("user4 => " + user4.toString());
sqlSession2.commit();
}
来看下结果
DEBUG 2019-01-30 00:01:29791 Opening JDBC Connection
DEBUG 2019-01-30 00:01:34688 Created connection 1121453612.
DEBUG 2019-01-30 00:01:34689 Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@42d8062c]
DEBUG 2019-01-30 00:01:34691 ==> Preparing: select id, name, age from user where id = ?
DEBUG 2019-01-30 00:01:34737 ==> Parameters: 1(Integer)
DEBUG 2019-01-30 00:01:34757 <== Total: 1
user1 => User{id=1, name='ayang', age=18}
DEBUG 2019-01-30 00:01:34757 Cache Hit Ratio [com.demo.mybatis.mapper.UserMapper]: 0.0
user2 => User{id=1, name='ayang', age=18}
DEBUG 2019-01-30 00:01:34818 Cache Hit Ratio [com.demo.mybatis.mapper.UserMapper]: 0.3333333333333333
user3 => User{id=1, name='ayang', age=18}
DEBUG 2019-01-30 00:01:34819 Cache Hit Ratio [com.demo.mybatis.mapper.UserMapper]: 0.5
user4 => User{id=1, name='ayang', age=18}
可以看到第一次和第二次走的是一级缓存,第三次和第四次走的是二级缓存。
总结:
一级缓存是自动开启的,sqlSession级别的缓存,查询结果存放在BaseExecutor中的localCache中。
如果第一次做完查询,接着做一次update | insert | delete | commit | rollback操作,则会清除缓存,第二次查询则继续走数据库。
对于一级缓存不同的sqlSession之间的缓存是互相不影响的。
二级缓存是手动开启的,作用域为sessionfactory,也可以说MapperStatement级缓存,也就是一个namespace(mapper.xml)就会有一个缓存,不同的sqlSession之间的缓存是共享的。
因为二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的,也就是要求实现Serializable接口,如果存储在内存中的话,实测不序列化也可以的。
一般为了避免出现脏数据,所以我们可以在每一次的insert | update | delete操作后都进行缓存刷新,也就是在Statement配置中配置flushCache属性,如下:
<!--刷新二级缓存 flushCache="true"-->
<update id="updateByPrimaryKey" parameterType="com.demo.mybatis.pojo.User" flushCache="true">
update user
set name = #{name,jdbcType=VARCHAR},
age = #{age,jdbcType=INTEGER}
where id = #{id,jdbcType=INTEGER}
</update>