您的位置:首页 > 移动开发

MyBatis官方教程及源码解析——mapper映射文件

2016-04-04 19:15 609 查看

缓存

1.官方文档

MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。MyBatis 3 中的缓存实现的很多改进都已经实现了,使得它更加强大而且易于配置。

默认情况下是没有开启缓存的,除了局部的session 缓存,可以增强变现而且处理循环 依赖也是必须的。要开启二级缓存,你需要在你的 SQL 映射文件中添加一行:

<cache/>


字面上看就是这样。这个简单语句的效果如下:

· 映射语句文件中的所有 select语句将会被缓存。

· 映射语句文件中的所有 insert,update和 delete 语句会刷新缓存。

· 缓存会使用 Least Recently Used(LRU,最近最少使用的)算法来收回。

· 根据时间表(比如 no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序 来刷新。

· 缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用。

· 缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而 且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

所有的这些属性都可以通过缓存元素的属性来修改。比如:

<cache

eviction="FIFO"

flushInterval="60000"

size="512"

readOnly="true"/>


这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会 导致冲突。

可用的收回策略有:

· LRU –最近最少使用的:移除最长时间不被使用的对象。

· FIFO –先进先出:按对象进入缓存的顺序来移除它们。

· SOFT –软引用:移除基于垃圾回收器状态和软引用规则的对象。

· WEAK –弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

默认的是 LRU。

flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒 形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。

size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的 可用内存资源数目。默认值是 1024。

readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓 存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化) 。这会慢一些,但是安全,因此默认是 false。

2.源码解析

缓存的解析比较简单,mybatis根据配置生成一个cache对象,并存入configuration,每个映射配置文件只能有一个cache,配置多个时只有第一个生效

//XMLMapperBuilder类
private void cacheElement(XNode context) throws Exception {
if (context != null) {
//获取配置
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
//由builderAssistant对象生成
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
//MapperBuilderAssistant类
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
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();
//将cache存入configuration
configuration.addCache(cache);
//置为当前cache
currentCache = cache;
return cache;
}


除了定义一个新的缓存,我们还可以直接使用其他映射文件配置的缓存,这就利用到了cache-ref

<cache-ref namespace="com.someone.application.data.SomeMapper"/>


要注意的是<cache-ref>的解析在<cache>之前,所以currentCache 对象会选择后者

private void cacheRefElement(XNode context) {
if (context != null) {
//按照两个映射文件的名空间存入configuration
configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
try {
//这里虽然定义了cacheRefResolver 对象,但最终调用的是MapperBuilderAssistant类的useCacheRef方法
cacheRefResolver.resolveCacheRef();
} catch (IncompleteElementException e) {
configuration.addIncompleteCacheRef(cacheRefResolver);
}
}
}
pu
10268
blic Cache useCacheRef(String namespace) {
if (namespace == null) {
throw new BuilderException("cache-ref element requires a namespace attribute.");
}
try {
unresolvedCacheRef = true;
Cache cache = configuration.getCache(namespace);
//cache找不到的一种可能是对应的映射文件还未解析,这种时候会抛出异常
if (cache == null) {
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
}
//如果找到对应cache则currentCache 会置为该对象
currentCache = cache;
unresolvedCacheRef = false;
return cache;
} catch (IllegalArgumentException e) {
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
}
}


Sql语句块

1.官方文档

这个元素可以被用来定义可重用的 SQL 代码段,可以包含在其他语句中。它可以被静态地(在加载参数) 参数化. 不同的属性值通过包含的实例变化. 比如:

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>


这个 SQL 片段可以被包含在其他语句中,例如:

<select id="selectUsers" resultType="map">


select


<include refid="userColumns"><property name="alias" value="t1"/></include>,


<include refid="userColumns"><property name="alias" value="t2"/></include>


from some_table t1


cross join some_table t2</select>


属性值可以用于包含的refid属性或者包含的字句里面的属性值,例如:

<sql id="sometable">


${prefix}Table</sql>


<sql id="someinclude">


from


<include refid="${include_target}"/></sql>


<select id="select" resultType="map">


select


field1, field2, field3


<include refid="someinclude">


<property name="prefix" value="Some"/>


<property name="include_target" value="sometable"/>


</include></select>


2.源码解析

Sql语句块和映射语句在解析的时候会根据DatabaseId来进行区分,如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。利用这点,在配置语句的时候可以为不同数据库配置不同的语句。

//XMLMapperBuilder类中
private void sqlElement(List<XNode> list) throws Exception {
//这里的配置取自配置文件中的databaseIdProvider
if (configuration.getDatabaseId() != null) {
sqlElement(list, configuration.getDatabaseId());
}
sqlElement(list, null);
}

private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
for (XNode context : list) {
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
id = builderAssistant.applyCurrentNamespace(id, false);
//在此处对databaseId配置和当前数据库进行匹配
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
//最终根据ID将整个节点存入
sqlFragments.put(id, context);
}
}
}

private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
//如果有配置databaseIdProvider,则两者必须一致
if (requiredDatabaseId != null) {
if (!requiredDatabaseId.equals(databaseId)) {
return false;
}
} else {
//如果没有配置databaseIdProvider,则不需要配置databaseId
if (databaseId != null) {
return false;
}
// 如果存在相同ID且databaseId不为空,则省略
if (this.sqlFragments.containsKey(id)) {
XNode context = this.sqlFragments.get(id);
if (context.getStringAttribute("databaseId") != null) {
return false;
}
}
}
return true;
}


在最后sql代码块将整个节点都存入Map中,这样做是因为sql可以实现动态复用,因此每次都必须重新解析sql代码块的值,这些在接下来映射语句的解析部分完成。

映射语句

映射语句是Mapper配置中比较复杂的一部分,一方面可以嵌入sql语句块,另一方面还有动态Sql。

//XMLMapperBuilder类中
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//这里又由XMLStatementBuilder类来进行解析
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
//XMLStatementBuilder类中
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");

if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}

Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);

Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
//这里判断是不是查询语句,影响到后面flushCache和userCache的默认值
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

//1.先处理sql代码块(include)
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());

//2.再处理selectKey并移除
processSelectKeyNodes(id, parameterTypeClass, langDriver);

//3.最后解析SQL语句
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? new Jdbc3KeyGenerator() : new NoKeyGenerator();
}

builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

先来看<include>的解析,在官方文档里面可以看到<include>中包含了<propery>子节点,

用户可以为<propery>配置不同的值来实现动态的复用,如果没有对<propery>进行配置,Mybatis会从XML配置文件里面寻找

public void applyIncludes(Node source) {
Properties variablesContext = new Properties();
//获取XML配置文件中的property值
Properties configurationVariables = configuration.getVariables();
if (configurationVariables != null) {
variablesContext.putAll(configurationVariables);
}
applyIncludes(source, variablesContext);
}
private void applyIncludes(Node source, final Properties variablesContext) {
//针对<include>进行解析
if (source.getNodeName().equals("include")) {
//这里新定义fullContext对象,保证在一个解析过程中使用同一套值
Properties fullContext;
String refid = getStringAttribute(source, "refid");
//对refid进行解析(例如refid="${include_target}"的形式)
refid = PropertyParser.parse(refid, variablesContext);
Node toInclude = findSqlFragment(refid);
//这里对<property>进行解析并返回结果
Properties newVariablesContext = getVariablesContext(source, variablesContext);
//根据解析结果使用不同的值
if (!newVariablesContext.isEmpty()) {
// merge contexts
fullContext = new Properties();
fullContext.putAll(variablesContext);
fullContext.putAll(newVariablesContext);
} else {
// no new context - use inherited fully
fullContext = variablesContext;
}
//针对Sql代码块解析,toInclude是<sql>节点
applyIncludes(toInclude, fullContext);
if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
toInclude = source.getOwnerDocument().importNode(toInclude, true);
}
//将<include>替换成对应<sql>
source.getParentNode().replaceChild(toInclude, source);
while (toInclude.hasChildNodes()) {
//插入<sql>节点解析后的sql语句
toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
}
//最后移除<sql>节点
toInclude.getParentNode().removeChild(toInclude);
}else if (source.getNodeType() == Node.ELEMENT_NODE) {
NodeList children = source.getChildNodes();
for (int i=0; i<children.getLength(); i++) {
applyIncludes(children.item(i), variablesContext);
}
} else if (source.getNodeType() == Node.ATTRIBUTE_NODE && !variablesContext.isEmpty()) {
// replace variables in all attribute values
source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
} else if (source.getNodeType() == Node.TEXT_NODE && !variablesContext.isEmpty()) {
// replace variables ins all text nodes
source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
}
}
以上过程最终将<include>替换成对应的sql语句。

接下来是<selectKey>的解析,我们可以将其视为一种特殊的映射语句,最终结果保存在configuration的keyGenerators中

private void processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) {
List<XNode> selectKeyNodes = context.evalNodes("selectKey");
//也需要判断databaseId
if (configuration.getDatabaseId() != null) {
parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());
}
parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);
//解析后删除节点
removeSelectKeyNodes(selectKeyNodes);
}
private void parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass, LanguageDriver langDriver, String skRequiredDatabaseId) {
for (XNode nodeToHandle : list) {
String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
String databaseId = nodeToHandle.getStringAttribute("databaseId");
if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) {
parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId);
}
}
}
private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) {
String resultType = nodeToHandle.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));
String keyProperty = nodeToHandle.getStringAttribute("keyProperty");
String keyColumn = nodeToHandle.getStringAttribute("keyColumn");
boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER"));

//defaults
boolean useCache = false;
boolean resultOrdered = false;
KeyGenerator keyGenerator = new NoKeyGenerator();
Integer fetchSize = null;
Integer timeout = null;
boolean flushCache = false;
String parameterMap = null;
String resultMap = null;
ResultSetType resultSetTypeEnum = null;

SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);
//这里将<selectKey>当做一种select语句
SqlCommandType sqlCommandType = SqlCommandType.SELECT;
//和映射语句一样,<selectKey>解析成MappedStatement对象并保存,
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);

id = builderAssistant.applyCurrentNamespace(id, false);
//这里将解析结果保存在configuration中
MappedStatement keyStatement = configuration.getMappedStatement(id, false);
configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
}


最后是映射语句的解析

public void parseStatementNode() {
//....省略

//3.最后解析SQL语句
//先是生产SqlSource对象,保存解析后的Sql语句
//该对象由langDriver产生,这部分主要是和动态Sql有关,暂时省略
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
//以下生成KeyGenerator
KeyGenerator keyGenerator;
//keyStatementId 和<SelectKey>的id解析方式一样,这就保证能取到前面<SelectKey>解析的结果
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? new Jdbc3KeyGenerator() : new NoKeyGenerator();
}

builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}


从以上代码可以看出,映射语句最终会由MapperBuilderAssistant解析成MappedStatement对象,最后看看该过程如何实现

//MapperBuilderAssistant类
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) {
//必须cache-ref解析完成后才能继续
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}

id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//最后由statementBuilder构建
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)
.resulSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);

ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}

MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  源码 Mybatis