Mybatis初始化源码解析
Mybatis的初始化,主要做一件事情,构建DefaultSqlSessionFactory。那么要构建它,其实只需要将Configuration对象构建起来即可,事实上整个初始化的过程就是在解析Mybatis的配置,装换成Configuration对象。然而Configuration对象比较复杂,有差不多50个属性,虽然不需要全部搞清楚,但我们必须知道一些核心属性含义,以及是如何构建出来的。
下图为Mybatis配置和Configuation属性对照关系,先有个映像,看源码的过程中少晕一下。
Mybatis主配置文件的解析
通常,我们使用SqlSessionFactoryBuilder来构建一个SqlSessionFactory出来,而SqlSessionFactoryBuilder的build方法需要传入一个mybaits的主配置文件,比如如下初始化代码:
//主配置文件的Reader
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
//通过SqlSessionFactoryBuilder来构建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
reader.close();
SqlSessionFactoryBuilder的build方法逻辑:
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
...
//通过XMLConfigBuilder来解析配置,生成Configuration对象
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
//调用重载的build方法,构建SqlSessionFactory
return build(parser.parse());
...
}
//通过传入的Configuration对象,直接new一个DefaultSqlSessionFactory返回。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
XMLConfigBuilder的构造方法中,会new一个Configuration出来,
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
//new一个Configuration,它的构造方法中会注册常用的别名(typeAlias)
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
//设置setVariables属性,为传入的props
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
整个解析位于XMLConfigBuilder.parse方法中:
public Configuration parse() {
//只能解析一次。
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//解析主配置(主配置根元素是configuration节点)
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
继续parseConfiguration方法,它一步一步完成了整个配置文件的解析:
private void parseConfiguration(XNode root) {
try {
//解析properties的配置
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
//解析settings的配置
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
//解析typeAliases的配置
typeAliasesElement(root.evalNode("typeAliases"));
//解析plugins的配置
pluginElement(root.evalNode("plugins"));
//解析plugins的配置
objectFactoryElement(root.evalNode("objectFactory"));
//解析plugins的配置
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//解析plugins的配置
reflectorFactoryElement(root.evalNode("reflectorFactory"));
//设置settings配置
settingsElement(settings);
//解析environments的配置
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
//解析databaseIdProvider的配置
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//解析typeHandlers的配置
typeHandlerElement(root.evalNode("typeHandlers"));
//解析mappers的配置
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
除了mappers的配置解析之外,其他的配置解析都比较简单,我们挑几个看看。
- properties配置解析
先观察下properties配置的xml结构:
<properties resource="" url="">
<property name="" value=""/>
</properties>
解析properties的配置逻辑:
private void propertiesElement(XNode context) throws Exception {
if (context != null) { //配置了propertiess
//获取所有的property配置
Properties defaults = context.getChildrenAsProperties();
//获取resource属性配置
String resource = context.getStringAttribute("resource");
//获取url属性配置
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
//不能同时配置resource和url
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
if (resource != null) {
//把resource配置的文件加进来,因为同名的key会产生覆盖,所以resource优先级高于直接property配置
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
//把url配置的文件加进来,因为同名的key会产生覆盖,所以url优先级高于直接property配置
defaults.putAll(Resources.getUrlAsProperties(url));
}
//这个是初始化时,方法参数传入的props
Properties vars = configuration.getVariables();
if (vars != null) {
//把方法参数传入的props加进来,同理,方法参数的方式优先级最高
defaults.putAll(vars);
}
//组合后的结果设置到parser中
parser.setVariables(defaults);
//组合后的结果设置到configuration的variables属性中。
configuration.setVariables(defaults);
}
}
可以看出,properties配置解析比较简单直观,唯一注意一点的就是存在优先级覆盖问题,优先级从高到低为:方法传参的方式 高于 resource/url 指定外部配置文件的方式 高于 直接配置的property元素方式。
- typeAliases的配置解析
先看下xml中的配置结构:
<typeAliases>
<package name="xxx"/>
<typeAlias type="xx" alias="xx" />
</typeAliases>
解析typeAliases的配置逻辑:
private void typeAliasesElement(XNode parent) {
if (parent != null) {
//遍历每个子元素
for (XNode child : parent.getChildren()) {
//如果是package元素,则通过包扫描的方式处理
if ("package".equals(child.getName())) {
//获取包名
String typeAliasPackage = child.getStringAttribute("name");
//扫描包并注册。
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
//否则就是typeAlias元素,配置单个别名
} else {
//获取别名配置
String alias = child.getStringAttribute("alias");
//获取类型配置
String type = child.getStringAttribute("type");
try {
//获取配置type的Class对象
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
//如果没有配置了别名,则看类上是否Alias注解,有则通过该注解的值作为别名注册到typeAliasRegistry中,如果没有则通过类的简单名作为别名注册。
typeAliasRegistry.registerAlias(clazz);
} else {
//有配置别名的话,直接使用配置的别名注册。
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
来看下扫描包的实现,这个在其他地方也会用到(mapper接口的包扫描),逻辑位于TypeAliasRegistry.registerAliases方法中:
public void registerAliases(String packageName){
registerAliases(packageName, Object.class);
}
public void registerAliases(String packageName, Class<?> superType){
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
//找到扫描包下是superType类型的所有类,这儿传入的是Object.class,那么就是扫描所有类
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
//遍历扫描出来的类
for(Class<?> type : typeSet){
// Ignore inner classes and interfaces (including package-info.java)
// Skip also inner classes. See issue #6
//进行过滤处理后,注册别名(别名的处理和单个中的没有配置别名的处理一致)
if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
registerAlias(type);
}
}
}
- environments的配置解析
同样,先看下xml配置样子
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="UNPOOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://xxxx"/>
<property name="username" value="xxx"/>
<property name="password" value="xxx"/>
</dataSource>
</environment>
</environments>
解析environments的配置逻辑:
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
//没有指定环境的话,使用default指定的默认环境
environment = context.getStringAttribute("default");
}
//遍历解析每个环境配置
for (XNode child : context.getChildren()) {
//获取环境ID
String id = child.getStringAttribute("id");
//如果当前准备解析环境ID,是指定的环境,才会真正的进入解析.
if (isSpecifiedEnvironment(id)) {
//解析transactionManager配置,根据配置的类型反射实例化TransactionFactory对象.
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
//解析dataSource配置,根据配置的类型反射实例化DataSourceFactory对象.
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
//获取DataSource
DataSource dataSource = dsFactory.getDataSource();
//new一个Environment.Builder。
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
//将构建出的Environment设置到configuration的environment属性中。
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
在整个解析过程中,多次使用类似的代码逻辑,即根据配置的类型反射实例化一个对象出来,比如上面的TransactionFactory、DataSourceFactory:
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
if (context != null) {
//获取指定类型
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
//反射实例化对象,可以看到使用的是默认的构造方法,因此要扩展的自定义的一些对象时,需要提供默认的无参构造方法,否则会报错。
TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
//大部分的可指定类型的接口,都会定义一个setProperties方法,通过这儿回调来设置自定义的属性。
factory.setProperties(props);
return factory;
}
}
Mapper映射文件的解析
Mapper映射文件的元素虽然不多,核心的主要是cache、resultMap、sql、以及select|update|delete|insert。但解析还是相对来说比较繁琐的。
Mapper映射文件解析的入口:
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//包扫描方式解析Mapper
if ("package".equals(child.getName())) {
//获取包扫描路径配置
String mapperPackage = child.getStringAttribute("name");
//进行包扫描和解析。
configuration.addMappers(mapperPackage);
//单个Mapper解析。
} else {
//单个Mapper配置的3中方式,resource、url、class。
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
//只配置了resource方式
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
//new一个XMLMapperBuilder来具体的解析
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
//只配置了url方式,解析与resource一致。
} 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();
//只配置了class方式,解析逻辑会复杂一些。
} else if (resource == null && url == null && mapperClass != null) {
//加载mapper接口类
Class<?> mapperInterface = Resources.classForName(mapperClass);
//进行Mapper Class类型的解析。
configuration.addMapper(mapperInterface);
//其他情况,抛出异常。
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
从简单入手,先看下XMLMapperBuilder来解析单个mapper.xml文件的流程:
public void parse() {
//每个mapper.class 和 mapper.xml 只能被解析一次。
if (!configuration.isResourceLoaded(resource)) {
//解析mapper根元素
configurationElement(parser.evalNode("/mapper"));
//解析完成后放入到loadedResources中,防止重复解析
configuration.addLoadedResource(resource);
//通过namespace看有没有对应的mapper接口类,如果有的话,注册到mapperRegistry中。
bindMapperForNamespace();
}
....
}
解析的主体代码:
private void configurationElement(XNode context) {
try {
//获取namespace配置,该配置是必须的。
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//builderAssistant 构建助手,使用它来构建如MappedStatement、cache、ResultMap等复杂的对象。
builderAssistant.setCurrentNamespace(namespace);
//解析cache-ref配置。
cacheRefElement(context.evalNode("cache-ref"));
//解析cache配置。
cacheElement(context.evalNode("cache"));
//解析parameterMap配置,以废弃,可忽略。
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析resultMap配置。
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析sql配置。
sqlElement(context.evalNodes("/mapper/sql"));
//解析select|insert|update|delete配置。
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
不全部展开全部的解析,关注两个重要的cache的解析和【select|insert|update|delete】这些语句的解析。
-
cache配置解析
首先看下xml中cache的配置:
<cache eviction="FIFO" size="512" readOnly="true"/>
具体的解析逻辑:
private void cacheElement(XNode context) throws Exception { if (context != null) { //获取缓存类型,默认为PERPETUAL String type = context.getStringAttribute("type", "PERPETUAL"); Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type); //获取逐出策略,默认为LRU。 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); } }
缓存对象的具体构建过程,参考 Mybatis缓存源码详解 的二级缓存源码解析。( https://blog.csdn.net/gruelxsp/article/details/103769381 ),详细的介绍了二级缓存的构建过程,如何通过装饰者模式来完成整个缓存的各种功能职责划分。
-
select|insert|update|delete配置解析
可以说,这个解析才是最最核心的,真正是配置SQL语句的解析。
xml配置样子:
<select id="selectById" resultMap="userMap"> select * from sys_user where id = #{id} </select>
解析逻辑:
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { //遍历所有的语句配置 for (XNode context : list) { //构建XMLStatementBuilder来解析做具体的语句解析工作。 final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { //执行语句解析。 statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } }
XMLStatementBuilder的parseStatementNode方法,执行真正的语句解析:
public void parseStatementNode() { //获取配置的信息,并做对应的处理。不单独一个一个解释。 String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); ... 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); //这儿是对应用了sql语句片段的解析。 // Include Fragments before parsing XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes and remove them. processSelectKeyNodes(id, parameterTypeClass, langDriver); //解析出具体的SQL语句,封装为SqlSource // Parse the SQL (pre: <selectKey> and <include> 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; 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,由它进行最后的mapppedStatement对象的构建和注册。 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }
builderAssistant.addMappedStatement方法:
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扩展,加上namespace,形成全局唯一的id。 id = applyCurrentNamespace(id, false); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; //创建MappedStatement.Builder对象,通过它来构建MappedStatement 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。 MappedStatement statement = statementBuilder.build(); //注册到configuration的mappedStatements中。注意,key除了为带namespace的全名称,还会注册一个简单的名称(方法名),不过应为方法名会冲突,所以在get存在冲突的方法名时,会抛出异常. configuration.addMappedStatement(statement); return statement; }
以上是单个的mapper.xml整个解析流程。
那么单个的mapper接口类是怎么解析的呢?从XMLConfigBuilder.mapperElement方法中的
configuration.addMapper(mapperInterface);
这行代码为入口分析:public <T> void addMapper(Class<T> type) { mapperRegistry.addMapper(type); }
它委托给mapperRegistry来处理,MapperRegistry#addMapper方法逻辑:
public <T> void addMapper(Class<T> type) { //只会care接口类型。 if (type.isInterface()) { //是否已经注册过了 if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { //不管解析有没有问题,先注册上去。注意注册的是MapperProxyFactory,这个在执行中会用到。 knownMappers.put(type, new MapperProxyFactory<T>(type)); //对于Mapper接口的解析的话,是通过MapperAnnotationBuilder来完成具体的解析的 MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { //如果没有正常解析完,把前面注册的mapper干掉。 if (!loadCompleted) { knownMappers.remove(type); } } } }
具体的解析逻辑MapperAnnotationBuilder#parse方法:
public void parse() { String resource = type.toString(); //不能重复解析 if (!configuration.isResourceLoaded(resource)) { //加载Mapper接口类对应的xml配置,这里面就会使用前面介绍的XMLMapperBuilder来完成整个mapper.xml文件的解析,mapper.xml的名称需要与接口类以及位置需要和接口类放在一样的classpath下,其原因就是在定位xml配置的时候,是直接通过mappeer接口类全限定名拼上.xml去找的。 loadXmlResource(); //放到loadedResources中。 configuration.addLoadedResource(resource); assistant.setCurrentNamespace(type.getName()); //解析接口上@CacheNamespace注解配置的cache。 parseCache(); //解析接口上@CacheNamespaceRef注解配置的cache。 parseCacheRef(); //对接口中的每个方法,进行解析。 Method[] methods = type.getMethods(); for (Method method : methods) { ... if (!method.isBridge()) { //解析方法(解析上面的语句注解,构建出MappedStatement并注册) parseStatement(method); } ... } }
上面代码中loadXmlResource方法、parseCache方法的核心逻辑与xml文件中的处理差不多,多出来的关键的方法是parseStatement,它会解析和构建新的MappedStatement。 具体的方法解析逻辑:
void parseStatement(Method method) { //获取方法的入参 Class<?> parameterTypeClass = getParameterType(method); //获取LanguageDriver,用它来解析sql语句 LanguageDriver languageDriver = getLanguageDriver(method); //解析Sql语句,这里面会找方法上的这些注解 /** sqlAnnotationTypes.add(Select.class); sqlAnnotationTypes.add(Insert.class); sqlAnnotationTypes.add(Update.class); sqlAnnotationTypes.add(Delete.class); sqlProviderAnnotationTypes.add(SelectProvider.class); sqlProviderAnnotationTypes.add(InsertProvider.class); sqlProviderAnnotationTypes.add(UpdateProvider.class); sqlProviderAnnotationTypes.add(DeleteProvider.class); */ SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver); if (sqlSource != null) { //获取@Options注解 Options options = method.getAnnotation(Options.class); //mappedStatementId就是类的全限定名,再连上方法名。 final String mappedStatementId = type.getName() + "." + method.getName(); //后面一堆其他的解析。 Integer fetchSize = null; Integer timeout = null; StatementType statementType = StatementType.PREPARED; ResultSetType resultSetType = ResultSetType.FORWARD_ONLY; SqlCommandType sqlCommandType = getSqlCommandType(method); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = !isSelect; boolean useCache = isSelect; KeyGenerator keyGenerator; String keyProperty = "id"; String keyColumn = null; if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) { // first check for SelectKey annotation - that overrides everything else SelectKey selectKey = method.getAnnotation(SelectKey.class); if (selectKey != null) { keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver); keyProperty = selectKey.keyProperty(); } else if (options == null) { keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } else { keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; keyProperty = options.keyProperty(); keyColumn = options.keyColumn(); } } else { keyGenerator = NoKeyGenerator.INSTANCE; } if (options != null) { if (FlushCachePolicy.TRUE.equals(options.flushCache())) { flushCache = true; } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) { flushCache = false; } useCache = options.useCache(); fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348 timeout = options.timeout() > -1 ? options.timeout() : null; statementType = options.statementType(); resultSetType = options.resultSetType(); } String resultMapId = null; //@ResultMap注解的解析 ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class); if (resultMapAnnotation != null) { String[] resultMaps = resultMapAnnotation.value(); StringBuilder sb = new StringBuilder(); for (String resultMap : resultMaps) { if (sb.length() > 0) { sb.append(","); } sb.append(resultMap); } resultMapId = sb.toString(); } else if (isSelect) { resultMapId = parseResultMap(method); } //把解析出来的所有信息,传给MapperBuilderAssistant的addMappedStatement方法,进行MappedStatement的构建和注册。 assistant.addMappedStatement( mappedStatementId, sqlSource, statementType, sqlCommandType, fetchSize, timeout, // ParameterMapID null, parameterTypeClass, resultMapId, getReturnType(method), resultSetType, flushCache, useCache, // TODO gcode issue #577 false, keyGenerator, keyProperty, keyColumn, // DatabaseID null, languageDriver, // ResultSets options != null ? nullOrEmpty(options.resultSets()) : null); } }
到此,就完成了单个Mapper接口类的解析和注册。
更进一步,包扫描在单个Mapper接口类的解析和注册基础之上,多做了扫描包的工作,再次回XMLConfigBuilder#mapperElement方法中,通过
configuration.addMappers(mapperPackage);
这条代码进入Configuration#addMappers方法:public void addMappers(String packageName) { mapperRegistry.addMappers(packageName); }
委托给MapperRegistry#addMappers方法:
public void addMappers(String packageName) { addMappers(packageName, Object.class); }
最终调用到:
public void addMappers(String packageName, Class<?> superType) { ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); //扫到所有的类 resolverUtil.find(new ResolverUtil.IsA(superType), packageName); Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses(); //遍历扫到的类,进行单个Mapper类的解析和注册。 for (Class<?> mapperClass : mapperSet) { //单个Mapper类的解析和注册 addMapper(mapperClass); } }
所以,包扫描就是将类扫出来之后,遍历对单个Mapper接口进行解析和注册,而对单个Mapper接口进行解析的过程中又使用了mapper.xml解析。
初始化源码总结
整个解析过程不算很复杂,但是很繁琐,通常不需要了解的这么细致,知道Configuration对象中的一些核心属性,比如environment、各种setting配置、variables、mapperRegistry(重要)、interceptorChain(重要)、mappedStatements(重要)等大概是如何解析的。