【源码分析】Mybatis使用中,同一个事物里,select查询不出之前insert的数据
一、问题场景模拟
问题:第二次查询和第一次查询结果一模一样,没有查询出我新插入的数据
猜测:第二次查询走了Mybatis缓存
疑问:那为什么会走缓存呢?
1.service方法
- @Override
- @Transactional(rollbackFor = Throwable.class,propagation = Propagation.REQUIRED)
- public void test() {
- //1.第一次查询
- List<Integer> studentIdListByCid = tCourseStudentDao.findStudentIdListByCid(1);
- System.out.println(studentIdListByCid);
- //2.插入一条新数据
- TCourseStudent tCourseStudent = new TCourseStudent();
- tCourseStudent.setCourseId(1);
- tCourseStudent.setStudentId(1);
- List list = new ArrayList();
- list.add(tCourseStudent);
- tCourseStudentDao.batchInsert(list);
- //第二次查询
- List<Integer> studentIdListByCid2 = tCourseStudentDao.findStudentIdListByCid(1);
- System.out.println(studentIdListByCid2);
- }
2.dao方法
- @SelectProvider(type = TCourseStudentDaoSqlProvider.class, method = "batchInsert")
- void batchInsert(@Param("tCourseStudents") List<TCourseStudent> tCourseStudents);
二、解决方法
是因为dao的方法注解使用错了
将@SelectProvider换成@InsertProvider就可以
三、源码解析
1.执行batchInsert时,会调用MapperProxy的invoke方法,该方法中会构件MapperMethod对象,真正用来执行sql的
- public class MapperProxy<T> implements InvocationHandler, Serializable {
- private static final long serialVersionUID = -6424540398559729838L;
- 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 {
- if (Object.class.equals(method.getDeclaringClass())) {
- return method.invoke(this, args);
- }
- final MapperMethod mapperMethod = cachedMapperMethod(method);
- 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;
- }
- }
1)当使用SelectProvider时,
构件MapperMethod时,type是"select"
2)当使用InsertProvider时,
构件MapperMethod时,type是"insert"
2.构件完MapperMethod后,会调用 mapperMethod.execute(sqlSession, args);然后根据SqlCommandType选择执行不同的sql方法
- 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, method);
- }
- public Object execute(SqlSession sqlSession, Object[] args) {
- Object result;
- if (SqlCommandType.INSERT == command.getType()) {
- Object param = method.convertArgsToSqlCommandParam(args);
- result = rowCountResult(sqlSession.insert(command.getName(), param));
- } else if (SqlCommandType.UPDATE == command.getType()) {
- Object param = method.convertArgsToSqlCommandParam(args);
- result = rowCountResult(sqlSession.update(command.getName(), param));
- } else if (SqlCommandType.DELETE == command.getType()) {
- Object param = method.convertArgsToSqlCommandParam(args);
- result = rowCountResult(sqlSession.delete(command.getName(), param));
- } else if (SqlCommandType.SELECT == command.getType()) {
- 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 {
- Object param = method.convertArgsToSqlCommandParam(args);
- result = sqlSession.selectOne(command.getName(), param);
- }
- } else {
- 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;
- }
- ....
- }
3.根据不同SqlCommandType执行不同的逻辑
1)当SqlCommandType为SqlCommandType.SELECT时,只是简单的执行查询逻辑,当前sql会执行selectOne方法
- if (SqlCommandType.SELECT == command.getType()) {
- 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 {
- Object param = method.convertArgsToSqlCommandParam(args);
- result = sqlSession.selectOne(command.getName(), param);
- }
- }
- sqlSession.selectOne===>然后执行
- SqlSessionInterceptor.invoke()====》然后执行
- DefaultSqlSession.selectOne(),我们发现到这一步只是简单的执行以下我们的插入sql,并没有清除Mybatis缓存的逻辑
- public class SqlSessionTemplate implements SqlSession {
- ...
- private class SqlSessionInterceptor implements InvocationHandler {
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- final SqlSession sqlSession = getSqlSession(
- SqlSessionTemplate.this.sqlSessionFactory,
- SqlSessionTemplate.this.executorType,
- SqlSessionTemplate.this.exceptionTranslator);
- try {
- Object result = method.invoke(sqlSession, args);
- if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
- // force commit even on non-dirty sessions because some databases require
- // a commit/rollback before calling close()
- sqlSession.commit(true);
- }
- return result;
- } catch (Throwable t) {
- Throwable unwrapped = unwrapThrowable(t);
- if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
- Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
- if (translated != null) {
- unwrapped = translated;
- }
- }
- throw unwrapped;
- } finally {
- closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
- }
- }
- }
- }
- public class DefaultSqlSession implements SqlSession {
- public <T> T selectOne(String statement, Object parameter) {
- // Popular vote was to return null on 0 results and throw exception on too many.
- List<T> list = this.<T>selectList(statement, parameter);
- if (list.size() == 1) {
- return list.get(0);
- } else if (list.size() > 1) {
- throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
- } else {
- return null;
- }
- }
- }
2)当SqlCommandType为SqlCommandType.INSERT时,执行sqlsession.insert()方法,并最终走BaseExecutor.update方法,该方法会清除缓存
除了SqlCommandType.INSERT,SqlCommandType.UPDATE,SqlCommandType.DELETE都会走BaseExecutor.update方法,所有会自动将Mybatis缓存清空,防止查询不到最新的数据
- if (SqlCommandType.INSERT == command.getType()) {
- Object param = method.convertArgsToSqlCommandParam(args);
- result = rowCountResult(sqlSession.insert(command.getName(), param));
- }
- public class SqlSessionTemplate implements SqlSession {
- 。。。
- public int insert(String statement, Object parameter) {
- return this.sqlSessionProxy.insert(statement, parameter);
- }
- private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
final SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
- }
- public class DefaultSqlSession implements SqlSession {
- public int insert(String statement, Object parameter) {
- return update(statement, parameter);
- }
- public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
- }
- public class Plugin implements InvocationHandler {
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- try {
- Set<Method> methods = signatureMap.get(method.getDeclaringClass());
- if (methods != null && methods.contains(method)) {
- return interceptor.intercept(new Invocation(target, method, args));
- }
- return method.invoke(target, args);
- } catch (Exception e) {
- throw ExceptionUtil.unwrapThrowable(e);
- }
- }
- }
- public class CachingExecutor implements Executor {
- public int update(MappedStatement ms, Object parameterObject) throws SQLException {
- flushCacheIfRequired(ms); //不是这一步清除的缓存, Cache cache = ms.getCache();cache为null
- return delegate.update(ms, parameterObject);
- }
- private void flushCacheIfRequired(MappedStatement ms) {
- Cache cache = ms.getCache();
- if (cache != null) {
- if (ms.isFlushCacheRequired()) {
- dirty = true; // issue #524. Disable using cached data for this session
- tcm.clear(cache);
- }
- }
- }
- }
到这一步,clearLocalCache();才真正的清除掉本地缓存
- public abstract class BaseExecutor implements Executor {
- 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 void clearLocalCache() {
- if (!closed) {
- localCache.clear();
- localOutputParameterCache.clear();
- }
- }
- }
- public class SimpleExecutor extends BaseExecutor {
- public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
- Statement stmt = null;
- try {
- Configuration configuration = ms.getConfiguration();
- StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
- stmt = prepareStatement(handler, ms.getStatementLog());
- return handler.update(stmt);
- } finally {
- closeStatement(stmt);
- }
- }
- }
- public class RoutingStatementHandler implements StatementHandler {
- public int update(Statement statement) throws SQLException {
- return delegate.update(statement);
- }
- }
- public class PreparedStatementHandler extends BaseStatementHandler {
- public int update(Statement statement) throws SQLException {
- PreparedStatement ps = (PreparedStatement) statement;
- ps.execute();
- int rows = ps.getUpdateCount();
- Object parameterObject = boundSql.getParameterObject();
- KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
- keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
- return rows;
- }
- }
【源码分析】Mybatis使用中,同一个事物里,select查询不出之前insert的数据的更多相关文章
- 【转】MaBatis学习---源码分析MyBatis缓存原理
[原文]https://www.toutiao.com/i6594029178964673027/ 源码分析MyBatis缓存原理 1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 ...
- vscode源码分析【九】窗口里的主要元素
第一篇: vscode源码分析[一]从源码运行vscode 第二篇:vscode源码分析[二]程序的启动逻辑,第一个窗口是如何创建的 第三篇:vscode源码分析[三]程序的启动逻辑,性能问题的追踪 ...
- MyBatis源码分析-MyBatis初始化流程
MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...
- 精尽 MyBatis 源码分析 - MyBatis 初始化(一)之加载 mybatis-config.xml
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽MyBatis源码分析 - MyBatis初始化(二)之加载Mapper接口与XML映射文件
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽 MyBatis 源码分析 - MyBatis 初始化(三)之 SQL 初始化(上)
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽MyBatis源码分析 - MyBatis初始化(四)之 SQL 初始化(下)
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽MyBatis源码分析 - MyBatis 的 SQL 执行过程(一)之 Executor
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- mybatis 学习四 源码分析 mybatis如何执行的一条sql
总体三部分,创建sessionfactory,创建session,执行sql获取结果 1,创建sessionfactory 这里其实主要做的事情就是将xml的所有配置信息转换成一个Confi ...
随机推荐
- Regsvr32 在64位机器上的用法(转载)
转载:http://blog.csdn.net/xuzhimin1991/article/details/65436864 regsvr32是windows上注册 OLE 控件(DLL 或 Activ ...
- 3.sql2008查询
根据需要和条件,查看并显示结果集,如果需要,可将结果集生成数据表select:查什么,列筛选,可以用*代表全部列from:在哪个表中查,where:符合什么样的条件,行筛选select: ...
- Oracle错误——SP2-0734: 未知的命令开头 "imp C##sin..." - 忽略了剩余的行。
错误 在windows的DOS窗口下使用命令导入Oracle数据. 原因 进入sqlplus里是不能执行imp的(sqlplus不认识imp),imp 是个工具,应该在cmd的dos命令提示符下执行.
- [转] J2EE基础知识
Servlet总结 阐述Servlet和CGI的区别? CGI的不足之处: Servlet的优点: Servlet接口中有哪些方法及Servlet生命周期探秘 get和post请求的区别 什么情况下调 ...
- C# 禁止任务管理器关闭
http://www.cnblogs.com/luomingui/archive/2011/06/25/2090130.html 测试了好像没用的.不知道什么原因
- P4822 [BJWC2012]冻结
思路 和p4568类似的分层图最短路 从上一层向下一层连边权/2的边即可 代码 #include <cstdio> #include <algorithm> #include ...
- [nginx] - 使用nginx实现反向代理,动静分离,负载均衡,session共享
反向代理概念 先说正向代理,比如要访问youtube,但是不能直接访问,只能先找个FQ软件,通过FQ软件才能访问youtube. FQ软件就叫做正向代理.所谓的反向代理,指的是用户要访问youtube ...
- LightOJ 1268 Unlucky Strings(KMP+矩阵乘法+基础DP)
题意 给出字符串的长度 \(n\) ,以及该字符串是由哪些小写字母组成,现给出一个坏串 \(S\) ,求存在多少种不同的字符串,使得其子串不含坏串. \(1 \leq n \leq 10^9\) \( ...
- 【Hadoop 分布式部署 八:分布式协作框架Zookeeper架构功能讲解 及本地模式安装部署和命令使用 】
What is Zookeeper 是一个开源的分布式的,为分布式应用提供协作服务的Apache项目 提供一个简单的原语集合,以便与分布式应用可以在他之上构建更高层次的同步服务 设计非常简单易于编 ...
- Transaction
SqlTransaction——事务详解 事务是将一系列操作作为一个单元执行,要么成功,要么失败,回滚到最初状态.在事务处理术语中,事务要么提交,要么中止.若要提交事务,所有参与者都必须保证对数据的任 ...