mybatis的MappedStatement线程安全探究
作者:codecraft
序
本文主要研究一下mybatis MappedStatement
MappedStatement
org/apache/ibatis/mapping/MappedStatement.java
public final class MappedStatement { private String resource; private Configuration configuration; private String id; private Integer fetchSize; private Integer timeout; private StatementType statementType; private ResultSetType resultSetType; private SqlSource sqlSource; private Cache cache; private ParameterMap parameterMap; private List<ResultMap> resultMaps; private boolean flushCacheRequired; private boolean useCache; private boolean resultOrdered; private SqlCommandType sqlCommandType; private KeyGenerator keyGenerator; private String[] keyProperties; private String[] keyColumns; private boolean hasNestedResultMaps; private String databaseId; private Log statementLog; private LanguageDriver lang; private String[] resultSets; private boolean dirtySelect; //...... }
MappedStatement定义了SqlSource
MappedStatement.Builder
public static class Builder { private final MappedStatement mappedStatement = new MappedStatement(); public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) { mappedStatement.configuration = configuration; mappedStatement.id = id; mappedStatement.sqlSource = sqlSource; mappedStatement.statementType = StatementType.PREPARED; mappedStatement.resultSetType = ResultSetType.DEFAULT; mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<>()).build(); mappedStatement.resultMaps = new ArrayList<>(); mappedStatement.sqlCommandType = sqlCommandType; mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; String logId = id; if (configuration.getLogPrefix() != null) { logId = configuration.getLogPrefix() + id; } mappedStatement.statementLog = LogFactory.getLog(logId); mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance(); } //...... }
MappedStatement定义了一个Builder用于构造MappedStatement
MapperBuilderAssistant
org/apache/ibatis/builder/MapperBuilderAssistant.java
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, boolean dirtySelect) { if (unresolvedCacheRef) { throw new IncompleteElementException("Cache-ref not yet resolved"); } id = applyCurrentNamespace(id, false); 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(flushCache).useCache(useCache).cache(currentCache).dirtySelect(dirtySelect); ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id); if (statementParameterMap != null) { statementBuilder.parameterMap(statementParameterMap); } MappedStatement statement = statementBuilder.build(); configuration.addMappedStatement(statement); return statement; } //...... }
MapperBuilderAssistant定义了addMappedStatement来专门用于创建和往configuration添加MappedStatement
Configuration
org/apache/ibatis/session/Configuration.java
public class Configuration { protected Environment environment; protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>( "Mapped Statements collection") .conflictMessageProducer((savedValue, targetValue) -> ". please check " + savedValue.getResource() + " and " + targetValue.getResource()); //...... public void addMappedStatement(MappedStatement ms) { mappedStatements.put(ms.getId(), ms); } //...... }
Configuration则定义了以statementId为key,value为MappedStatement的StrictMap
Configuration.StrictMap
protected static class StrictMap<V> extends ConcurrentHashMap<String, V> { private static final long serialVersionUID = -4950446264854982944L; private final String name; private BiFunction<V, V, String> conflictMessageProducer; public StrictMap(String name, int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); this.name = name; } public StrictMap(String name, int initialCapacity) { super(initialCapacity); this.name = name; } //...... }
StrictMap继承了ConcurrentHashMap
SqlSource
org/apache/ibatis/mapping/SqlSource.java
/** * Represents the content of a mapped statement read from an XML file or an annotation. It creates the SQL that will be * passed to the database out of the input parameter received from the user. * * @author Clinton Begin */ public interface SqlSource { BoundSql getBoundSql(Object parameterObject); }
而SqlSource接口则定义了getBoundSql方法,根据入参parameterObject来获取BoundSql
SqlSource有DynamicSqlSource、ProviderSqlSource、RawSqlSource、StaticSqlSource这四种实现
BoundSql
org/apache/ibatis/mapping/BoundSql.java
public class BoundSql { private final String sql; private final List<ParameterMapping> parameterMappings; private final Object parameterObject; private final Map<String, Object> additionalParameters; private final MetaObject metaParameters; //...... }
BoundSql则代表了处理动态内容之后的SQL,该SQL可能还包含占位符
MappedStatement.getBoundSql
public BoundSql getBoundSql(Object parameterObject) { BoundSql boundSql = sqlSource.getBoundSql(parameterObject); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings == null || parameterMappings.isEmpty()) { boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject); } // check for nested result maps in parameter mappings (issue #30) for (ParameterMapping pm : boundSql.getParameterMappings()) { String rmId = pm.getResultMapId(); if (rmId != null) { ResultMap rm = configuration.getResultMap(rmId); if (rm != null) { hasNestedResultMaps |= rm.hasNestedResultMaps(); } } } return boundSql; }
MappedStatement的getBoundSql方法,在从sqlSource获取到的boundSql的parameterMappings为空时,会根据自己的ParameterMap的getParameterMappings来重新构建boundSql
DefaultSqlSession
org/apache/ibatis/session/defaults/DefaultSqlSession.java
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { try { MappedStatement ms = configuration.getMappedStatement(statement); return executor.query(ms, wrapCollection(parameter), rowBounds, handler); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
DefaultSqlSession的selectList方法则是根据statement从configuration获取到MappedStatement然后传递给executor
BaseExecutor
org/apache/ibatis/executor/BaseExecutor.java
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }
BaseExecutor的query从MappedStatement获取到了BoundSql,然后一路传递下去
小结
mybatis的MappedStatement是根据statementId从configuration获取的,这个是在启动的时候扫描注册上去的,因此如果通过反射改了MappedStatement会造成全局的影响,也可能有并发修改的问题;而BoundSql则是每次根据parameter从MappedStatement获取的,而MappedStatement则是从sqlSource获取到的BoundSql,因为每次入参都不同,所以这个BoundSql是每次执行都会new的,因而如果要在拦截器进行sql改动,改动BoundSql即可。
以上就是mybatis的MappedStatement线程安全探究的详细内容,更多关于mybatis MappedStatement线程安全的资料请关注脚本之家其它相关文章!