本次测试的实例中的相关配置文件如下:
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中声明的接口生成动态代理了。在实际使用接口的时候,实际是调用的是根据接口动态生成的代理对象。相关的内容在以后的博客中说吧!