Mybatis 配置初始化过程
测试代码
SqlMapConfig.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="db.properties"/>
<typeAliases>
<package name="com.zhiwei.entity"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="advanced/StudentMapper.xml"/>
</mappers>
</configuration>
StudentMapper.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhiwei.advanced.mapper.StudentMapper">
<resultMap type="com.zhiwei.entity.Student" id="studentResultMap">
<id column="sid" property="sid"/>
<result column="stuname" property="stuName"/>
<result column="stupasswd" property="stuPasswd"/>
<result column="stuage" property="stuAge"/>
<result column="stugender" property="stuGender" javaType="java.lang.String"/>
<result column="stuscore" property="stuScore" javaType="java.lang.Integer"/>
</resultMap>
<resultMap type="com.zhiwei.advanced.pojo.StudentCustomer" id="studentCustomerResultMap">
<id column="sid" property="sid"/>
<result column="sid" property="uid"/>
<result column="stupasswd" property="passwd"/>
<result column="stuname" property="stuName"/>
<result column="stupasswd" property="stuPasswd"/>
<result column="stuage" property="stuAge"/>
<result column="stugender" property="stuGender" javaType="java.lang.String"/>
<result column="stuscore" property="stuScore" javaType="java.lang.Integer"/>
</resultMap>
<select id="findStudentById" parameterType="int" resultMap="studentResultMap">
select * from student where sid = #{sid}
</select>
</mapper>
Student
package com.zhiwei.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Student {
private int sid;
private String stuName;
private String stuPasswd;
private int stuAge;
private String stuGender;
private float stuScore;
}
StudentMapper
package com.zhiwei.advanced.mapper;
import com.zhiwei.advanced.pojo.StudentCustomer;
import com.zhiwei.advanced.pojo.StudentQueryVo;
import com.zhiwei.entity.Student;
import java.util.List;
public interface StudentMapper {
public Student findStudentById(Integer sid);
}
启动类
sqlSession.getMapper(StudentMapper.class).findStudentById(id);
工作流程
解析XML映射为org.apache.ibatis.session.Configuration对象,并以此为基础创建SessionFactory, SessionFactory创建的Session与Executor绑定,交由Executor执行
配置加载过程
SessionFactory构建代码
Inputstream is = Resources.getResourceAsStream("SqlMapConfig.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SessionFactory构建
org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties)
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
XMLConfigBuilder 工具类:主要解析SqlMapConfig.xml
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
new Configuration(): 默认填充配置:类似缓存别名映射类
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
XML解析:parser.parse()
parsed解析标志,底层调用 parseConfiguration(parser.evalNode("/configuration")); 按照Xpath语法解析SqlMapConfig.xml根节点
private void parseConfiguration(XNode root) {
try {
// 解析SqlMapConfig.xml的properties标签, configuration.setVariables(defaults)解析properties文件属性到configuration缓存
propertiesElement(root.evalNode("properties"));
// setting配置: configuration.setVfsImpl(vfsImpl)
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
//别名配置:configuration.typeAliasRegistry
//dtd约束:mybatis-3-config.dtd
//package: key:类简单名驼峰命名法,class:类全限定名
//typeAlias: key: alias自定义别名 type:类全限定名
//注意:最好不用直接引用package形式,否则多个包存在相同名的类会出现冲突
typeAliasesElement(root.evalNode("typeAliases"));
//插件配置:相当于拦截器可自定义操作,例如分页、CRUD操作拦截,类似AOP:configuration.interceptorChain
pluginElement(root.evalNode("plugins"));
//设置对象工厂:哦、configuration.objectFactory
objectFactoryElement(root.evalNode("objectFactory"));
//社会组obectMapper工厂:configuration.objectWrapperFactory
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//设置反射器工厂:configuration.reflectorFactory,反射机制操作
reflectorFactoryElement(root.evalNode("reflectorFactory"));
//mybatis全局配置:若无自定义则使用默认配置
settingsElement(settings);
//设置环境:configuration.environment,本质缓存当前工作数据库信息
//注意:Environment可设置多套数据库环境,通过default属性指定具体数据库
environmentsElement(root.evalNode("environments"));
//明确指定mybatis工作的数据库ID:数据库产品名称
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//类型处理器:解析具体的Mapper数据类型: configuration.typeHandlerRegistry
//作用:数据库操作Mapper的解析和转换工作
typeHandlerElement(root.evalNode("typeHandlers"));
//mapper处理:configuration.mapperRegistry
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
Mapper标签解析
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//package形式批量配置:常用
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
//mapper单个独立配置
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
mapperParser.parse(); 解析
//解析流程
public void parse() {
//判断StudentMapper.xml是否已加载
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
//XML解析可能出现问题,重复处理,例如子ResultMap先于父ResultMap加载
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
configurationElement(parser.evalNode("/mapper")); 解析Mapper标签
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
//解析parameterMap标签:configuration.parameterMaps
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析resultMap标签:configuration.resultMaps
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析sql标签:configuration.sqlFragments
sqlElement(context.evalNodes("/mapper/sql"));
//解析:CRUD标签映射为MapperStatement
//分析:构建sql原材料ParameterMaps、ResuldMap、sql片段、namespace准备后开始构建
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
构建MapperStatement
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
statementParser.parseStatementNode(); 解析成MapperStatement
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));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
//填充include refid引用sql片段
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
//解析mybatis selectkey主键生成策略标签
processSelectKeyNodes(id, parameterTypeClass, 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;
//MapperStatementId生成策略:名称空间 + id + !操作类型key
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);
}
configuration添加MapperStatement
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);
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) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
绑定命名空间: bindMapperForNamespace();
org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespace
本质:命名空间和Mapper接口绑定,方便后续通过Mapper操作动态生成代理类
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
//mapperRegistry:保存代理工厂类
configuration.addMapper(boundType);
}
}
}
}
至此MapperStatement初始化工作完成