Статья впервые опубликована вblog.CSDN.net/Dujinlong 1…
Весной большинство слоев дао используют 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 здесь на самом деле является sqlSessionTemplate, установленным во время настройки Spring.Выполните один из них по желанию: вы можете найти такой хороший метод в классе 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 на самом деле достаточно, чтобы увидеть объяснение его метода: «получить sqlsession из менеджера транзакций Spring, если нет, создать новый», смысл этого предложения в том, что если есть транзакция, используйте один 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); Этот метод, а во втором подзаголовке мы видим, что он выполняется через прокси, и, наконец, нет реальных транзакций submit sql . Это основное действие по выполнению sql. Получите sqlsession, отправьте на выполнение Sql.
3.2, возьми маппер.
В нашем повседневном коде это может быть не написано так, но на самом деле мы можем это сделать при необходимости, например:
XxxxxMapper xxxxxMapper = session.getMapper(xxxxxMapper.class);
В общем, если вы хотите это сделать, вам сначала нужно внедрить sqlSessionFactory, а затем использовать
sqlSessionFactory.openSession()。
Вы можете получить сессию.
####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 не используется.