书接上回
本文主要对上一篇关于 springboot和 mybatis集成中留下的几个问题通过梳理源码的方式进行解答,主要包括
Mybatis-spring 是如何进行mapperInterface 动态代理生成的 ?
Spring 为什么要 创建一个 SqlSessionTemplate ?
纠正一个错误
上一篇中提到 mapper 接口是在 org.mybatis.spring.mapper.MapperFactoryBean#checkDaoConfig 方法中通过 configuration.addMapper(this.mapperInterface); 将 mapperInterface 添加到 Configuration中的 mapperRegistry 中的,其实是有误的,通过debug调试发现,在checkDaoConfig 方法中,configuration.hasMapper(this.mapperInterface) 已经存在这个mapperInterface了,实际对mapperInterface的处理是在 SqlSessionFactoryBean 的 org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory 方法中进行的,而这里是通过 MybatisAutoConfiguration 配置 sqlSessionFactory 组件时触发的
可以看到由于一般会在application.properties 相关配置文件中添加 如下配置,所以 SqlSessionFactoryBean 在创建 SqlSessionFactory 的时候会对mapper的xml文件进行解析
mybatis.mapper-locations=classpath:mapper/*.xml
之后会进入到 org.apache.ibatis.builder.xml.XMLMapperBuilder#parse 的接口中对 Xml的mapper文件进行解析
这个parse方法主要做了两个事情
configurationElement(parser.evalNode("/mapper"));
将mapper.xml 中的 mapper 标签中的内容,也就是定义的各种 select 或者 update 标签的xml标签块,解析成 MappedStatement ,然后存储在 org.apache.ibatis.session.Configuration#mappedStatements 中,供后续获取使用,最终解析的结果如下:
bindMapperForNamespace()
之后调用就是调用 Configuratation 中的MapperRegistry 通过org.apache.ibatis.binding.MapperRegistry#addMapper 一方面将 mapperInterface的class为key ,以 new MapperProxyFactory<>(type) 为value放入 mapper注册中心的 knownMappers map中
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<>(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);
}
}
}
}
下面详细看下 org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse 方法主要做了什么
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
for (Method method : type.getMethods()) {
if (!canHaveStatement(method)) {
continue;
}
if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
&& method.getAnnotation(ResultMap.class) == null) {
parseResultMap(method);
}
try {
parseStatement(method);
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
重点看下 loadXmlResource(); 方法
由于 “namespace:com.future.common.mapper.UserMapper” 这个namespace实际上已经在之前解析mapper.xml的时候,已经解析过了,所以这里其实会跳过
private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
if (!configuration.isResourceLoaded( "namespace:" + type.getName())) {
String xmlResource = type.getName().replace( '.' , '/' ) + ".xml" ;
// #1347
InputStream inputStream = type.getResourceAsStream( "/" + xmlResource);
if (inputStream == null) {
// Search XML mapper that is not in the module but in the classpath.
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e2) {
// ignore, resource is not required
}
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource,
configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
之后会走到 org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parseStatement 方法,这个方法其实主要就是查看 MapperInterface中的 method 上是否有 一下注解 ,也就是使用的是注解类型的sql,所以显然也是没有的,下面的ifPresent的逻辑也会跳过,所以最后的解析 mapperInterface 中method 为MappedStatement ,并将其放入 org.apache.ibatis.session.Configuration#mappedStatements 也不会执行了
private static final Set<Class<? extends Annotation>> statementAnnotationTypes = Stream
.of(Select.class, Update.class, Insert.class, Delete.class, SelectProvider.class, UpdateProvider.class,
InsertProvider.class, DeleteProvider.class)
.collect(Collectors.toSet());
void parseStatement(Method method) {
final Class<?> parameterTypeClass = getParameterType(method);
final LanguageDriver languageDriver = getLanguageDriver(method);
getAnnotationWrapper(method, true, statementAnnotationTypes).ifPresent(statementAnnotation -> {
final SqlSource sqlSource = buildSqlSource(statementAnnotation.getAnnotation(), parameterTypeClass,
languageDriver, method);
final SqlCommandType sqlCommandType = statementAnnotation.getSqlCommandType();
final Options options = getAnnotationWrapper(method, false, Options.class).map(x -> (Options) x.getAnnotation())
.orElse(null);
final String mappedStatementId = type.getName() + "." + method.getName();
//............................ 省略
assistant.addMappedStatement(mappedStatementId, sqlSource, statementType, sqlCommandType, fetchSize, timeout,
// ParameterMapID
null, parameterTypeClass, resultMapId, getReturnType(method, type), resultSetType, flushCache, useCache,
// TODO gcode issue #577
false, keyGenerator, keyProperty, keyColumn, statementAnnotation.getDatabaseId(), languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null, statementAnnotation.isDirtySelect());
});
}
综上,所以对于使用了xml格式的写sql的方式,整个 bindMapperForNamespace 方法其实只是把 MapperInterface 放到了 Configuration 中 Mapper的注册中心里了,其他的逻辑,比如加载 MapperInterface对应的mapper的xml文件被跳过了,解析MapperInterface class中 method上是否有注解类型的sql这个逻辑也被跳过了
小总结
以上就是 spring通过调用在 SqlSessionFactoryBean中根据用户传入的xml的path ,手动构造了一个 XMLMapperBuilder ,之后通过 xmlMapperBuilder.parse(); 方法对xml 进行解析,并将xml中的各种定义的sql标签解析为MappedStatement 存入Configuration 中。之后对 xml中namespace对应的class进行注册到 Configuration 中的mapper的注册中心中,同时会尝试反向查找class对应的xml文件进行再次解析(前面就是从xml解析过来的,所以这里不会再次触发解析)。
从这里也可以看出,mybatis会从正反两个方向,通过xml找到namespace对应的mapperinterface class, 以及 MapperFactoryBean 中通过 MapperInterface class 反向查找对应的xml文件进行解析。
Mapper正向查询视角
前面已经通过解析,将xml中各种标签语句解析好了,就等正向的使用了,下面通过一个实际查询的例子来看Mapper正向查询的例子。从之前的文章可知,一个 MapperInterface就对应一个 MapperFactoryBean ,所以当代码中注入 一个 @Autowaire UserMapper userMapper时, 实际注入的类型是通过 org.mybatis.spring.mapper.MapperFactoryBean#getObject 获取到的实例,此处的 getSqlSession() 获取到是 SqlSessionTemplate
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
可以看到在getMapper时,把自己也就是 SqlSessionTemplate 传过去了 ,对比mybatis的默认实现 DefaultSqlSession ,都是传递的this,但是传递的确实不同的 SqlSession的实现,这也说明了Spring 确实要通过 SqlSessionTemplate 解决一些问题, 不然直接使用 DefaultSqlSession 就可以了,至于要解决什么问题,后面会在 SqlSessionTemplate 部分详细讲解下,这里继续梳理下 mybatis查询的正向流程
getmapper最终走到如下的方法 org.apache.ibatis.binding.MapperRegistry#getMapper
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 newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
由于之前UserMapper class已经添加过了,所以这里会通过 return mapperProxyFactory.newInstance(sqlSession); 再次创建 一个 对 mapperInterface 的代理对象 MapperProxy,后续 mapperInterface 中所有方法的执行都会被 MapperProxy的 invoke 方法拦截
下面看下 MapperProxy 中的逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 先调用 cachedInvoker
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return MapUtil.computeIfAbsent(methodCache, method, m -> {
if (!m.isDefault()) {
// 此处会创建一个 PlainMethodInvoker ,内部组合了 MapperMethod
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
this.mapperMethod = mapperMethod;
}
//最终还是通过 MapperMethod来执行具体的逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
}
可以看到构造一个Invoker,然后通过执行 Invoker的 invoke来执行具体的调用 ,注意这里又把 sqlSession 传入了,此处的SqlSession 是Spring创建的 SqlSessionTemplate ,可以看到下面的 execute 方法中最终还是通过 SqlSession的接口执行查询的。所以最终还是要经过 SqlSessionTemplate
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
// .......................... 省略一些代码
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);
// 可以看到这里最终 还是通过调用 sqlSession的查询方法的,而此处的sqlSession是SqlSessionTemplate
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
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;
}
SqlSessionTemplate
前面铺垫了很多关于 mybatis 最终是如何 关联上 Spring 创建的SqlSessionTemplate 的,下面开始详细看下SqlSessionTemplate 的生命周期,以及为什么Spring 在有了 Mybatis 默认的SqlSession的实现的情况下,依然还要创建这个类
SqlSessionTemplate 的生命周期
创建
SqlSessionTemplate 是在 org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration#sqlSessionTemplate 进行自动配置的 , 通过组合SqlSessionFactory 创建了一个 SqlSessionTemplate ,并最终通过构造函数的时候,在内部就默认创建一个SqlSession 的代理类,SqlSessionTemplate本身就实现了 SqlSession,然后在其实现方法中委托给内部创建的 SqlSession 的代理类 去执行操作,所以看了看出 SqlSessionTemplate本身的设计是结合了 适配器 以及门面模式的 。
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
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());
}
看下 SqlSessionTemplate 内部创建的动态代理类
如下,所以调用SqlSession接口的方法都会走到这个动态代理里,所以搞懂这个方法相比mybatis默认的DefaultSqlSession 多做了哪些操作,基本上就能看出SqlSessionTemplate的目的了
invoke方法一上来就是先通过一个getSqlSession方法获取一个 SqlSession ,而这个SqlSession 其实也就是mybatis中默认的DefaultSqlSession, 最终的执行还是要通过委托给 DefaultSqlSession来走mybatis原先那一套,但是在获取过程中,spring做了一些操作,下面详细看下getSqlSession 方法
@Override
public <T> T selectOne(String statement) {
return this.sqlSessionProxy.selectOne(statement);
}
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);
}
}
}
}
org.mybatis.spring.SqlSessionUtils#getSqlSession
可以看到 先通过spring的事务管理器中(ThreadLocal)中是否存在SqlSessionFactory对应的SessionHolder,
如果有,就直接返回了。如果没有,说明当前没有事务或者事务还没执行过mybatis的操作流程,此时则会通过SqlSessionFactory 创建一个Sqlsession 。创建完成之后,如果当前存在事务环境,则把<SqlSessionFactory, SqlSessionHolder> 放入ThreadLocal中,之后注册一个 SqlSessionSynchronization 同步器 ,用来被Spring事务执行的时候进行回调,用来管理 SqlSession 的生命周期
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
//通过Spring的事务管理器看下当前ThreadLocal中是否存在以SessionFactory为key的 value(也就是SqlSessionHolder)
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
LOGGER.debug(() -> "Creating a new SqlSession" );
//如果上面没有获取到,则通过工厂创建一个SqlSession,这里就是Mybatis的原生方法了
session = sessionFactory.openSession(executorType);
//之后把获取到的 sqlSession 注册到 Spring的事务管理器上下文中
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
SqlSessionHolder holder;
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Environment environment = sessionFactory.getConfiguration().getEnvironment();
if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]" );
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
TransactionSynchronizationManager
.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
holder.setSynchronizedWithTransaction(true);
holder.requested();
} else {
if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
LOGGER.debug(() -> "SqlSession [" + session
+ "] was not registered for synchronization because DataSource is not transactional" );
} else {
throw new TransientDataAccessResourceException(
"SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization" );
}
}
} else {
LOGGER.debug(() -> "SqlSession [" + session
+ "] was not registered for synchronization because synchronization is not active" );
}
}
总结下:
从上面也可以看出,SqlSessonTemplate 主要就是在通过代理的方式,在SqlSession的创建过程中,来和Spring的事务进行同步,保持在Spring一个事务的上下文中,对SqlSession的生命周期进行管理。这里的管理不仅仅是数据库连接的管理,而是SqlSession实例的管理。
其实如果只是需要保证Mybatis执行时和Spring事务使用的是同一个Connection的话,Spring-mybatis是没有必要对<SqlSessionFactory,SqlSession> 进行管理的。因为在Mybatis最终获取Connection的时候,和SqlSession是没有必然的关系的,比如在
org.apache.ibatis.executor.SimpleExecutor#prepareStatement 中 ,通过 Connection connection = getConnection(statementLog); 获取Connection ,
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
}
return connection;
}
最终会走到 org.mybatis.spring.transaction.SpringManagedTransaction#openConnection 方法中 ,可以看到是通过 DataSourceUtils.getConnection(this.dataSource) 获取的 ,而这个方法底层可以看到还是通过ThreadLocal获取以datasource为key的 Connection, 所以只要事务管理器配置的数据源和mybatis使用的数据源是同一个,这里获取到的就会是同一个Connection.
private void openConnection() throws SQLException {
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
LOGGER.debug(() -> "JDBC Connection [" + this.connection + "] will"
+ (this.isConnectionTransactional ? " " : " not " ) + "be managed by Spring" );
}
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified" );
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug( "Fetching resumed JDBC Connection from DataSource" );
conHolder.setConnection(fetchConnection(dataSource));
}
return conHolder.getConnection();
}
// Else we either got no holder or an empty thread-bound holder here.
// ....................... 省略若干 ...................
return con;
}
所以 SqlSessionTemplate 中代理方法中创建 <SqlSessionFactory , SqlSessionHolder> 以及 添加 SqlSessionSynchronization 到事务管理器的ThreadLocal 中的主要目的并不是为了保证事务上下文中Spring和Mybatis使用的Connection是同一个, 而是为了在事务的生命周期中,管理Mybatis的SqlSession对象 .
因为在mybatis中,SqlsessionFactory是一个线程安全的,可以是单例的,主要用来创建SqlSession的,所以Springboot 也是在配置类中申明了一个单例的 SqlsessionFactory 配置。
但是 mybatis 中的SqlSession 不是线程安全的,是一个有状态的对象。以前手动通过mybatis操作数据的时候,比如 ,在同一个线程的同一个方法中,也是会存在一个或者多个mapper的多个数据库操作在一个SqlSession中执行,不可能每一次执行一次DB查询操作都创建一个SqlSession实例,虽然可以这么搞 (DB的Connection是通过Spring的事务管理器获取的,同一个事务方法中),但是会创建大量的SqlSession,所以在 Spring事务开启的时候 ,Spring通过ThreadLocal 的方式从获取SqlSessionFactory 绑定的 SqlSession,就达到了Session 复用的目的。
同时在事务场景下,通过自定义的 SqlSessionSynchronization ,可以在spring事务挂起,提交,回滚等 回调****SqlSessionSynchronization ,从而管理SqlSession
但是在 非事务场景,mapper的每一次操作, 比如mapper的一次查询,再次执行同样的查询,都会创建不同的SqlSession ,这是和手动操作Mybatis有区别的地方,比如下面使用原生mybatis 在一次Session中操作了多次Mapper的操作,从而可以复用SqlSession,这点是有区别的,主要注意下
原生Mybatis操作数据库
// 单次操作(查询)
try (SqlSession session = sqlSessionFactory.openSession()) {
User user = session.getMapper(UserMapper.class).selectById(1L);
System.out.println(user);
}
// 多操作
try (SqlSession session = sqlSessionFactory.openSession(true)) { // false = 手动提交
UserMapper mapper = session.getMapper(UserMapper.class);
mapper.insert(user1);
mapper.update(user2);
} catch (Exception e) {
throw e;
}
一个容易被忽略的点
上面的解析中 , 涉及到了Spring的事务管理器 TransactionSynchronizationManager 组件, 这些内容会在下一篇 Spring 的事务解析中详细介绍下。而且从上面的解析中提到一点,就是 mybaits 获取 DB的 Connection 也是从ThreadLocal 中获取的 ,传递方向是从Spring -> mybatis ,
但是细想一下,这是不是就意味着 Spring是事务方法一进来就提前创建了 Connection 呢? 如果后续方法中如果有分支逻辑提前退出了方法,没有执行任何的db操作,那是不是意味着创建出来的 Connection 没有任何使用的地方 ?
关于这些问题会在下一篇关于Spring 事务的解析中去解答以及给出解决方案
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);