当前位置:   article > 正文

Mybatis的核心——SqlSession解读

this.sqlsessionproxy.

文章首发于blog.csdn.net/doujinlong1…

在spring中,dao层大多都是用Mybatis,那么

1,Mybatis执行sql最重要的是什么?

在以前对Mybatis的源码解读中,我们知道,Mybatis利用了动态代理来做,最后实现的类是MapperProxy,在最后执行具体的方法时,实际上执行的是:

  1. @Override
  2. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  3. if (Object.class.equals(method.getDeclaringClass())) {
  4. try {
  5. return method.invoke(this, args);
  6. } catch (Throwable t) {
  7. throw ExceptionUtil.unwrapThrowable(t);
  8. }
  9. }
  10. final MapperMethod mapperMethod = cachedMapperMethod(method);
  11. return mapperMethod.execute(sqlSession, args);
  12. }
  13. 复制代码

最重要的一步:

  1. mapperMethod.execute(sqlSession, args);
  2. 复制代码

这里的sqlSession 其实是在Spring的配置时设置的 sqlSessionTemplate,随便对其中的一个进行跟进:可以在sqlSessionTemplate类中发现很好这样的方法,用来执行具体的sql,如:

  1. @Override
  2. public <T> T selectOne(String statement, Object parameter) {
  3. return this.sqlSessionProxy.<T> selectOne(statement, parameter);
  4. }
  5. 复制代码

这一步就是最后执行的方法,那么问题来了 sqlSessionProxy 到底是啥呢? 这又得回到最开始。

2,使用mybatis连接mysql时一般都是需要注入SqlSessionFactory,SqlSessionTemplate,PlatformTransactionManager。

其中SqlSessionTemplate是生成sqlSession的模版,来看他的注入过程(注解形式注入):

  1. @Bean
  2. public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
  3. return new SqlSessionTemplate(sqlSessionFactory);
  4. }
  5. 复制代码

在这个初始化过程中:

  1. /**
  2. * Constructs a Spring managed {@code SqlSession} with the given
  3. * {@code SqlSessionFactory} and {@code ExecutorType}.
  4. * A custom {@code SQLExceptionTranslator} can be provided as an
  5. * argument so any {@code PersistenceException} thrown by MyBatis
  6. * can be custom translated to a {@code RuntimeException}
  7. * The {@code SQLExceptionTranslator} can also be null and thus no
  8. * exception translation will be done and MyBatis exceptions will be
  9. * thrown
  10. *
  11. * @param sqlSessionFactory
  12. * @param executorType
  13. * @param exceptionTranslator
  14. */
  15. public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
  16. PersistenceExceptionTranslator exceptionTranslator) {
  17. notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
  18. notNull(executorType, "Property 'executorType' is required");
  19. this.sqlSessionFactory = sqlSessionFactory;
  20. this.executorType = executorType;
  21. this.exceptionTranslator = exceptionTranslator;
  22. this.sqlSessionProxy = (SqlSession) newProxyInstance(
  23. SqlSessionFactory.class.getClassLoader(),
  24. new Class[] { SqlSession.class },
  25. new SqlSessionInterceptor());
  26. 复制代码

}

最后一步比较重要,用java动态代理生成了一个sqlSessionFactory。代理的类是:

  1. /**
  2. * Proxy needed to route MyBatis method calls to the proper SqlSession got
  3. * from Spring's Transaction Manager
  4. * It also unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to
  5. * pass a {@code PersistenceException} to the {@code PersistenceExceptionTranslator}.
  6. */
  7. private class SqlSessionInterceptor implements InvocationHandler {
  8. @Override
  9. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  10. SqlSession sqlSession = getSqlSession(
  11. SqlSessionTemplate.this.sqlSessionFactory,
  12. SqlSessionTemplate.this.executorType,
  13. SqlSessionTemplate.this.exceptionTranslator);
  14. try {
  15. Object result = method.invoke(sqlSession, args);
  16. if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
  17. // force commit even on non-dirty sessions because some databases require
  18. // a commit/rollback before calling close()
  19. sqlSession.commit(true);
  20. }
  21. return result;
  22. } catch (Throwable t) {
  23. Throwable unwrapped = unwrapThrowable(t);
  24. if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
  25. // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
  26. closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
  27. sqlSession = null;
  28. Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
  29. if (translated != null) {
  30. unwrapped = translated;
  31. }
  32. }
  33. throw unwrapped;
  34. } finally {
  35. if (sqlSession != null) {
  36. closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
  37. }
  38. }
  39. }
  40. 复制代码

}

在sqlSession执行sql的时候就会用这个代理类。isSqlSessionTransactional 这个会判断是不是有Transactional,没有则直接提交。如果有则不提交,在最外层进行提交。

其中

  1. getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,SqlSessionTemplate.this.executorType,SqlSessionTemplate.this.exceptionTranslator);
  2. 复制代码

这个方法用来获取sqlSession。具体实现如下:

  1. /**
  2. * Gets an SqlSession from Spring Transaction Manager or creates a new one if needed.
  3. * Tries to get a SqlSession out of current transaction. If there is not any, it creates a new one.
  4. * Then, it synchronizes the SqlSession with the transaction if Spring TX is active and
  5. * <code>SpringManagedTransactionFactory</code> is configured as a transaction manager.
  6. *
  7. * @param sessionFactory a MyBatis {@code SqlSessionFactory} to create new sessions
  8. * @param executorType The executor type of the SqlSession to create
  9. * @param exceptionTranslator Optional. Translates SqlSession.commit() exceptions to Spring exceptions.
  10. * @throws TransientDataAccessResourceException if a transaction is active and the
  11. * {@code SqlSessionFactory} is not using a {@code SpringManagedTransactionFactory}
  12. * @see SpringManagedTransactionFactory
  13. */
  14. public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
  15. notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
  16. notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
  17. SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
  18. SqlSession session = sessionHolder(executorType, holder);
  19. if (session != null) {
  20. return session;
  21. }
  22. if (LOGGER.isDebugEnabled()) {
  23. LOGGER.debug("Creating a new SqlSession");
  24. }
  25. session = sessionFactory.openSession(executorType);
  26. registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
  27. return session;
  28. }
  29. 复制代码

这个sqlSession的创建其实看他方法解释就够了,“从Spring事务管理器中获取一个sqlsession,如果没有,则创建一个新的”,这句话的意思其实就是如果有事务,则sqlSession用一个,如果没有,就给你个新的咯。 再通俗易懂一点:**如果在事务里,则Spring给你的sqlSession是一个,否则,每一个sql给你一个新的sqlSession。**这里生成的sqlSession其实就是DefaultSqlSession了。后续可能仍然有代理,如Mybatis分页插件等,不在此次讨论的范围内。

3,第二步的 sqlSession 一样不一样到底有什么影响?

在2中,我们看到如果是事务,sqlSession 一样,如果不是,则每次都不一样,且每次都会提交。这是最重要的。

sqlSession,顾名思义,就是sql的一个会话,在这个会话中发生的事不影响别的会话,如果会话提交,则生效,不提交不生效。

来看下sqlSession 这个接口的介绍。

  1. /**
  2. * The primary Java interface for working with MyBatis.
  3. * Through this interface you can execute commands, get mappers and manage transactions.
  4. * 为Mybatis工作最重要的java接口,通过这个接口来执行命令,获取mapper以及管理事务
  5. * @author Clinton Begin
  6. */
  7. 复制代码

注释很明白了,来一一看看怎么起的这些作用。

3.1,执行命令。

在第一个小标题中 执行sql最重要的方法就是 this.sqlSessionProxy. selectOne(statement, parameter); 这个方法,而在第二个小标题中我们看到是通过代理来执行的,最后实际上没有事务则提交sql。这就是执行sql的基本动作了。获取sqlsession,提交执行Sql。

3.2,获取mapper。

在我们日常的代码中可能不会这么写,但是实际上,如果必要我们是可以这么做的,如:

  1. XxxxxMapper xxxxxMapper = session.getMapper(xxxxxMapper.class);
  2. 复制代码

一般情况下,如果要这么做,首先需要注入 sqlSessionFactory,然后利用

  1. sqlSessionFactory.openSession()。
  2. 复制代码

即可获取session

####3.3,事务管理 ####

上面我一直提到一点,sqlSession 那个代理类里有个操作,判断这个是不是事务管理的sqlSession,如果是,则不提交,不是才提交,这个就是事务管理了,那么有个问题,在哪里提交这个事务呢????

4,事务从哪里拦截,就从哪里提交

Spring中,如果一个方法被 @Transactional 注解标注,在生效的情况下(不生效的情况见我写动态代理的那篇博客),则最终会被TransactionInterceptor 这个类所代理,执行的方法实际上是这样的:

  1. @Override
  2. public Object invoke(final MethodInvocation invocation) throws Throwable {
  3. // Work out the target class: may be {@code null}.
  4. // The TransactionAttributeSource should be passed the target class
  5. // as well as the method, which may be from an interface.
  6. Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
  7. // Adapt to TransactionAspectSupport's invokeWithinTransaction...
  8. return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
  9. @Override
  10. public Object proceedWithInvocation() throws Throwable {
  11. return invocation.proceed();
  12. }
  13. });
  14. }
  15. 复制代码

继续看invokeWithinTransaction这个方法:

  1. /**
  2. * General delegate for around-advice-based subclasses, delegating to several other template
  3. * methods on this class. Able to handle {@link CallbackPreferringPlatformTransactionManager}
  4. * as well as regular {@link PlatformTransactionManager} implementations.
  5. * @param method the Method being invoked
  6. * @param targetClass the target class that we're invoking the method on
  7. * @param invocation the callback to use for proceeding with the target invocation
  8. * @return the return value of the method, if any
  9. * @throws Throwable propagated from the target invocation
  10. */
  11. protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
  12. throws Throwable {
  13. // If the transaction attribute is null, the method is non-transactional.
  14. final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
  15. final PlatformTransactionManager tm = determineTransactionManager(txAttr);
  16. final String joinpointIdentification = methodIdentification(method, targetClass);
  17. //基本上我们的事务管理器都不是一个CallbackPreferringPlatformTransactionManager,所以基本上都是会从这个地方进入,下面的else情况暂不讨论。
  18. if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
  19. // Standard transaction demarcation with getTransaction and commit/rollback calls.
  20. //获取具体的TransactionInfo ,如果要用编程性事务,则把这块的代码可以借鉴一下。
  21. TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
  22. Object retVal = null;
  23. try {
  24. // This is an around advice: Invoke the next interceptor in the chain.
  25. // This will normally result in a target object being invoked.
  26. retVal = invocation.proceedWithInvocation(); //执行被@Transactional标注里面的具体方法。
  27. }
  28. catch (Throwable ex) {
  29. // target invocation exception
  30. //异常情况下,则直接完成了,因为在sqlsession执行完每一条指令都没有提交事务,所以表现出来的就是回滚事务。
  31. completeTransactionAfterThrowing(txInfo, ex);
  32. throw ex;
  33. }
  34. finally {
  35. cleanupTransactionInfo(txInfo);
  36. }
  37. //正常执行完成的提交事务方法 跟进可以看到实际上执行的是:(编程性事务的提交)
  38. // ==============txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());===========
  39. commitTransactionAfterReturning(txInfo);
  40. return retVal;
  41. }
  42. // =======================else情况不讨论================================
  43. else {
  44. // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
  45. try {
  46. Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr,
  47. new TransactionCallback<Object>() {
  48. @Override
  49. public Object doInTransaction(TransactionStatus status) {
  50. TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
  51. try {
  52. return invocation.proceedWithInvocation();
  53. }
  54. catch (Throwable ex) {
  55. if (txAttr.rollbackOn(ex)) {
  56. // A RuntimeException: will lead to a rollback.
  57. if (ex instanceof RuntimeException) {
  58. throw (RuntimeException) ex;
  59. }
  60. else {
  61. throw new ThrowableHolderException(ex);
  62. }
  63. }
  64. else {
  65. // A normal return value: will lead to a commit.
  66. return new ThrowableHolder(ex);
  67. }
  68. }
  69. finally {
  70. cleanupTransactionInfo(txInfo);
  71. }
  72. }
  73. });
  74. // Check result: It might indicate a Throwable to rethrow.
  75. if (result instanceof ThrowableHolder) {
  76. throw ((ThrowableHolder) result).getThrowable();
  77. }
  78. else {
  79. return result;
  80. }
  81. }
  82. catch (ThrowableHolderException ex) {
  83. throw ex.getCause();
  84. }
  85. }
  86. }
  87. 复制代码

5,小结,SqlSession 还在别的地方有用到吗?

其实,Mybatis的一级缓存就是 SqlSession 级别的,只要SqlSession 不变,则默认缓存生效,也就是说,如下的代码,实际上只会查一次库的:

  1. XxxxxMapper xxxxxMapper = session.getMapper(xxxxxMapper.class);
  2. //对应的sql为: select id from test_info;
  3. xxxxxMapper.selectFromDb();
  4. xxxxxMapper.selectFromDb();
  5. xxxxxMapper.selectFromDb();
  6. 复制代码

实际上只会被执行一次,感兴趣的朋友们可以试试。

但是,在日常使用中,我们都是使用spring来管理Mapper,在执行selectFromDb 这个操作的时候,其实每次都会有一个新的SqlSession,所以,Mybatis的一级缓存是用不到的。

转载于:https://juejin.im/post/5b543ec06fb9a04fa8672c5b

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/158244
推荐阅读
相关标签
  

闽ICP备14008679号