设为首页 收藏本站
查看: 1118|回复: 0

[经验分享] Mybatis 代码流程及实现原理解析(三)

[复制链接]

尚未签到

发表于 2016-11-28 06:06:10 | 显示全部楼层 |阅读模式
  接上篇, 这篇继续分析XMLMapperBuilder.parse()里的configurationElement() 这个方法。

private void configurationElement(XNode context) {
try {
//mapper可以加namespace来避免重复情况
String namespace = context.getStringAttribute("namespace");
builderAssistant.setCurrentNamespace(namespace);
//该节点表示从其他名目空间引用缓存配置
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));//解析cache子节点.
//解析parameterMap 子节点,因为有多个并列的parameterMap节点,所以要加上路径,解析返回的列表
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析resultMap子节点,因为有多个并列的resultMap节点,所以要加上路径,解析返回的列表
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析sql子节点,因为有多个并列的sql节点,所以要加上路径,解析返回的列表
sqlElement(context.evalNodes("/mapper/sql"));
//解析select,insert,update,delete子节点
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new RuntimeException("Error parsing Mapper XML. Cause: " + e, e);
}
}

此方法逐步解析<mapper>的子节点。  子节点<cache-ref>
  xml配置片段:


<cache-ref namespace="com.Book"/>
  java代码解析:

private void cacheRefElement(XNode context) {
if (context != null) {
//首先用该节点的namespace替换cacherefmap中mapper文件的。
configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
try {
//创建resolver,判断该namespace是否可用。
cacheRefResolver.resolveCacheRef();
} catch (IncompleteElementException e) {
//如果不可用,加入到为完成列表,在所有节点解析完成后,再统一做处理
configuration.addIncompleteCacheRef(cacheRefResolver);
}
}
}CacheRefResolver.java的resolve方法
public Cache resolveCacheRef() {
return assistant.useCacheRef(cacheRefNamespace);
}MapperBuilderAssistant.java的useCacheRef方法:
public 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);
//如果当前configuration对象没有持有该namespace的Cache, 则抛出异常。
if (cache == null) {
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
}
currentCache = cache;
unresolvedCacheRef = false;
return cache;
} catch (IllegalArgumentException e) {
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
}
}
  子节点<cache>

  xml配置如下:


<mapper namespace="com.skoyou.domain.UserDAO">
<cache type="com.domain.something.MyCustomCache"
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="tr">
<property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>type:配置自定义缓存或为其他第三方缓存方案 创建适配器来完全覆盖缓存行为。如果未指定的话, 使用“PERPETFUL”类型(PerpetualCache.java)。

eviction:缓存回收策略,默认值是“LRU”, 有如下几种策略可以指定:

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

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

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

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

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

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

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

cache可以有个子节点<property> 来加载.

  java方法解析:

private void cacheElement(XNode context) throws Exception {
if (context != null) {
//获取cache type的值,没有的话,使用默认PERPETUAL
String type = context.getStringAttribute("type", "PERPETUAL");
// 得到type类, 默认是PERPETUAL 对应的PerpetualCache
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
//获取eviction的值,没有的话,使用默认LRU得到eviction类, 即LruCache.class
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);
Properties props = context.getChildrenAsProperties();
//根据提供的参数, 建一个cache对象, 并将这个对象放入到Configuration 对象中.
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props);
}
}




子节点<parameterMap>
  在Mybatis中,这个节点已经被废弃了!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除。其实解析还是比较简单的。取这个节点的所有属性,build出一个ParameterMapping对象,方法parametermappings列表里, 然后将这个列表更新到configuration对象中的ParameterMap中去。


    for (XNode parameterMapNode : list) {
String id = parameterMapNode.getStringAttribute("id");
String type = parameterMapNode.getStringAttribute("type");
Class<?> parameterClass = resolveClass(type);
List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
for (XNode parameterNode : parameterNodes) {
String property = parameterNode.getStringAttribute("property");
String javaType = parameterNode.getStringAttribute("javaType");
String jdbcType = parameterNode.getStringAttribute("jdbcType");
String resultMap = parameterNode.getStringAttribute("resultMap");
String mode = parameterNode.getStringAttribute("mode");
String typeHandler = parameterNode.getStringAttribute("typeHandler");
Integer numericScale = parameterNode.getIntAttribute("numericScale");
ParameterMode modeEnum = resolveParameterMode(mode);
Class<?> javaTypeClass = resolveClass(javaType);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
@SuppressWarnings("unchecked")
Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
parameterMappings.add(parameterMapping);
}
builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
}
}

子节点<resultMap>
  这个节点的解析较复杂,会在下一遍中单独讲解。

  子节点<sql>


<sql id="userColumns"> id,username,password </sql>这个元素可以被用来定义可重用的 SQL 代码段,可以包含在其他语句中。

Java 代码
private void sqlElement(List<XNode> list) throws Exception {
if (configuration.getDatabaseId() != null) {  //传人configuration现有的database id。
sqlElement(list, configuration.getDatabaseId());
}
sqlElement(list, null);
}sqlElement方法:
  private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
for (XNode context : list) { //由于有多个并列的sql节点,需求逐个解析。
//sql 也可以有自己的database id,但实际上这个id要与全局配置的一致
String databaseId = context.getStringAttribute("databaseId");  
String id = context.getStringAttribute("id");
//判断id是否包含有当前的namespace,包含的话直接返回当前id。 没有的话,则以当前mapper的namespace.id值返回
id = builderAssistant.applyCurrentNamespace(id, false);
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId))
sqlFragments.put(id, context);
}
}

databaseIdMatchesCurrent 方法:  sql节点自己指定的database id要与configuration中的一致。


private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
if (requiredDatabaseId != null) {
if (!requiredDatabaseId.equals(databaseId)) {
return false;
}
} else {
if (databaseId != null) {
return false;
}
// skip this fragment if there is a previous one with a not null databaseId
if (this.sqlFragments.containsKey(id)) {
XNode context = this.sqlFragments.get(id);
if (context.getStringAttribute("databaseId") != null) {
return false;
}
}
}
return true;
}实际上这个sqlFragments map是在XMLMappBuilder初始化的时候,有configuration传入的, 所以configuration能得到最新的对这个map的修改。这个map会在build select等节点的时候被用到。  子节点<select>,<insert>,<update>,<delete>

节点配置,以select为例:

<select
id="selectPerson"
databaseId="test"
fetchSize="256"
timeout="10000"
parameterMap="deprecated"
parameterType="int"
resultMap="personResultMap"
resultType="hashmap"
lang="XML"
resultSetType="FORWARD_ONLY"
statementType="PREPARED"
flushCache="false"
useCache="true"
resultOrdered="false"
>


节点属性描述:  



属性
描述

id
在命名空间中唯一的标识符,可以被用来引用这条语句。


databaseId
如果配置了databaseProvider,databaseId要与配置的一致。如果不匹配的话,后续代码不会继续解析     


fetchSize
这是暗示驱动程序每次批量返回的结果行数。默认不设置(驱动 自行处理)。


timeout
这个设置驱动程序等待数据库返回请求结果,并抛出异常时间的 最大等待值。默认不设置(驱动自行处理)


parameterMap
这是引用外部 parameterMap 的已经被废弃的方法。使用内联参数 映射和 parameterType 属性。


parameterType
将会传入这条语句的参数类的完全限定名或别名。


resultMap
命名引用外部的 resultMap。 返回 map 是 MyBatis 最具力量的特性, 对其有一个很好的理解的话, 许多复杂映射的情形就 能被解决了。 使用 resultMap 或 resultType,但不能同时使用。


resultType
从这条语句中返回的期望类型的类的完全限定名或别名。注意集 合情形,那应该是集合可以包含的类型,而不能是集合本身。使 用 resultType 或 resultMap,但不能同时使用


lang
提供”XML“或”RAW“,不同的lang,可能会影响最终的sql语句的生成,默认是”XML“


resultSetType
FORWARD_ONLY|SCROLL_SENSITIVE|SCROLL_INSENSITIVE 中的一种。默认是不设置(驱动自行处理)。


statementType
STA TEMENT,PREPARED 或 CALLABLE 的一种。 这会让 MyBatis 使用选择使用 Statement,PreparedStatement 或 CallableStatement。 默认值:PREPARED


fhushCache
将其设置为 true,不论语句什么时候被调用,都会导致缓存被 清空。默认值:false


userCache
将其设置为 true, 将会导致本条语句的结果被缓存。 默认值: true


resultOrdered
这个属性值适用于有嵌套结果的select 语句。如果是true, 那么就认为包含了嵌套的结果, 或者嵌套的结果被分在一组。这样的话, 当一个新的主结果返回时, 就不会出现上一个结果行。 这能够更加内存友好地填充嵌套结果。默认是false。


  另外还有3个是只对insert有效的属性:
  


useGeneratedKeys这会告诉 MyBatis使用JDBC的getGeneratedKeys 方法来取出由数据(比如:像 MySQL 和 SQL Server 这样的数据库管理系统的自动递增字段)内部生成的主键。 默认值:false
keyProperty标记一个属性, MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值。 默认: 不设置
keyColumn标记一个属性, MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值。 默认: 不设置


  sql语句的解析实际上是在 XMLStatementBuider的parseStatementNode方法中解析的。

  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) {
//构造statementBuilder。这几个sql语句相关的节点在这个类里解析
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}


XMLStatementBuilder.java的 parseStatementNode方法,如下。这个方法比较长,但主要是读取属性值,然后包装生成一个MappedStatement,并放入configuration对象中。
public void parseStatementNode() {
//读取id 属性
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
//读取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 默认是PreparedStatement
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));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//如果是select语句,则默认值是false。否则默认值是true。
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
//如果是select语句,则默认值是true。否则默认值是false。
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// 解析<include>子节点,其实是用引用替换<sql>节点的实际内容。
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// 解析<selectKey>子节点,仅对<insert>有用。后面会讲解parseSelectKeyNodes方法
List<XNode> selectKeyNodes = context.evalNodes("selectKey");
if (configuration.getDatabaseId() != null) {
parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());
}
parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);
//默认是XMLLanguageDriver,parameterTypeClass用不上
//生成持有当前sql语句的sqlSource对象,后面会具体看这个sqlSource是怎么生成的
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
//keyProperty和keyColumn使用于<insert>语句
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();
}
//通过解析出来的属性值,构造MappedStatement对象。并放入configuration对象中
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver);
}

<insert>节点的子节点<selectKey>的解析
  <selectKey>配置例子:

<selectKey
databaseId="mysql“
keyProperty="id"
resultType="int"
order="BEFORE"
statementType="PREPARED">
select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
</selectKey>
  keyProperty:selectKey 语句结果应该被设置的目标属性。就是给哪个字段生成主键值。
  resultType:结果的类型。MyBatis 通常可以算出来,但是写上也没有问题。 MyBatis 允许任何简单类型用作主键的类型,包括字符串
  statementType:MyBatis 支持 STA TEMENT ,PREPARED 和 CALLABLE 语句的映射类型,分别代表 PreparedStatement 和 CallableStatement 类型。
  order:这可以被设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那 么它会首先选择主键, 设置 keyProperty 然后执行插入语句。 如果 设置为 AFTER,那么先执行插入语句,然后是 selectKey 元素- 这和如 Oracle 数据库相似,可以在插入语句中嵌入序列调用.
  java代码:

public void parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass, LanguageDriver langDriver, String skRequiredDatabaseId) {
//可以有多个<selectKey>节点。
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);
}
}
}
//
public 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");
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;
//解析生成主键的sql语句
SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);
SqlCommandType sqlCommandType = SqlCommandType.SELECT;
//生成MappedStatement 对象,放入configuration对象的mappedStatements map中
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, null, databaseId, langDriver);
id = builderAssistant.applyCurrentNamespace(id, false);
MappedStatement keyStatement = configuration.getMappedStatement(id, false);
//同时将这个MappedStatement 对象放入到configuration的 keyGenerator map中
configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
nodeToHandle.getParent().getNode().removeChild(nodeToHandle.getNode());
}


下篇会主要看看<resultMap>的解析和sqlSource的生成。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-306263-1-1.html 上篇帖子: mybatis 字符串比较与in语句处理空集合 下篇帖子: jquery+json+struts2+mybatis实现的多级关联下拉效果
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表