重要的概念:
理解mybatis和数据库连接池中间件如hikariCP的关系
简单来说,java的JDBC为与数据库交互操作提供了一套标准接口,常见的datasource,connection,preparedstatement等。hikariCP这类的框架负责实现这些接口。
但是单纯使用JDBC操作数据库不方便,效率也不高。所以mybatis是将JDBC操作给封装了。两者以datasource作为桥梁。mybatis底层拿到hikariCP的datasource执行JDBC标准的数据库操作,在mybatis源码里datasource作为成员变量存储在
org.apache.ibatis.mapping.Environment
这个类的对象里。
sqlSession
源码
public interface SqlSession extends Closeable {
<T> T selectOne(String statement);
<T> T selectOne(String statement, Object parameter);
<E> List<E> selectList(String statement);
<E> List<E> selectList(String statement, Object parameter);
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
<K, V> Map<K, V> selectMap(String statement, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
<T> Cursor<T> selectCursor(String statement);
<T> Cursor<T> selectCursor(String statement, Object parameter);
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
void select(String statement, Object parameter, ResultHandler handler);
void select(String statement, ResultHandler handler);
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
int insert(String statement);
int insert(String statement, Object parameter);
int update(String statement);
int update(String statement, Object parameter);
int delete(String statement);
int delete(String statement, Object parameter);
void commit();
void commit(boolean force);
void rollback();
void rollback(boolean force);
List<BatchResult> flushStatements();
@Override
void close();
void clearCache();
Configuration getConfiguration();
<T> T getMapper(Class<T> type);
Connection getConnection();
}
实现
概述
sqlSession是mybatis的一个sql会话,对外提供SQL操作的接口,但是真正执行的是其成员变量executor,后面会说(当使用mybatis等框架时,通过是调用mapper接口内定义的方法,但实际是最终还是会调用sqlSession来执行sql)。由于spring是加载bean是单例的,所以每执行一个sql会话,默认情况下都会创建一个sqlSession,以此来保证了sqlSession的线程安全。但是当启动一个事务的时候,或者说多个SQL在同一个事务内执行的时候,sqlSession会被复用。下面是具体一条SQL是如何被执行的。
以常用实现sqlSessionTemplate为例:
@Override
public <T> T selectOne(String statement) {
return this.sqlSessionProxy.selectOne(statement);
}
可以看到使用代理sqlSessionProxy执行。下面是代理的初始化:
private final SqlSession sqlSessionProxy;
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());
}
通过动态代理,去执行sqlSession接口里的方法,这里涉及了动态代理的知识,就不多说了,总之执行时会调用SqlSessionInterceptor的involve方法。下面是源码
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);
}
}
}
这里实际上是个拦截器,与spring的AOP原理类似,在调用对应的method(method.invoke)前后,加上其他的逻辑。大概就是先获取另外一个sqlSession的实现,去执行method,后面看这个SQL是不是开启了一个事务,如果不是,就直接commit,如果是,提交操作就由这个事务完成,最后再把sqlSession关掉。
第一步就是通过getSqlSession获取sqlSession的另一个实现,去真正执行method。进去看源码
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;
}
LOGGER.debug(() -> "Creating a new SqlSession");
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
逻辑是尝试获取sqlSessionHolder,再由sqlSessionHolder获取sqlSession,获取到了直接返回,这里就是之前说的在同一事务下,复用sqlSession的原理。sqlSessionHolder获取不到则通过openSession,新建一个sqlSession,再通过registerSessionHolder把刚刚新建的sqlSession放进一个sqlSessionHolder里,这样下一个sql执行的时候,就能直接拿到sqlSession了,但是注意这个方法其实不一定会生效,只有之前所说的同一个事务下,才会,后面会解释。下面一一看源码:
getResource
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
public static Object getResource(Object key) {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Object value = doGetResource(actualKey);
if (value != null && logger.isTraceEnabled()) {
logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
Thread.currentThread().getName() + "]");
}
return value;
}
private static Object doGetResource(Object actualKey) {
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
Object value = map.get(actualKey);
// Transparently remove ResourceHolder that was marked as void...
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
map.remove(actualKey);
// Remove entire ThreadLocal if empty...
if (map.isEmpty()) {
resources.remove();
}
value = null;
}
return value;
}
可以看到,核心逻辑就是从当前线程的threadLocal(resource变量)里面以当前的sessionFactory为key,获取sqlSessionHolder ,获取不到就返回null。返回null,就代表了当前sql就是独立的一个事务,不和其他sql共享会话,所以可以理解为一个事务对应一个SqlSession
再看后面的sessionHolder