本文记录我对MyBati SqlSession执行流程分析相关过程及注意事项的理解(基于MyBatis 3.4版本)。
前言
MyBatis中SqlSession通常使用方式如下:
public class MyBatisUtil {
public static SqlSessionFactory getSqlSessionFactory() {
return sqlSessionFactory;
}
private static SqlSessionFactory sqlSessionFactory = null;
static {
String resource = "configuration.xml";
try {
// 通过SqlSessionFactoryBuilder来获取SqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(
Resources.getResourceAsStream(resource)
);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class MyBatisExample {
public static void main(String[] args) {
SqlSession sqlSession = null;
try {
// sqlSession默认关闭事务的自动提交
sqlSession = MyBatisUtil.getSqlSessionFactory().openSession(false);
// 实际是根据mapper文件创建了一个实现了UserDao接口的代理对象,JDK动态代理
UserDao userDao = sqlSession.getMapper(UserDao.class);
User user = userDao.getById(1);
System.out.println(user);
sqlSession.commit();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
}
接下来就以这个Demo为例,分析其内部实现机制。
SqlSession的获取
public class MyBatisExample {
public static void main(String[] args) {
SqlSession sqlSession = null;
try {
// sqlSession默认关闭事务的自动提交
sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
...
}
}
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建指定类型的被事务包裹的Executor
final Executor executor = configuration.newExecutor(tx, execType);
// 返回创建好的SqlSession,每个sqlSession对象对应一个Executor对象
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
}
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
// 每个sqlSession对象对应一个Executor对象,每个Executor对应一个Transaction类对象
private final Executor executor;
private final boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
}
SqlSession.getMapper实现
public class MyBatisExample {
public static void main(String[] args) {
SqlSession sqlSession = null;
try {
// sqlSession默认关闭事务的自动提交
sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
// 猜测应该是根据mapper文件创建了一个实现了UserDao接口的代理对象
UserDao userDao = sqlSession.getMapper(UserDao.class);
User user = userDao.getById(1);
System.out.println(user);
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
}
public class DefaultSqlSession implements SqlSession {
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
}
public class Configuration {
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
}
public class MapperRegistry {
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// key值为要被代理的接口类,value为其对应的MapperProxyFactory
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);
}
}
}
// MapperProxyFactory类对象在Configuration.addMapper方法中被创建,初始化时methodCache中并没有存储任何东西
// 一个mapperInterface对应一个MapperProxyFactory对象
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
// 每次调用session.getMapper都会返回一个新的mapperInterface接口的动态代理对象(MapperProxy类对象),但是多个对象中持有的sqlSession对象是相同的
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
// JDK动态代理创建mapperInterface的代理实现,invocationHandler实现是mapperProxy
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
}
获取的userDao是一个动态代理对象,对其方法的调用实际是从与其对应的MapperProxy中的invoke方法开始:
// 调用userDao.getById方法方法实际上是从MapperProxy中的invoke方法开始调用
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
// 一个mapperInterface下所有SqlSession对象共用一个methodCache,就是产生MapperProxy的MapperProxyFactory中的methodCache
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
// 被代理的方法来源于Object类
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 从缓存中获取MapperMethod对象,如果缓存中没有,则创建新的MapperMethod对象并添加到缓存中
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 调用mapperMethod.execute方法执行SQL语句
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
可见实际方法的反射调用是先找到与当前method对应的MapperMethod,然后调用mapperMethod.execute方法执行SQL语句:
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
// SqlCommand中并没有存储实际的sql语句信息,实际sql信息还存储在Configuration.mappedStatements代表的map中
public static class SqlCommand {
private final String name;
// 枚举类型,type可以为UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
private final SqlCommandType type;
// 将MappedStatement中相关信息封装为SqlCommand对象
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
public String getName() {
return name;
}
public SqlCommandType getType() {
return type;
}
// 从Configuration对象中获取mapper.xml中与当前方法对应的MappedStatement
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
String statementId = mapperInterface.getName() + "." + methodName;
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {
return null;
}
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}
public static class MethodSignature {
private final boolean returnsMany;
private final boolean returnsMap;
private final boolean returnsVoid;
private final boolean returnsCursor;
private final Class<?> returnType;
private final String mapKey;
private final Integer resultHandlerIndex;
private final Integer rowBoundsIndex;
// 使用paramNameResolver处理Mapper接口中定义的方法的参数列表,内部有一个map维护方法参数名称与参数索引之间的关系
private final ParamNameResolver paramNameResolver;
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class<?>) {
this.returnType = (Class<?>) resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
} else {
this.returnType = method.getReturnType();
}
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
this.returnsCursor = Cursor.class.equals(this.returnType);
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
...
}
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) { // 根据其sql command实际类型进行相应操作
case INSERT: {
// 把args参数数组与method中参数绑定起来
// method.convertArgsToSqlCommandParam返回值可能是一个map,存储了参数名称与传入的参数值之间的对应关系
Object param = method.convertArgsToSqlCommandParam(args);
// command.getName()返回结果是mapper.xml中namespace + sql语句中的id属性,比如为com.michael.mybatisStudy.dao.UserDao.getById
// 根据command.getName()返回结果可以找到其对应的MappedStatement对象,其中含有实际的sql语句信息
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
// rowCountResult方法的作用是将sqlsession中的insert等方法返回的int值转换为Mapper接口中对应方法的返回值
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
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进行实际的数据库操作
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
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;
}
}
接下来介绍实际对数据库进行操作的sqlSession.selectOne方法,看其具体是如何执行sql语句的:
// SqlSession的创建过程
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建指定类型的被事务包裹的Executor
final Executor executor = configuration.newExecutor(tx, execType);
// 返回创建好的SqlSession,每个sqlSession对象对应一个Executor对象
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
}
public class Configuration {
// 默认创建的是SimpleExecutor对象
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) { // 如果二级缓存功能开启的话
executor = new CachingExecutor(executor);// CachingExecutor是普通Executor的装饰器,在其上添加二级缓存功能
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
}
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
// 默认是SimpleExecutor,每个DefaultSqlSession对应一个Executor,每个Executor对应一个Transaction接口实现(比如ManagedTransaction)
private final Executor executor;
private final boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 获取对应的MappedStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
// Executor.NO_RESULT_HANDLER为null
// 如果二级缓存开启的话,这里调用的是CachingExecutor.query方法
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
}
public class CachingExecutor implements Executor {
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 创建在缓存中的key值
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
// 先从二级缓存中查找,看此sql语句之前执行的结果是否尚在缓存中
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 调用BaseExecutor的query(ms, parameterObject, rowBounds, resultHandler, key, boundSql)方法
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
}
public abstract class BaseExecutor implements Executor {
private static final Log log = LogFactory.getLog(BaseExecutor.class);
protected Transaction transaction; // BaseExecutor使用此对象进行事务控制
protected Executor wrapper;
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
// 内部通过HashMap实现的一级缓存
protected PerpetualCache localCache; // 每个sqlSession对应一个Executor,每个Executor对应一个一级缓存localCache
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;
protected int queryStack;
private boolean closed;
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 二级缓存查找完后在一级缓存中查找
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 缓存中查找不到的话就在数据库中查找
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 调用真正的Executor实现类实现查找功能,比如SimpleExecutor
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
}
// SimpleExecutor整个就相当于模板模式(封装Statement的相关操作) + 策略模式的组合
public class SimpleExecutor extends BaseExecutor {
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 实际返回的是RoutingStatementHandler对象,根据ms.StatementType选择具体的StatementHandler实现。默认是PreparedStatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
// statement处理好之后进行真正的查询操作
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
// 方法的作用是完成java.sql.Statement对象的创建和初始化
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
// 创建preparedStatement对象
stmt = handler.prepare(connection, transaction.getTimeout());
// 处理Statement对象中的占位符
handler.parameterize(stmt);
return stmt;
}
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) { // 添加log代理
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
}
public abstract class BaseStatementHandler implements StatementHandler {
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 实际调用的是PreparedStatementHandler.instantiateStatement(...)方法,返回的是一个PreparedStatement对象
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
}
public class PreparedStatementHandler extends BaseStatementHandler {
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() != null) {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.prepareStatement(sql);
}
}
@Override
public void parameterize(Statement statement) throws SQLException {
// 使用parameterHandler设置statement中sql参数
parameterHandler.setParameters((PreparedStatement) statement);
}
}
接下来来看真正的查询操作是怎么进行的:
public class SimpleExecutor extends BaseExecutor {
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 实际返回的是RoutingStatementHandler对象,根据ms.StatementType选择具体的StatementHandler实现。默认是PreparedStatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
// statement处理好之后进行真正的查询操作
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
}
public class RoutingStatementHandler implements StatementHandler {
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
return delegate.<E>query(statement, resultHandler);
}
}
public class PreparedStatementHandler extends BaseStatementHandler {
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 进行实际查询操作
ps.execute();
// 进行resultSet封装
return resultSetHandler.<E> handleResultSets(ps);
}
}
由上面的分析过程可以知道,查找过程为:二级缓存中查找 ——> 一级缓存中查找 ——> 数据库中查找。
具体sqlSession执行过程图示如下:
分析完上面的整个查找流程之后,我们再对sqlSession执行过程中的事务控制和缓存两个点做一下详细分析。
SqlSession执行过程中的事务控制
由上面的分析,我们知道每个SqlSession对象内部都持有一个与自己对应的Executor对象用来执行之后的查找操作,Executor进行实际的数据库操作的时候会通过其内部持有的transaction实例对象来获取一个数据库连接:
// SimpleExecutor整个就相当于模板模式(封装Statement的相关操作) + 策略模式的组合
public class SimpleExecutor extends BaseExecutor {
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 实际返回的是RoutingStatementHandler对象,根据ms.StatementType选择具体的StatementHandler实现。默认是PreparedStatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
// statement处理好之后进行真正的查询操作
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
// 方法的作用是完成java.sql.Statement对象的创建和初始化
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
// 创建preparedStatement对象
stmt = handler.prepare(connection, transaction.getTimeout());
// 处理Statement对象中的占位符
handler.parameterize(stmt);
return stmt;
}
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) { // 添加log代理
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
}
以JdbcTransaction为例看其getConnection()实现:
// SqlSession的创建过程
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
// 根据environment中的配置可获取自定义的TransactionFactory,从而可以创建自定义的Transaction实现
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建指定类型的被事务包裹的Executor
final Executor executor = configuration.newExecutor(tx, execType);
// 返回创建好的SqlSession,每个sqlSession对象对应一个Executor对象
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
}
public class JdbcTransaction implements Transaction {
protected Connection connection;
protected DataSource dataSource;
protected TransactionIsolationLevel level;
// MEMO: We are aware of the typo. See #941
protected boolean autoCommmit;
public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
dataSource = ds;
level = desiredLevel;
autoCommmit = desiredAutoCommit;
}
@Override
public Connection getConnection() throws SQLException {
// 之前没创建过Connection才创建新的Connection
if (connection == null) {
openConnection();
}
return connection;
}
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
// autoCommmit默认为false
setDesiredAutoCommit(autoCommmit);
}
protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
try {
if (connection.getAutoCommit() != desiredAutoCommit) {
if (log.isDebugEnabled()) {
log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]");
}
connection.setAutoCommit(desiredAutoCommit);
}
} catch (SQLException e) {
// Only a very poorly implemented driver would fail here,
// and there's not much we can do about that.
throw new TransactionException("Error configuring AutoCommit. "
+ "Your driver may not support getAutoCommit() or setAutoCommit(). "
+ "Requested setting: " + desiredAutoCommit + ". Cause: " + e, e);
}
}
}
可以看到每个SqlSession对象对应的Connection对象都是唯一的,之后使用SqlSession进行的所有对数据库的操作都是使用这个Connection对象来进行的,并且默认开启了事务控制,所以我们使用完sqlSession之后要记得进行commit()操作,否则我们进行的插入数据等操作是不生效的。
MyBatis自身对于Transaction接口的实现有两种:JdbcTransaction以及ManagedTransaction,分别JdbcTransactionFactory和ManagedTransactionFactory创建。
- 使用JDBC的事务管理机制,就是利用java.sql.Connection对象完成对事务的提交
- 使用MANAGED的事务管理机制,这种机制mybatis自身不会去实现事务管理,而是让程序的容器(JBOSS,WebLogic)来实现对事务的管理
在实践中,MyBatis通常会与Spring集成使用,数据库的事务交由Spring管理,具体实现在之后的博客中会介绍。
SqlSession执行过程中的缓存使用
上面已经说过,查找过程是是先在二级缓存,之后是一级缓存,最后是实际数据库,mapper.xml中对应的namespace对应一个二级缓存对象,多个mapper.xml可以对应同一个二级缓存对象,具体如下图:
二级缓存对象的创建
二级缓存对象的创建是在mapper.xml解析cache节点的过程中创建MapperBuilderAssistant对象的过程时创建的:
public class XMLMapperBuilder extends BaseBuilder {
private final MapperBuilderAssistant builderAssistant;
// mybaits的二级缓存是mapper范围级别,除了在SqlMapConfig.xml设置二级缓存的总开关,还要在具体的mapper.xml中开启二级缓存。
// 负责解析<cache>节点
private void cacheElement(XNode context) throws Exception {
if (context != null) {
...
// 通过MapperBuilderAssistant创建cache对象,并添加到Configuration.caches集合中保存
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
}
每个mapper.xml对应一个XMLMapperBuilder对象,每个XMLMapperBuilder对象对应一个builderAssistant对象,每个builderAssistant对象对应一个Cache类对象:
public class MapperBuilderAssistant extends BaseBuilder {
private Cache currentCache;
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
// 通过CacheBuilder类创建Cache,默认实现类是PerpetualCache
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
configuration.addCache(cache);
currentCache = cache;
return cache;
}
}
之后在此mapper.xml的创建每个MappedStatement的过程中都将此currentCache引用传入,这样就可以通过MappedStatement.getCache获取此Cache二级缓存对象:
public class XMLStatementBuilder extends BaseBuilder {
private final MapperBuilderAssistant builderAssistant;
private final XNode context;
private final String requiredDatabaseId;
// 参数中的XNode是<select>、<update>、<delete>、<insert>其中之一
public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context, String databaseId) {
super(configuration);
this.builderAssistant = builderAssistant;
this.context = context;
this.requiredDatabaseId = databaseId;
}
public void parseStatementNode() {
...
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
}
public class MapperBuilderAssistant extends BaseBuilder {
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache); // 引用了每个namepspace对应的唯一的Cache二级缓存对象
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
// 把sql(增删改查)节点封装成相应的MappedStatement
MappedStatement statement = statementBuilder.build();
// 将MappedStatement保存到全局configuration对象中
configuration.addMappedStatement(statement);
return statement;
}
}
二级缓存的查找
对于缓存的查找过程是通过CachingExecutor实现的,CachingExecutor是一个装饰器,装饰了实际的Executor,为其添加了缓存功能:
public class CachingExecutor implements Executor {
private final Executor delegate;
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
public CachingExecutor(Executor delegate) {
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 创建在缓存中的key值
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
// 先从二级缓存中查找,看此sql语句之前执行的结果是否尚在缓存中
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 调用BaseExecutor的query(ms, parameterObject, rowBounds, resultHandler, key, boundSql)方法
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
}
可见对于缓存的查找是通过TransactionalCacheManager.getObject方法实现的,向缓存中存数据是通过putObject方法先存入到TransactionalCache对象中的:
public class TransactionalCacheManager {
// transactionalCaches中的key值默认是PerpetualCache类对象,可能被 其它类装饰
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value);
}
private TransactionalCache getTransactionalCache(Cache cache) {
TransactionalCache txCache = transactionalCaches.get(cache);
if (txCache == null) {
txCache = new TransactionalCache(cache);
transactionalCaches.put(cache, txCache);
}
return txCache;
}
public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();
}
}
public void rollback() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.rollback();
}
}
}
public class TransactionalCache implements Cache {
// 保存事务提交之前缓存的数据
private final Map<Object, Object> entriesToAddOnCommit;
}
为什么这里还要使用TransactionalCacheManager组件和TransactionalCache组件呢,而不是直接将数据存入二级缓存Cache对象中呢?
是由于事务控制的原因。通常情况下每个线程使用一个SqlSession对象完成对数据库的操作,多个SqlSession可能同时操作同一个二级缓存对象,如果直接将某个SqlSession更改后未提交的查询结果存入缓存中的话,就有可能有另一个线程读取到缓存中的脏数据。比如事务T1中添加了记录A,之后查询A记录,事务尚未提交;之后事务T2查询记录A,如果事务T1在尚未提交之前就将A记录的查询结果放入二级缓存中的话,事务T2就会查询到二级缓存中的事务T1尚未提交的脏数据,这显然不是我们期望的结果。
所以在一个SqlSession的某个事务尚未提交之前的查询结果先缓存在transactionalCaches中的TransactionalCache对象的entriesToAddOnCommit的Map中,在事务提交的时候再将此结果放置在二级缓存当中。
二级缓存的使用时注意事项
- 只能在【只有单表操作】的表上使用缓存:不只是要保证这个表在整个系统中只有单表操作,而且和该表有关的全部操作必须全部在一个namespace下。
- 在可以保证查询远远大于insert,update,delete操作的情况下使用缓存,这一点需要保证在1的前提下才可以!
避免使用二级缓存
二级缓存带来的好处可能比不上他所隐藏的危害。
缓存是以namespace为单位的,不同namespace下的操作互不影响。
insert,update,delete操作会清空所在namespace下的全部缓存。
通常使用MyBatis Generator生成的代码中,都是各个表独立的,每个表都有自己的namespace。
为什么避免使用二级缓存?
在符合【Cache使用时的注意事项】的要求时,并没有什么危害。其他情况就会有很多危害了。比如:
- 针对一个表的某些操作不在他独立的namespace下进行。
例如在UserMapper.xml中有大多数针对user表的操作。但是在一个XXXMapper.xml中,还有针对user单表的操作。 这会导致user在两个命名空间下的数据不一致。如果在UserMapper.xml中做了刷新缓存的操作,在XXXMapper.xml中缓存仍然有效,如果有针对user的单表查询,使用缓存的结果可能会不正确。 更危险的情况是在XXXMapper.xml做了insert,update,delete操作时,会导致UserMapper.xml中的各种操作充满未知和风险。
- 多表操作一定不能使用缓存
首先不管多表操作写到那个namespace下,都会存在某个表不在这个namespace下的情况。
例如两个表:role和user_role,如果我想查询出某个用户的全部角色role,就一定会涉及到多表的操作。
<select id="selectUserRoles" resultType="UserRoleVO">
select * from user_role a,role b where a.roleid = b.roleid and a.userid = #{userid}
</select>
像上面这个查询,应该写到那个xml中呢??
不管是写到RoleMapper.xml还是UserRoleMapper.xml,或者是一个独立的XxxMapper.xml中。如果使用了二级缓存,都会导致上面这个查询结果可能不正确。
如果正好修改了这个用户的角色,上面这个查询使用缓存的时候结果就是错的。
如果让他们都使用同一个namespace(通过
对于上述的问题的解决方案可以是通过拦截器判断执行的Sql语句涉及到哪些表,然后将相关表的缓存自动清空,但这样做会降低使用缓存的效率。
SqlSessionManager介绍
SqlSessionManager同时实现了SqlSession和SqlSessionFactory接口,也就同时提供了SqlSessionFactory创建SqlSession对象以及SqlSession操作数据库的功能。
SqlSessionManager作为SqlSessionFactory使用时与普通sqlSessionFactory没有什么区别:
public class MyBatisExample {
private static String resource = "configuration.xml";
public static void main(String[] args) throws IOException {
SqlSession sqlSession = null;
try {
// 使用sqlSessionManager获取SqlSession
SqlSessionManager sqlSessionManager = SqlSessionManager.newInstance(Resources.getResourceAsStream(resource));
sqlSession = sqlSessionManager.openSession(false);
// 猜测应该是根据mapper文件创建了一个实现了UserDao接口的代理对象
UserDao userDao = sqlSession.getMapper(UserDao.class);
User user = userDao.getById(1);
System.out.println(user);
User user1 = new User("heihei", 14);
int result = userDao.insertUser(user1);
System.out.println("result: " + result + " " + user1.getId());
sqlSession.commit();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
}
public class SqlSessionManager implements SqlSessionFactory, SqlSession {
private final SqlSessionFactory sqlSessionFactory;
private final SqlSession sqlSessionProxy;
private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<SqlSession>();
private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[]{SqlSession.class},
new SqlSessionInterceptor());
}
// 使用SqlSessionManager.openSession方法获取SqlSession,与普通sqlSessionFactory没有什么区别
@Override
public SqlSession openSession() {
return sqlSessionFactory.openSession();
}
}
SqlSessionManager作为SqlSession使用时有两种方式,使用哪种模式取决于在调用之前是否调用了SqlSessionManager.startManagedSession()方法,下面来看看这个方法的作用:
public class SqlSessionManager implements SqlSessionFactory, SqlSession {
private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<SqlSession>();
public void startManagedSession() {
this.localSqlSession.set(openSession());
}
}
可见是新生成了一个数据库连接,之后将其放在了一个ThreadLocal变量中,这样做的目的是为了记录与当前线程绑定的SqlSession对象,供当前线程循环使用,从而避免在同一线程多次创建SqlSession对象带来的性能损失。如果在使用之前不调用SqlSessionManager.startManagedSession()方法,就会出现同一线程每次通过SqlSessionManager对象访问数据库时,都会创建新的DefaultSqlSession对象完成数据库操作:
public class SqlSessionManager implements SqlSessionFactory, SqlSession {
private final SqlSessionFactory sqlSessionFactory;
private final SqlSession sqlSessionProxy;
private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<SqlSession>();
private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[]{SqlSession.class},
new SqlSessionInterceptor());
}
@Override
public <T> T selectOne(String statement) {
// 使用动态代理对象进行实际操作
return sqlSessionProxy.<T> selectOne(statement);
}
}
sqlSessionProxy是一个动态代理对象,对它的方法的调用会转到SqlSessionInterceptor.invoke方法中去:
public class SqlSessionManager implements SqlSessionFactory, SqlSession {
private class SqlSessionInterceptor implements InvocationHandler {
public SqlSessionInterceptor() {
// Prevent Synthetic Access
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 尝试从localSqlSession中获取之前通过startManagedSession方法设置好的SqlSession对象
final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
if (sqlSession != null) {
try {
// 反射调用sqlSession的方法
return method.invoke(sqlSession, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
} else {
// 如果之前没有通过startManagedSession方法设置sqlSession对象,则每次
// 通过SqlSessionManager对象访问数据库时,都会创建新的DefaultSqlSession对象
final SqlSession autoSqlSession = openSession();
try {
final Object result = method.invoke(autoSqlSession, args);
autoSqlSession.commit();
return result;
} catch (Throwable t) {
autoSqlSession.rollback();
throw ExceptionUtil.unwrapThrowable(t);
} finally {
autoSqlSession.close();
}
}
}
}
}
通过上面代码分析可以看出,SqlSessionManager的作为SqlSession使用的优势在于可以记录与当前线程绑定的SqlSession对象,劣势在于对于实际SqlSession对象方法的调用是通过反射进行的,对效率会有所影响。
SqlSessionFactory与SqlSession线程安全
SqlSessionFactory.openSession()方法我觉得是线程安全的,但创建好的SqlSession并不是是线程安全的,理由之一是每个SqlSession底层对应一个数据库的Connection连接,实际对数据库的操作都是使用这个Connection连接进行的,java.sql.Connection对象不是线程安全的,所以SqlSession对象不是线程安全的,最好在单线程中使用它。
关于MyBatis的SqlSession的执行流程就介绍到这里。
(完)
参考文章:Mybatis - 二级缓存的利弊