mybatis的MappedStatement線程安全探究
序
本文主要研究一下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定義了一個(gè)Builder用于構(gòu)造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來專門用于創(chuàng)建和往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方法,根據(jù)入?yún)arameterObject來獲取BoundSql
SqlSource有DynamicSqlSource、ProviderSqlSource、RawSqlSource、StaticSqlSource這四種實(shí)現(xiàn)
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則代表了處理動(dòng)態(tài)內(nèi)容之后的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為空時(shí),會(huì)根據(jù)自己的ParameterMap的getParameterMappings來重新構(gòu)建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方法則是根據(jù)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,然后一路傳遞下去
小結(jié)
mybatis的MappedStatement是根據(jù)statementId從configuration獲取的,這個(gè)是在啟動(dòng)的時(shí)候掃描注冊(cè)上去的,因此如果通過反射改了MappedStatement會(huì)造成全局的影響,也可能有并發(fā)修改的問題;而BoundSql則是每次根據(jù)parameter從MappedStatement獲取的,而MappedStatement則是從sqlSource獲取到的BoundSql,因?yàn)槊看稳雲(yún)⒍疾煌?,所以這個(gè)BoundSql是每次執(zhí)行都會(huì)new的,因而如果要在攔截器進(jìn)行sql改動(dòng),改動(dòng)BoundSql即可。
以上就是mybatis的MappedStatement線程安全探究的詳細(xì)內(nèi)容,更多關(guān)于mybatis MappedStatement線程安全的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java實(shí)現(xiàn)將Markdown轉(zhuǎn)換為純文本
這篇文章主要為大家詳細(xì)介紹了兩種在 Java 中實(shí)現(xiàn) Markdown 轉(zhuǎn)純文本的主流方法,文中的示例代碼講解詳細(xì),大家可以根據(jù)需求選擇適合的方案2025-03-03
Spring AOP方法內(nèi)部調(diào)用不生效的解決方案
最近有個(gè)需求,統(tǒng)計(jì)某個(gè)方法的調(diào)用次數(shù),開始使用 Spring AOP 實(shí)現(xiàn),后來發(fā)現(xiàn)當(dāng)方法被內(nèi)部調(diào)用時(shí),切面邏輯將不會(huì)生效,所以本文就給大家介紹了Spring AOP方法內(nèi)部調(diào)用不生效的解決方案,需要的朋友可以參考下2025-01-01
SpringBoot定制JSON響應(yīng)數(shù)據(jù)返回的示例代碼
@JsonView 是 Jackson 庫中的一個(gè)注解,它允許你定義哪些屬性應(yīng)該被序列化到 JSON 中,基于不同的“視圖”或“配置”,在本文中,通過了解@JsonView,你將能夠更好地掌握如何在Spring Boot應(yīng)用中定制JSON數(shù)據(jù)的輸出,需要的朋友可以參考下2024-05-05
Idea連接GitLab的過程以及創(chuàng)建在gitlab中創(chuàng)建用戶和群組方式
本文介紹了如何在IDEA中連接GitLab,首先需安裝GitLab插件并配置SSH免密登錄,接著,創(chuàng)建GitLab個(gè)人令牌并在Git中配置,文章還提到了如何在GitLab中創(chuàng)建用戶、群組及設(shè)置權(quán)限,如Owner、Maintainer、Developer等,并強(qiáng)調(diào)了群組名和人員名稱的命名規(guī)范2024-11-11
SpringBoot整合Docker實(shí)現(xiàn)一次構(gòu)建到處運(yùn)行的操作方法
本文講解的是 SpringBoot 引入容器化技術(shù) Docker 實(shí)現(xiàn)一次構(gòu)建到處運(yùn)行,包括鏡像構(gòu)建、Docker倉庫搭建使用、Docker倉庫可視化UI等內(nèi)容,需要的朋友可以參考下2022-10-10
一文盤點(diǎn)Java中常見內(nèi)存泄漏場(chǎng)景與解決方法
內(nèi)存泄漏 是指對(duì)象 已經(jīng)不再被程序使用,但因?yàn)槟承┰?nbsp;無法被垃圾回收器回收,長(zhǎng)期占用內(nèi)存,最終可能引發(fā)?OOM,本文會(huì)介紹常見的幾類內(nèi)存泄漏場(chǎng)景,大家可以避免一下2025-12-12
SpringBoot事件發(fā)布與監(jiān)聽超詳細(xì)講解
今天去官網(wǎng)查看spring boot資料時(shí),在特性中看見了系統(tǒng)的事件及監(jiān)聽章節(jié),所以下面這篇文章主要給大家介紹了關(guān)于SpringBoot事件發(fā)布和監(jiān)聽的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11

