本次测试的实例中的相关配置文件如下:
application.xml文件:
version="1.0" encoding="UTF-8"?>
"http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="name" value="mysql"/>
<property name="url" value="xxxxxxx"/>
<property name="username" value="xxxxxxx"/>
<property name="password" value="zh4y4q5ang"/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:mybatis/*.xml"/>
<property name="typeAliasesPackage" value="com.entities"/>
<property name="configLocation" value="classpath:config/configuration.xml"/>
id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.interfaces"/>
id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
configuration.xml文件:
<configuration>
<plugins>
<plugin interceptor="com.interceptors.LogInterceptor" >plugin>
plugins>
configuration>
CityMapper.xml文件:
<mapper namespace="com.interfaces.CityMapper">
<resultMap id="BaseResultMap" type="com.entities.City">
<id column="ID" jdbcType="INTEGER" property="id"/>
<result column="Name" jdbcType="CHAR" property="name"/>
<result column="CountryCode" jdbcType="CHAR" property="countrycode"/>
<result column="District" jdbcType="CHAR" property="district"/>
<result column="Population" jdbcType="INTEGER" property="population"/>
resultMap>
<select id="selectCityById" parameterType="int" resultType="com.entities.City">
SELECT * FROM city WHERE ID=#{id,jdbcType=INTEGER}
select>
<insert id="insertCity" parameterType="com.entities.City">
insert into city
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">
ID,
if>
<if test="name != null">
Name,
if>
<if test="countrycode != null">
CountryCode,
if>
<if test="district != null">
District,
if>
<if test="population != null">
Population,
if>
trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
#{id,jdbcType=INTEGER},
if>
<if test="name != null">
#{name,jdbcType=CHAR},
if>
<if test="countrycode != null">
#{countrycode,jdbcType=CHAR},
if>
<if test="district != null">
#{district,jdbcType=CHAR},
if>
<if test="population != null">
#{population,jdbcType=INTEGER},
if>
trim>
insert>
<delete id="deleteCityById" parameterType="java.lang.Integer">
delete from city
where ID = #{id,jdbcType=INTEGER}
delete>
<update id="updateCityById" parameterType="com.entities.City">
UPDATE city set Name=#{name,jdbcType=CHAR},
CountryCode=#{countrycode,jdbcType=CHAR},
District=#{district,jdbcType=CHAR},
Population=#{population,jdbcType=INTEGER}
WHERE ID=#{id,jdbcType=INTEGER}
update>
<select id="selectWithIf" parameterType="map" resultMap="BaseResultMap">
SELECT * FROM city <if test="id!=0">WHERE city.id=#{id}if> <if test="id==0" >limit 2if>
select>
<select id="selectWithChoose" parameterType="map" resultMap="BaseResultMap">
SELECT * FROM city
<choose >
<when test="id==1">
limit 1
when>
<when test="id==2">
limit 2
when>
<otherwise>
WHERE city.id=#{id}
otherwise>
choose>
select>
<select id="selectWithForeach" parameterType="map" resultMap="BaseResultMap">
SELECT * FROM city
WHERE city.id in
<foreach collection="ids" item="tmp" open="(" close=")" separator="," index="">
#{tmp.id}
foreach>
select>
mapper>
在初始化spring框架的时候,会将在application.xml声明的bean注入到spring的上下文中。包括:
初始化dataSource,并将DataSource注入到spring框架中。
初始化SqlSessionFactoryBean对象,并将DataSource的实体对象注入到SqlSessionFactory中。
SqlSessionFactoryBean实现InitializingBean方法,在其实现的方法中,创建一个SqlSessionFactory对象,具体实现为DefaultSqlSessionFactory类,内部传入configuration对象。关于配置文件的主要解析过程就是在这里实现的。
下面来分析一下源码:
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
//创建一个SqlSessionFactory对象,具体实现为DefaultSqlSessionFactor类
this.sqlSessionFactory = buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
//刚开始时,this.configuration为空,在测试中设置了mybatis的configuration的xml配置文件
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
configuration = this.configuration;
if (configuration.getVariables() == null) {
configuration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
configuration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
//从XmlConfigBuilder中构建一个Configuration
configuration = xmlConfigBuilder.getConfiguration();
} else {
LOGGER.debug(() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
//直接新建一个Configuration对象
configuration = new Configuration();
if (this.configurationProperties != null) {
configuration.setVariables(this.configurationProperties);
}
}
if (this.objectFactory != null) {
configuration.setObjectFactory(this.objectFactory);
}
if (this.objectWrapperFactory != null) {
configuration.setObjectWrapperFactory(this.objectWrapperFactory);
}
if (this.vfs != null) {
configuration.setVfsImpl(this.vfs);
}
//测试中设置了typeAliasesPackage
if (hasLength(this.typeAliasesPackage)) {
//根据,或者;对一条字符串进行分割,得到用来存放实体类的包名
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeAliasPackageArray) {
//将对数据库记录封装的包名保存在Configuration对象中
configuration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for aliases");
}
}
if (!isEmpty(this.typeAliases)) {
for (Class typeAlias : this.typeAliases) {
configuration.getTypeAliasRegistry().registerAlias(typeAlias);
LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
}
}
if (!isEmpty(this.plugins)) {
for (Interceptor plugin : this.plugins) {
//设置类一个log拦截器
configuration.addInterceptor(plugin);
LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
}
}
if (hasLength(this.typeHandlersPackage)) {
String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeHandlersPackageArray) {
configuration.getTypeHandlerRegistry().register(packageToScan);
LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for type handlers");
}
}
if (!isEmpty(this.typeHandlers)) {
for (TypeHandler typeHandler : this.typeHandlers) {
configuration.getTypeHandlerRegistry().register(typeHandler);
LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
}
}
//没有设置databaseId
if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
try {
configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
//测试中,没有使用缓存
if (this.cache != null) {
configuration.addCache(this.cache);
}
if (xmlConfigBuilder != null) {
try {
//开始解析configuration的xml配置文件
//由于在测试的configuration配置文件中,只是定义了一个plugin,在解析的时候,直接解析出plugin的实现类,并将plugin的实体对象保存在configuration的实体对象中。
xmlConfigBuilder.parse();
LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
//从这里开始,处理mapper.xml文件
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
//创建一个XMLMapperBuilder用来解析定义sql语句的xml文件
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
//开始解析mapper.xml文件
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
} else {
LOGGER.debug(() -> "Property 'mapperLocations' was not specified or no matching resources found");
}
//返回一个SqlSessionFactory实体对象
return this.sqlSessionFactoryBuilder.build(configuration);
}
下面看看如何解析mapper.xml文件的:
public void parse() {
//判断当前mapper.xml文件是否已经被解析过
if (!configuration.isResourceLoaded(resource)) {
//在configurationElement完成mapper.xml中所有元素的解析,并将解析出来的相关属性全部保存在configuration中
configurationElement(parser.evalNode("/mapper"));
//添加已经加载过的mapper文件
configuration.addLoadedResource(resource);
//绑定sql语句与Java中定义的接口
bindMapperForNamespace();
}
//这个几个方法都是将前面判断是不完整的语句再次解析一下,就不多说了
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
private void configurationElement(XNode context) {
try {
//namespace就是定义的接口:com.interfaces.CityMapper
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//想builderAssistant设置解析的文件对应的java接口
builderAssistant.setCurrentNamespace(namespace);
//解析出标签,测试中没有使用此标签
cacheRefElement(context.evalNode("cache-ref"));
//解析出标签,测试中没有使用此标签
cacheElement(context.evalNode("cache"));
//解析标签,这个标签官方已经不推荐使用了
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析标签
resultMapElements(context.evalNodes("/mapper/resultMap"));
//测试中没有使用此标签
sqlElement(context.evalNodes("/mapper/sql"));
//解析
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
接来下分析如何解析resultMap标签:
//additionalResultMappings为一个空的list
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
//获取resultMap的id,也是这个resultMap的唯一名称
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
//指明这个resultMap用来与数据库中字段相映射的java类
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
//测试中没有使用这个属性,暂不分析
String extend = resultMapNode.getStringAttribute("extends");
//是否自动映射
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
//获取与数据库字段相映射的java类型
Class typeClass = resolveClass(type);
//没有使用标签
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
resultMappings.addAll(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
//开始解析子标签,现在每一个标签都表示一个映射字段与属性的映射关系
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List<ResultFlag> flags = new ArrayList<ResultFlag>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
//解析标签
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
//构建一个resultMapResolver对象,这个里面什么实质性的操作,具体的操作还是有MapperBuilderAssistant完成
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
//创建一个ResultMap,返回ResultMap对象并将ResultMap保存在Configuration对象中
//在Configuration对象中存在一个ResultMaps,以resultMap中的id作为key用来存储resultMap实体对象
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
private ResultMapping buildResultMappingFromContext(XNode context, Class resultType, List<ResultFlag> flags) throws Exception {
//解析出result标签中所有的属性,并将属性的值传入builderAssistant中,又builderAssistant创建一个ResultMapping对象。
String property;
if (flags.contains(ResultFlag.CONSTRUCTOR)) {
property = context.getStringAttribute("name");
} else {
property = context.getStringAttribute("property");
}
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
String nestedSelect = context.getStringAttribute("select");
String nestedResultMap = context.getStringAttribute("resultMap",
processNestedResultMappings(context, Collections.<ResultMapping>emptyList()));
String notNullColumn = context.getStringAttribute("notNullColumn");
String columnPrefix = context.getStringAttribute("columnPrefix");
String typeHandler = context.getStringAttribute("typeHandler");
String resultSet = context.getStringAttribute("resultSet");
String foreignColumn = context.getStringAttribute("foreignColumn");
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
Class javaTypeClass = resolveClass(javaType);
@SuppressWarnings("unchecked")
Class extends TypeHandler> typeHandlerClass = (Class extends TypeHandler>) resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
//返回一个包含所有属性信息的resultMapping对象
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
接下来看看如何解析具体的select语句:
//在测试中传入的requiredDatabaseId为null
private void buildStatementFromContext(List list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//开始解析sql语句的Satatement
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
public void parseStatementNode() {
//获取id,也是在java接口中的方法名
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.PREPARED
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
//获取标签名称
String nodeName = context.getNode().getNodeName();
//分析出sql的命令
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
//解析sql语句中的include标签
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: and were parsed and removed)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
//生成statementId
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))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
开始构建MappedStatement:
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);
//判断是否是select命令,只有这个命令才会用到resultMap或者resultType这个属性
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);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
//将ParameterMap设置到StatementBuilder中,这样在创建Mapped的时候就可以包含ParameterMap的值了
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
//将创建好的Statement保存在Configuration对象中mappedStatements集合中,以statement的id为key进行存储
configuration.addMappedStatement(statement);
return statement;
}
代码中都添加了注释,应该还算是比较容易看的,就不多说了!!!!
到这里基本上就完成了初始的工作了,剩下的就是根据在java中声明的接口生成动态代理了。在实际使用接口的时候,实际是调用的是根据接口动态生成的代理对象。相关的内容在以后的博客中说吧!