初始化
mybatis初始化主要解析两种配置文件
- 一种是mybatis-config.xml,通过解析这个文件来加载用户自定义配置,系统配置,类型别名,java和jdbc类型之间的转换器等
- 另外一种是各种Mapper的配置文件,通过解析这个文件来加载每个Mapper方法的sql,参数转换,类型转换等
首先看下mybatis初始化的代码
// 指定配置文件
Reader reader = Resources
.getResourceAsReader("org/apache/ibatis/submitted/annotion_many_one_add_columnprefix/SqlMapConfig.xml"));
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = sqlSessionFactory.openSession()
UserDao mapper = sqlSession.getMapper(UserDao.class);
build
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
// 解析配置文件
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
XMLConfigBuilder.parse
public Configuration parse() {
// 设置标志位,避免重复解析
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 使用XPathParser来解析配置文件中的confiugration节点
// 并将这个节点的信息交给parseConfiguration
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
// 当前的root是configuration节点
// 解析自定义配置
propertiesElement(root.evalNode("properties"));
// 解析Mybatis内置的配置项,比如是否开启缓存
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 读取vfsImpl,加载对应的虚拟文件系统类,方便读取系统文件
loadCustomVfs(settings);
// 读取logImpl,使用指定的日志实现类
loadCustomLogImpl(settings);
// 解析类别别名
typeAliasesElement(root.evalNode("typeAliases"));
// 解析插件
pluginElement(root.evalNode("plugins"));
// 下面三个主要就是读取相应的节点,然后创建相应的factory实例,然后设置到configuration中
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 解析settings节点,将系统配置设置到configuration中
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 解析environments标签,可以通过为不同的环境使用不同的environment子标签,在environment子标签中可以指定当前环境使用的事务管理企和数据源
environmentsElement(root.evalNode("environments"));
// 通过指定dataBaseIdProvider可以适配不同的数据库厂商的产品,在Mapper文件中,可以为同一个id为selectById的sql代码块指定不同的dataBaseId,从而当执行selectById这个方法时,会根据当前使用的数据库产品,执行对应的sql
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 注册java类型和jdbc类型之间的类型转换器
typeHandlerElement(root.evalNode("typeHandlers"));
// 这里是最关键的,解析每个mapper
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
propertiesElement
这个方法主要用来解析自定义配置
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
// 读取properties的所有子标签,分别读取每个标签的name和value属性,放到properties中
Properties defaults = context.getChildrenAsProperties();
// 读取properties标签的resource url属性
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
// 分别根据resource和url来加载指定的配置文件
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
// 加载configuration中的配置
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
// 将整合好的配置放到parser和configuration中
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
从上面的源码可以看出,配置自定义属性一共有两种方法,一种就是简单地在xml中进行配置,比如
<properties>
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test" />
<property name="username" value="root" />
<property name="password" value="root" />
</properties>
另外一种就是在properties的resource和url属性中来指定配置文件,其中jdbc.properties中存放了一些和数据库相关的配置
<properties resource="jdbc.properties"/>
settingsAsProperties
这个方法主要用来解析系统配置,比如是否使用缓存等
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
// 遍历settings的所有子节点,读取每个节点的name和value放到properties中
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
// 下面校验这里设置的属性是否是Mybatis内置的
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
for (Object key : props.keySet()) {
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
return props;
}
<settings>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="useGeneratedKeys" value="true"/>
</settings>
loadCustomVfs
主要用来读取配置中的vfsImpl,使用指定的虚拟文件系统实现类,来实现读取系统文件的功能
private void loadCustomVfs(Properties props) throws ClassNotFoundException {
String value = props.getProperty("vfsImpl");
if (value != null) {
String[] clazzes = value.split(",");
for (String clazz : clazzes) {
if (!clazz.isEmpty()) {
@SuppressWarnings("unchecked")
Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
configuration.setVfsImpl(vfsImpl);
}
}
}
}
loadCustomLogImpl
主要用来读取配置中的logImpl,使用指定日志实现类
private void loadCustomLogImpl(Properties props) {
Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
}
public void setLogImpl(Class<? extends Log> logImpl) {
if (logImpl != null) {
this.logImpl = logImpl;
LogFactory.useCustomLogging(this.logImpl);
}
}
public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
setImplementation(clazz);
}
private static void setImplementation(Class<? extends Log> implClass) {
try {
Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
Log log = candidate.newInstance(LogFactory.class.getName());
if (log.isDebugEnabled()) {
log.debug("Logging initialized using '" + implClass + "' adapter.");
}
logConstructor = candidate;
} catch (Throwable t) {
throw new LogException("Error setting Log implementation. Cause: " + t, t);
}
}
当调用LogFactory.getLog的时候,就会创建我们指定的日志实现类
public static Log getLog(String logger) {
try {
return logConstructor.newInstance(logger);
} catch (Throwable t) {
throw new LogException("Error creating logger for logger " + logger + ". Cause: " + t, t);
}
}
typeAliasesElement
这个方法是用来解析类型别名的
private void typeAliasesElement(XNode parent) {
if (parent != null) {
// 解析typeAliases标签下的所有子标签
for (XNode child : parent.getChildren()) {
// 如果是package,那么会将指定包下的所有类
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
// 其他子标签,typeAliases,只是单独注册一个别名
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
<typeAliases>
<typeAlias alias="LazyFoo"
type="org.apache.ibatis.submitted.lazy_deserialize.LazyObjectFoo" />
<typeAlias alias="LazyBar"
type="org.apache.ibatis.submitted.lazy_deserialize.LazyObjectBar" />
<package name="org.apache.ibatis.submitted.autodiscover.aliases" />
</typeAliases>
另外mybatis会内置一些默认的别名,在TypeAliasRegistry的构造函数中会注册一些基本类型的别名
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
registerAlias("integer", Integer.class);
registerAlias("double", Double.class);
registerAlias("float", Float.class);
registerAlias("boolean", Boolean.class);
registerAlias("byte[]", Byte[].class);
registerAlias("long[]", Long[].class);
registerAlias("short[]", Short[].class);
registerAlias("int[]", Integer[].class);
registerAlias("integer[]", Integer[].class);
registerAlias("double[]", Double[].class);
registerAlias("float[]", Float[].class);
registerAlias("boolean[]", Boolean[].class);
registerAlias("_byte", byte.class);
registerAlias("_long", long.class);
registerAlias("_short", short.class);
registerAlias("_int", int.class);
registerAlias("_integer", int.class);
registerAlias("_double", double.class);
registerAlias("_float", float.class);
registerAlias("_boolean", boolean.class);
registerAlias("_byte[]", byte[].class);
registerAlias("_long[]", long[].class);
registerAlias("_short[]", short[].class);
registerAlias("_int[]", int[].class);
registerAlias("_integer[]", int[].class);
registerAlias("_double[]", double[].class);
registerAlias("_float[]", float[].class);
registerAlias("_boolean[]", boolean[].class);
registerAlias("date", Date.class);
registerAlias("decimal", BigDecimal.class);
registerAlias("bigdecimal", BigDecimal.class);
registerAlias("biginteger", BigInteger.class);
registerAlias("object", Object.class);
registerAlias("date[]", Date[].class);
registerAlias("decimal[]", BigDecimal[].class);
registerAlias("bigdecimal[]", BigDecimal[].class);
registerAlias("biginteger[]", BigInteger[].class);
registerAlias("object[]", Object[].class);
registerAlias("map", Map.class);
registerAlias("hashmap", HashMap.class);
registerAlias("list", List.class);
registerAlias("arraylist", ArrayList.class);
registerAlias("collection", Collection.class);
registerAlias("iterator", Iterator.class);
registerAlias("ResultSet", ResultSet.class);
}
在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);
}
package标签
下面看下mybatis是如何处理package节点的
public void registerAliases(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
// 读取指定包下的所有以.class结尾的并且是superType子类的类文件,将这些类放到集合中
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);
}
}
}
下面看下ResolverUtil.find
public ResolverUtil<T> find(Test test, String packageName) {
// 将包路径的.使用/进行替换,找到包对应的目录路径
String path = getPackagePath(packageName);
try {
// 显示对应目录下的所有文件
List<String> children = VFS.getInstance().list(path);
for (String child : children) {
// 过滤出文件名后缀是class的,并且使用Test来做进一步的筛选
if (child.endsWith(".class")) {
addIfMatching(test, child);
}
}
} catch (IOException ioe) {
log.error("Could not read package: " + packageName, ioe);
}
return this;
}
protected void addIfMatching(Test test, String fqn) {
try {
// 将路径名切换成包名
String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
ClassLoader loader = getClassLoader();
if (log.isDebugEnabled()) {
log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
}
Class<?> type = loader.loadClass(externalName);
if (test.matches(type)) {
matches.add((Class<T>) type);
}
} catch (Throwable t) {
log.warn("Could not examine class '" + fqn + "'" + " due to a "
+ t.getClass().getName() + " with message: " + t.getMessage());
}
}
public void registerAlias(Class<?> type) {
// 获取类名
String alias = type.getSimpleName();
// 获取类上的@Alias注解,如果使用了@Alias注解,那么会使用注解的value作为别名
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
alias = aliasAnnotation.value();
}
registerAlias(alias, type);
}
主要就是根据指定的报名,遍历对应目录下的所有类文件,然后判断类上是否使用了@Alias注解,如果使用了该注解,那么会使用该注解的value作为别名,否则会使用类名作为别名,而且这里会判断该别名是否被使用
typeAlias标签
public void registerAlias(String alias, Class<?> value) {
if (alias == null) {
throw new TypeException("The parameter alias cannot be null");
}
// issue #748
// 将别名转换成小写
String key = alias.toLowerCase(Locale.ENGLISH);
// 校验之前是否有存放过相同别名
if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
}
typeAliases.put(key, value);
}
pluginElement
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
// 遍历子节点
for (XNode child : parent.getChildren()) {
// 解析节点的Interceptor属性,该属性代表了插件类的包名
String interceptor = child.getStringAttribute("interceptor");
// 遍历当前节点的所有子节点,读取每个节点的name和value属性,设置到properties中
Properties properties = child.getChildrenAsProperties();
// 生成interceptor实例
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
// 将interceptor配置设置到intercetpor实例中,我们自己实现的interceptor可以通过这种设置properties的方法传一些interceptor的参数进来
interceptorInstance.setProperties(properties);
// 将interceptor设置到configuration中
configuration.addInterceptor(interceptorInstance);
}
}
}
下面看下Configuration的addInterceptor方法
主要就是调用InterceptorChain类的addInterceptor方法
public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}
environmentElement
在没有结合使用Mybatis和spring之前,如果我们需要为不同的环境使用不同的数据库相关的配置,我们可以配置文件中使用environments标签,其中的每个子标签environment代表一个环境,其中包含该环境的一些数据库配置
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="" value=""/>
</transactionManager>
<dataSource type="UNPOOLED">
<property name="driver" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:mem:automapping"/>
<property name="username" value="sa"/>
</dataSource>
</environment>
</environments>
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
// 这里会判断是否指定了环境,如果没有指定环境,那么会使用environments标签上的default属性设置的值
if (environment == null) {
environment = context.getStringAttribute("default");
}
// 将当前生效的环境配置设置到configuration中
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
databaseIdProviderElement
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql" />
<property name="Oracle" value="oracle" />
</databaseIdProvider>
<mapper namespace="com.yihaomen.mybatis.dao.DatabaseIdProviderMapper">
<select id="selectTime" resultType="String" databaseId="mysql">
SELECT NOW() FROM dual
</select>
<select id="selectTime" resultType="String" databaseId="oracle">
SELECT 'oralce'||to_char(sysdate,'yyyy-mm-dd hh24:mi:ss') FROM dual
</select>
</mapper>
当使用的是mysql数据库时,当通过Mapper来执行selectTime方法时,会执行SELECT NOW() FROM dual,可以看出,dataBaseIdProvider的作用就是用来适配不同的数据库产品
private void databaseIdProviderElement(XNode context) throws Exception {
DatabaseIdProvider databaseIdProvider = null;
if (context != null) {
String type = context.getStringAttribute("type");
// awful patch to keep backward compatibility
if ("VENDOR".equals(type)) {
type = "DB_VENDOR";
}
Properties properties = context.getChildrenAsProperties();
// resolveClass会将type作为别名找到对应的类,这里会找到type对应的DataBaseIdProvider的实现类,然后实例化
databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor().newInstance();
databaseIdProvider.setProperties(properties);
}
Environment environment = configuration.getEnvironment();
if (environment != null && databaseIdProvider != null) {
// 使用DataBaseIdProvider来根据当前环境的数据源来获取对应数据库产品的dataBaseId,然后设置到configuration中
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
configuration.setDatabaseId(databaseId);
}
}
看下DataBaseIdProvider接口的定义,可以看到最主要的方法就是getDatabaseId,根据数据源类型来返回对应的数据库产品的id
public interface DatabaseIdProvider {
default void setProperties(Properties p) {
// NOP
}
String getDatabaseId(DataSource dataSource) throws SQLException;
}
typeHandlerElement
这里的主要做的就是解析配置文件中的typeHandlers标签下的所有typeHandler子标签,每个子标签代表java类型和jdbc类型之间的类型转换器
这里的处理和typeAliasElement的处理类似,也提供了两种方法,一种是通过使用package子标签,来对某个包下的类进行批量注册,另外一种就是使用typeHandler节点,来注册一个
private void typeHandlerElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeHandlerPackage = child.getStringAttribute("name");
typeHandlerRegistry.register(typeHandlerPackage);
} else {
String javaTypeName = child.getStringAttribute("javaType");
String jdbcTypeName = child.getStringAttribute("jdbcType");
String handlerTypeName = child.getStringAttribute("handler");
Class<?> javaTypeClass = resolveClass(javaTypeName);
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
Class<?> typeHandlerClass = resolveClass(handlerTypeName);
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
package标签
public void register(String packageName) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
// 扫描指定的包名下面的类,会将包名转换成路径名,然后遍历每个类,判断是欧服是TypeHandler的子类
resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
for (Class<?> type : handlerSet) {
//Ignore inner classes and interfaces (including package-info.java) and abstract classes
if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
register(type);
}
}
}
typeHandler标签
这里就是解析typeHandler节点的javaType,jdbcType,handler属性,然后解析出对应的类,然后注册到map中
String javaTypeName = child.getStringAttribute("javaType");
String jdbcTypeName = child.getStringAttribute("jdbcType");
String handlerTypeName = child.getStringAttribute("handler");
Class<?> javaTypeClass = resolveClass(javaTypeName);
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
Class<?> typeHandlerClass = resolveClass(handlerTypeName);
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
mapperElement
这里做的就是解析mappers标签的所有子标签,将对应的mapper配置文件或者mapper类注册进来
这里和之前的typeHandler和typeAlias的注册相同,注册形式也分为两种,一种是通过扫描指定包下面的所有类,另外一种就是使用一个标签来注册指定的mapper
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} 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);
try(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);
try(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.");
}
}
}
}
}
packages标签
扫描指定包下的所有类,将这些类作为Mapper类,并且解析这些Mapper类上的注解,然后注册进来
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
Configuration的addMappers会调用MapperRegistry的addMappers
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
然后和typeAlias和typeHandler的处理类似,使用ResolverUtils来扫描指定包下的所有类,这里的过滤条件是只要是类就可以
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
public <T> void addMapper(Class<T> type) {
// mapper必须是接口
if (type.isInterface()) {
// 判断是否已经注册过
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// MapperProxyFactory用来生成Mapper接口的实现类,实现类主要做的就是当调用Mapper的方法时,会调用配置的sql
// MapperProxyFactory的源码分析之前的文章中有
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
// 对Mapper类进行解析
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
mapper标签
这里主要读取mapper标签上的resource,url和class属性,前面两种属性指定了xml配置文件的路径,class属性则指定了Mapper接口的路径
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);
try(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);
try(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.");
}