在 spring 中, dao 层大多都是用 Mybatis, 那么
1,Mybatis 执行 sql 最重要的是什么?
在以前对 Mybatis 的源码解读中, 我们知道, Mybatis 利用了动态代理来做, 最后实现的类是 MapperProxy, 在最后执行具体的方法时, 实际上执行的是:
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- if (Object.class.equals(method.getDeclaringClass())) {
- try {
- return method.invoke(this, args);
- } catch (Throwable t) {
- throw ExceptionUtil.unwrapThrowable(t);
- }
- }
- final MapperMethod mapperMethod = cachedMapperMethod(method);
- return mapperMethod.execute(sqlSession, args);
- }
复制代码
最重要的一步:
mapperMethod.execute(sqlSession, args);
复制代码
这里的 sqlSession 其实是在 Spring 的配置时设置的 sqlSessionTemplate, 随便对其中的一个进行跟进: 可以在 sqlSessionTemplate 类中发现很好这样的方法, 用来执行具体的 sql, 如:
- @Override
- public <T> T selectOne(String statement, Object parameter) {
- return this.sqlSessionProxy.<T> selectOne(statement, parameter);
- }
复制代码
这一步就是最后执行的方法, 那么问题来了 sqlSessionProxy 到底是啥呢? 这又得回到最开始.
2, 使用 mybatis 连接 mysql 时一般都是需要注入 SqlSessionFactory,SqlSessionTemplate,PlatformTransactionManager.
其中 SqlSessionTemplate 是生成 sqlSession 的模版, 来看他的注入过程 (注解形式注入):
- @Bean
- public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
- return new SqlSessionTemplate(sqlSessionFactory);
- }
复制代码
在这个初始化过程中:
- /**
- * Constructs a Spring managed {@code SqlSession} with the given
- * {@code SqlSessionFactory} and {@code ExecutorType}.
- * A custom {@code SQLExceptionTranslator} can be provided as an
- * argument so any {@code PersistenceException} thrown by MyBatis
- * can be custom translated to a {@code RuntimeException}
- * The {@code SQLExceptionTranslator} can also be null and thus no
- * exception translation will be done and MyBatis exceptions will be
- * thrown
- *
- * @param sqlSessionFactory
- * @param executorType
- * @param exceptionTranslator
- */
- public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
- PersistenceExceptionTranslator exceptionTranslator) {
- notNull(sqlSessionFactory, "Property'sqlSessionFactory'is required");
- notNull(executorType, "Property'executorType'is required");
- this.sqlSessionFactory = sqlSessionFactory;
- this.executorType = executorType;
- this.exceptionTranslator = exceptionTranslator;
- this.sqlSessionProxy = (SqlSession) newProxyInstance(
- SqlSessionFactory.class.getClassLoader(),
- new Class[] { SqlSession.class },
- new SqlSessionInterceptor());
复制代码
}
最后一步比较重要, 用 java 动态代理生成了一个 sqlSessionFactory. 代理的类是:
- /**
- * Proxy needed to route MyBatis method calls to the proper SqlSession got
- * from Spring's Transaction Manager
- * It also unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to
- * pass a {@code PersistenceException} to the {@code PersistenceExceptionTranslator}.
- */
- private class SqlSessionInterceptor implements InvocationHandler {
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- 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) {
- // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
- closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
- sqlSession = null;
- Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
- if (translated != null) {
- unwrapped = translated;
- }
- }
- throw unwrapped;
- } finally {
- if (sqlSession != null) {
- closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
- }
- }
- }
复制代码
}
在 sqlSession 执行 sql 的时候就会用这个代理类. isSqlSessionTransactional 这个会判断是不是有 Transactional, 没有则直接提交. 如果有则不提交, 在最外层进行提交.
其中
getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,SqlSessionTemplate.this.executorType,SqlSessionTemplate.this.exceptionTranslator);
复制代码
这个方法用来获取 sqlSession. 具体实现如下:
- /**
- * Gets an SqlSession from Spring Transaction Manager or creates a new one if needed.
- * Tries to get a SqlSession out of current transaction. If there is not any, it creates a new one.
- * Then, it synchronizes the SqlSession with the transaction if Spring TX is active and
- * <code>SpringManagedTransactionFactory</code> is configured as a transaction manager.
- *
- * @param sessionFactory a MyBatis {@code SqlSessionFactory} to create new sessions
- * @param executorType The executor type of the SqlSession to create
- * @param exceptionTranslator Optional. Translates SqlSession.commit() exceptions to Spring exceptions.
- * @throws TransientDataAccessResourceException if a transaction is active and the
- * {@code SqlSessionFactory} is not using a {@code SpringManagedTransactionFactory}
- * @see SpringManagedTransactionFactory
- */
- public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
- notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
- notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
- SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
- SqlSession session = sessionHolder(executorType, holder);
- if (session != null) {
- return session;
- }
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug("Creating a new SqlSession");
- }
- session = sessionFactory.openSession(executorType);
- registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
- return session;
- }
复制代码
这个 sqlSession 的创建其实看他方法解释就够了,"从 Spring 事务管理器中获取一个 sqlsession, 如果没有, 则创建一个新的", 这句话的意思其实就是如果有事务, 则 sqlSession 用一个, 如果没有, 就给你个新的咯. 再通俗易懂一点:** 如果在事务里, 则 Spring 给你的 sqlSession 是一个, 否则, 每一个 sql 给你一个新的 sqlSession.** 这里生成的 sqlSession 其实就是 DefaultSqlSession 了. 后续可能仍然有代理, 如 Mybatis 分页插件等, 不在此次讨论的范围内.
3, 第二步的 sqlSession 一样不一样到底有什么影响?
在 2 中, 我们看到如果是事务, sqlSession 一样, 如果不是, 则每次都不一样, 且每次都会提交. 这是最重要的.
sqlSession, 顾名思义, 就是 sql 的一个会话, 在这个会话中发生的事不影响别的会话, 如果会话提交, 则生效, 不提交不生效.
来看下 sqlSession 这个接口的介绍.
/**
* The primary Java interface for working with MyBatis.
* Through this interface you can execute commands, get mappers and manage transactions.
* 为 Mybatis 工作最重要的 java 接口, 通过这个接口来执行命令, 获取 mapper 以及管理事务
* @author Clinton Begin
*/
复制代码
注释很明白了, 来一一看看怎么起的这些作用.
3.1, 执行命令.
在第一个小标题中 执行 sql 最重要的方法就是 this.sqlSessionProxy. selectOne(statement, parameter); 这个方法, 而在第二个小标题中我们看到是通过代理来执行的, 最后实际上没有事务则提交 sql. 这就是执行 sql 的基本动作了. 获取 sqlsession, 提交执行 Sql.
3.2, 获取 mapper.
在我们日常的代码中可能不会这么写, 但是实际上, 如果必要我们是可以这么做的, 如:
XxxxxMapper xxxxxMapper = session.getMapper(xxxxxMapper.class);
复制代码
一般情况下, 如果要这么做, 首先需要注入 sqlSessionFactory, 然后利用
sqlSessionFactory.openSession().
复制代码
即可获取 session.
####3.3, 事务管理 ####
上面我一直提到一点, sqlSession 那个代理类里有个操作, 判断这个是不是事务管理的 sqlSession, 如果是, 则不提交, 不是才提交, 这个就是事务管理了, 那么有个问题, 在哪里提交这个事务呢????
4, 事务从哪里拦截, 就从哪里提交
Spring 中, 如果一个方法被 @Transactional 注解标注, 在生效的情况下 (不生效的情况见我写动态代理的那篇博客), 则最终会被 TransactionInterceptor 这个类所代理, 执行的方法实际上是这样的:
- @Override
- public Object invoke(final MethodInvocation invocation) throws Throwable {
- // Work out the target class: may be {@code null}.
- // The TransactionAttributeSource should be passed the target class
- // as well as the method, which may be from an interface.
- Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
- // Adapt to TransactionAspectSupport's invokeWithinTransaction...
- return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
- @Override
- public Object proceedWithInvocation() throws Throwable {
- return invocation.proceed();
- }
- });
- }
复制代码
继续看 invokeWithinTransaction 这个方法:
- /**
- * General delegate for around-advice-based subclasses, delegating to several other template
- * methods on this class. Able to handle {@link CallbackPreferringPlatformTransactionManager}
- * as well as regular {@link PlatformTransactionManager} implementations.
- * @param method the Method being invoked
- * @param targetClass the target class that we're invoking the method on
- * @param invocation the callback to use for proceeding with the target invocation
- * @return the return value of the method, if any
- * @throws Throwable propagated from the target invocation
- */
- protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
- throws Throwable {
- // If the transaction attribute is null, the method is non-transactional.
- final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
- final PlatformTransactionManager tm = determineTransactionManager(txAttr);
- final String joinpointIdentification = methodIdentification(method, targetClass);
- // 基本上我们的事务管理器都不是一个 CallbackPreferringPlatformTransactionManager, 所以基本上都是会从这个地方进入, 下面的 else 情况暂不讨论.
- if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
- // Standard transaction demarcation with getTransaction and commit/rollback calls.
- // 获取具体的 TransactionInfo , 如果要用编程性事务, 则把这块的代码可以借鉴一下.
- TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
- Object retVal = null;
- try {
- // This is an around advice: Invoke the next interceptor in the chain.
- // This will normally result in a target object being invoked.
- retVal = invocation.proceedWithInvocation(); // 执行被 @Transactional 标注里面的具体方法.
- }
- catch (Throwable ex) {
- // target invocation exception
- // 异常情况下, 则直接完成了, 因为在 sqlsession 执行完每一条指令都没有提交事务, 所以表现出来的就是回滚事务.
- completeTransactionAfterThrowing(txInfo, ex);
- throw ex;
- }
- finally {
- cleanupTransactionInfo(txInfo);
- }
- // 正常执行完成的提交事务方法 跟进可以看到实际上执行的是:(编程性事务的提交)
- // ==============txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());===========
- commitTransactionAfterReturning(txInfo);
- return retVal;
- }
- // =======================else 情况不讨论 ================================
- else {
- // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
- try {
- Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr,
- new TransactionCallback<Object>() {
- @Override
- public Object doInTransaction(TransactionStatus status) {
- TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
- try {
- return invocation.proceedWithInvocation();
- }
- catch (Throwable ex) {
- if (txAttr.rollbackOn(ex)) {
- // A RuntimeException: will lead to a rollback.
- if (ex instanceof RuntimeException) {
- throw (RuntimeException) ex;
- }
- else {
- throw new ThrowableHolderException(ex);
- }
- }
- else {
- // A normal return value: will lead to a commit.
- return new ThrowableHolder(ex);
- }
- }
- finally {
- cleanupTransactionInfo(txInfo);
- }
- }
- });
- // Check result: It might indicate a Throwable to rethrow.
- if (result instanceof ThrowableHolder) {
- throw ((ThrowableHolder) result).getThrowable();
- }
- else {
- return result;
- }
- }
- catch (ThrowableHolderException ex) {
- throw ex.getCause();
- }
- }
- }
复制代码
5, 小结, SqlSession 还在别的地方有用到吗?
其实, Mybatis 的一级缓存就是 SqlSession 级别的, 只要 SqlSession 不变, 则默认缓存生效, 也就是说, 如下的代码, 实际上只会查一次库的:
- XxxxxMapper xxxxxMapper = session.getMapper(xxxxxMapper.class);
- // 对应的 sql 为: select id from test_info;
- xxxxxMapper.selectFromDb();
- xxxxxMapper.selectFromDb();
- xxxxxMapper.selectFromDb();
复制代码
实际上只会被执行一次, 感兴趣的朋友们可以试试.
但是, 在日常使用中, 我们都是使用 spring 来管理 Mapper, 在执行 selectFromDb 这个操作的时候, 其实每次都会有一个新的 SqlSession, 所以, Mybatis 的一级缓存是用不到的.
来源: https://juejin.im/post/5b543ec06fb9a04fa8672c5b