Mybatis.xml
在SqlSessionFactoryBuilder中有一个build方法入口,用来解析配置文件。 这里传入配置文件的输入流,也能使用不同的解析方式,传入对应的环境,或者具体的参数。
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
在这之后,它会创建一个XML解析器来解析xml文件。调用parse()方法。parse()会返回一个Configuration对象.mybatis用的解析器是java自带的dom解析
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//创建解析器
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//parse()用来解析xml里面的节点
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
最后将这个SqlSessionFactory返回,我们就能用这个Factory来创建Session了。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
我们开始分析具体的解析流程,XMLConfigBuilder调用自身的parse方法来进行xml的解析。 在无参的parse()方法中,它需要先检查是否已经解析过了,parsed默认为false,是SqlSessionFactoryBuilder中的成员变量,当解析完后将其置为true。
当然你也可以创建新的SqlSessionFactoryBuilder来再次解析xml,这么做是没有必要的。
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
在判断完之后,开始解析节点,我们传入根节点configuration来进行解析。mybatis配置文件中的节点顺序是不能进行颠倒的,从下面的代码也能看出节点的解析是按照顺序。最先解析的是properties,最末是mappers。
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
// 所谓别名 其实就是吧你指定的别名对应的class存储在一个Map当中
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
//objectFactory自定义实例化对象的行为 比如说返回User 对象
objectFactoryElement(root.evalNode("objectFactory"));
//MateObject 方便反射操作实体类的对象
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
properties节点
使用方式
方法一:
<properties>
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test1"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>
方法二:
<!--这里也能化成url-->
<properties resource="db.properties"></properties>
db.properties文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/sql05
username=root
password=123
解析源码
//这里传入properties节点
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
//解析properties节点下面的子节点,并记录property节点
Properties defaults = context.getChildrenAsProperties();
//获取使用resource的配置方式
String resource = context.getStringAttribute("resource");
//获取使用url的配置方式
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
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
//解析url
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
//获取配置文件的其他成员变量,也就是configuration类中的Properties对象。
//这时候还没有解析其他的节点,所以Properties是空的。将解析好的Properties节点数据存入到configuration类中的Properties对象中。
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
这里还需要注意的是,由于Properties的父类是hashtable,所以当执行putAll方法时,会覆盖已存在的同名Node节点信息。 由于是先解析的property子节点,后解析resource或者url节点,所以property节点可能会被覆盖。
如果property中有需要动态替换的值,会在解析resource或者url的时候再进行替换。
settings节点
Mybatis一些配置。
使用方式
<settings>
<!-- 全局的映射器开启或禁用缓存 (默认true) -->
<setting name="cacheEnabled" value="true" />
<!-- 全局开启或管理延迟加载。当开启时,所有关联对象都会延迟加载 (默认false) -->
<setting name="lazyLoadingEnabled" value="true" />
<!--还有很多,不一一列举-->
<!-- ... -->
</settings>
解析源码
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
//解析seethings下面的子节点
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
//这里创建了一个MetaClass
//这是mybatis的工具类用来检查,这里用来configuration中是否有对应的set方法。
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中的参数都放到一个临时容器props中,还没有写入到configuration对象。
private void parseConfiguration(XNode root) {
try {
//...
Properties settings = settingsAsProperties(root.evalNode("settings"));
//这里也是settings中的两个参数,单独进行解析
loadCustomVfs(settings);
loadCustomLogImpl(settings);
//...
//这里才将参数设置到Configuration。因为上文还要解析其他的一些配置。
settingsElement(settings);
//...
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
最后会将这些配置一个个从临时容器中取出,放到configuration中
private void settingsElement(Properties props) {
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}
typeAliases
类型别名可为 Java 类型设置一个缩写名字。 它仅用于XML配置,意在降低冗余的全限定类名书写。
使用方式
方式一:使用包名,mybatis会自动扫名包,并给它一个缩写的别名。如com.entity.User会被自动赋予别名User
<typeAliases>
<package name="com.entity"/>
</typeAliases>
方式二:直接指定别名,这种方式可以手动的将类指定为想要的任意名字,这里我将其指定为MyUser。
<typeAliases>
<typeAlias alias="MyUser" type="com.entity.User"/>
</typeAliases>
当然也能是方式一和方式二两者相结合的方式。
解析源码
private void typeAliasesElement(XNode parent) {
if (parent != null) {
//这里便利子节点,判断是方式一的package类型还是方式二的类型。
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
//如果是包的类型,需要获取包路径,并扫描,最后添加到configuration的别名库中。
String typeAliasPackage = child.getStringAttribute("name");
//TypeAliasRegistry是别名的注册中心,类似的还有TypeHandlerRegistry类型处理器注册中心等
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
//如果是自行指定的别名,那么直接添加到别名库中,当然如果不指定别名,那么使用的就是全路径类名。
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
// com.luban.DemoMapper alias = DemoMapper type :com.luban.DemoMapper
try {
//这里使用反射去获取类,如果获取不到会抛出ClassNotFoundException异常
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);
}
}
}
}
}
在注册别名时,会将其转化为小写。如果发现同名的情况,会直接抛出异常。
public void registerAlias(String alias, Class<?> value) {
if (alias == null) {
throw new TypeException("The parameter alias cannot be null");
}
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);
}
包扫描代码,能扫描包里面的所有子包以及其下的所有java文件
//TypeAliasRegistry.registerAliases
public void registerAliases(String packageName) {
registerAliases(packageName, Object.class);
}
//TypeAliasRegistry.registerAliases
public void registerAliases(String packageName, Class<?> superType) {
//ResolverUtil用来解析并校验包下面的类
//ResolverUtil.IsA是一个内部类,用来校验当前类是否是superType类或者superType的子类
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
//resolverUtil.find用来查找包下面的,superType的所有子类.是这里传入的是Object,那也就是查找所有的java类
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
for (Class<?> type : typeSet) {
// 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) {
//获取包的路径 如com/entity
String path = getPackagePath(packageName);
try {
//这个方法获取包下面的所有文件的路径 + 名称
List<String> children = VFS.getInstance().list(path);
for (String child : children) {
//判断是否是Java的class文件
if (child.endsWith(".class")) {
//判断是否是待检查父类(Object)的子类,如果是,则添加到set集合中
addIfMatching(test, child);
}
}
} catch (IOException ioe) {
log.error("Could not read package: " + packageName, ioe);
}
return this;
}
//VSF.list
public List<String> list(String path) throws IOException {
List<String> names = new ArrayList<>();
for (URL url : getResources(path)) {
names.addAll(list(url, path));
}
return names;
}
获取当前线程的classLoader来解析路径,最后使用java的java.lang.ClassLoader.getResources来解析路径
protected static List<URL> getResources(String path) throws IOException {
return Collections.list(Thread.currentThread().getContextClassLoader().getResources(path));
}
除了我们手动配置的别名之外,别名注册器TypeAliasRegistry还会在构造方法中初始化一些常用的别名。这里就不作展示。
plugin
使用方式
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
解析源码
其中property参数能在setProperties(Properties properties)中获取到。 这部分代码就是初始化一个拦截器,并添加到拦截器链中。不熟悉拦截器的朋友可以返回章节一查看。
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
interceptorInstance.setProperties(properties);
//调用InterceptorChain.addInterceptor
configuration.addInterceptor(interceptorInstance);
}
}
}
objectFactory
Mybatis有一个对象工厂DefaultObjectFactory来创建结果对象的实例。如果我们需要自定义的创建对象,就可以声明一个对象工厂。这个配置一般较少使用。
使用方式
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
<property name="someProperty" value="100"/>
</objectFactory>
这里就是初始化工厂实例,并将这个工厂实例塞到Configuration中,替换原来默认的工厂。
private void objectFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties properties = context.getChildrenAsProperties();
ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();
factory.setProperties(properties);
configuration.setObjectFactory(factory);
}
objectWrapperFactory
使用方式
提供一个包装器包装器工厂,来根据具体的java对象,返回包装器。默认DefaultObjectWrapperFactory中的方法是空的。自定义的包装器需要实现ObjectWrapper接口。
<objectWrapperFactory type="com.wrapperfactory.MapWrapperFactory"/>
解析源码
//包装器工厂。
public interface ObjectWrapperFactory {
//判断该对象是否有包装器
boolean hasWrapperFor(Object object);
//返回对象的包装器
ObjectWrapper getWrapperFor(MetaObject metaObject, Object object);
}
代码与对象工厂类似,简单的实例化对象。
private void objectWrapperFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance();
configuration.setObjectWrapperFactory(factory);
}
}
reflectorFactory
用来缓存Reflector的工厂,Reflector类用来计入java类的信息,如get,set,方法信息等。
使用方式
<reflectorFactory type="com.reflectorFactory.MyReflectorFactory"/>
解析源码
public interface ReflectorFactory {
//缓存是否开启
boolean isClassCacheEnabled();
//设置缓存开关
void setClassCacheEnabled(boolean classCacheEnabled);
//查找对应的缓存信息
Reflector findForClass(Class<?> type);
}
代码与对象工厂类似,简单的实例化对象。
private void reflectorFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
ReflectorFactory factory = (ReflectorFactory) resolveClass(type).getDeclaredConstructor().newInstance();
configuration.setReflectorFactory(factory);
}
}
environments
环境参数能让我们方便的在测试,开发,生产环境进行切换。其中default参数用来设置默认的环境。
使用方式
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理 -->
<transactionManager type="JDBC" />
<!-- 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://192.168.101.14:3306/test?useUnicode=true&characterEncoding=utf-8" />
<property name="username" value="jeesite4test" />
<property name="password" value="jeesite4test" />
</dataSource>
</environment>
</environments>
解析源码
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
//先去查找默认的环境id,这里指代上文的development
if (environment == null) {
environment = context.getStringAttribute("default");
}
//遍历所有的环境
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
//isSpecifiedEnvironment用来判断default的环境ID是否是当前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中
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
解析事务管理器代码
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
//别名库中注册了两种类型,
//typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
//typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
//如果当前节点为空,就会抛出没有指定事务管理器的异常
throw new BuilderException("Environment declaration requires a TransactionFactory.");
}
解析数据源代码
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
//别名库中有三种类型 EJB 或应用服务器这类容器中使用,使用连接池,不用连接池
//typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
//typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
//typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
databaseIdProvider
数据库厂商标识
使用方式
<databaseIdProvider type="DB_VENDOR" />
解析源码
private void databaseIdProviderElement(XNode context) throws Exception {
DatabaseIdProvider databaseIdProvider = null;
if (context != null) {
String type = context.getStringAttribute("type");
//这里是兼容代码
if ("VENDOR".equals(type)) {
type = "DB_VENDOR";
}
//解析子节点
Properties properties = context.getChildrenAsProperties();
//从别名库中获取
//typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor().newInstance();
databaseIdProvider.setProperties(properties);
}
//获取环境,如果不为空而且数据库厂商不为空,那么设置数据库ID
Environment environment = configuration.getEnvironment();
if (environment != null && databaseIdProvider != null) {
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
configuration.setDatabaseId(databaseId);
}
}
typeHandlers
类型处理器,为了将java类型和数据库类型进行映射。列入我们在进行占位符传参的时候,或者我们在查询完结果后,需要将数据库类型和java类型进行转化。
使用方式
方式一:
<typeHandlers>
<typeHandler handler="com.handelr.ExampleTypeHandler"/>
</typeHandlers>
方式二:
<typeHandlers>
<package name="com.handelr"/>
</typeHandlers>
也能使用方式一和方式二相结合的方式
解析源码
private void typeHandlerElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//解析package节点
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");
//从别名库中查找java的类型
Class<?> javaTypeClass = resolveClass(javaTypeName);
//从数据库枚举类型中查找对应的类型
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
//将我们配置的handler注册到别名库typeAliasRegistry,并返回对应的class.如果找不到会抛出异常.
Class<?> typeHandlerClass = resolveClass(handlerTypeName);
//最后添加到类型处理器注册中心中
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
包扫描代码,与扫描别名包类似,不在过多分析
public void register(String packageName) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
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);
}
}
}
mappers
使用方式
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
解析源码
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
//遍历解析mappers节点
for (XNode child : parent.getChildren()) {
//判断节点是否是package
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
//其他类型,resource,url,class.且每个mapper只能有其中一种类型,否则到最后的else抛出异常.
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
//resource类型
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
//解析resource.xml
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
//url类型
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类型,直接查找class并添加到configuration中的mapperRegistry里
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.");
}
}
}
}
}
包解析,与解析别名包typeAliases和解析类型处理器typeHandlers的类似,superType也是Object。
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);
}
}
package的方式最后调用addMapper方法.与解析class的方式相同,最后都是调用MapperRegistry.addMapper。而resource和url的方式调用了XMLMapperBuilder.parse。我们逐个分析。
MapperRegistry.addMapper
public <T> void addMapper(Class<T> type) {
//判断该类是否是接口,dao层的类是接口类型的。如果不是那就不做处理,忽略。
if (type.isInterface()) {
//如果已经注册过了,抛出异常
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//这里先保存,如果下文parse解析出现异常,再移除
knownMappers.put(type, new MapperProxyFactory<>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
//解析dao对应的xml文件
parser.parse();
loadCompleted = true;
} finally {
//如果出现异常,移除
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
MapperAnnotationBuilder.parse. MapperAnnotationBuilder类用来解析接口上面的注解
public void parse() {
//resource 是dao层接口的路径
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
//这里会根据dao的路径找到xml的路径
loadXmlResource();
//添加到set集合中
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
//解析注解@CacheNamespace缓存配置
parseCache();
//解析注解@CacheNamespaceRef
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// 判断是否是桥接方法,如果不是,需要进行解析.
if (!method.isBridge()) {
//解析该方法上面的注解
parseStatement(method);
}
} catch (IncompleteElementException e) {
//如果发生异常,并不是退出,加入到一个解析失败的集合中
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
//该方法对解析失败的方法进行二次解析,如果解析成功,那就移除集合,失败就忽略异常,不做处理.
parsePendingMethods();
}
mapper并不是解析失败就抛出异常,而是会加入到一个失败队列中进行多次的重试
MapperAnnotationBuilder.loadXmlResource
private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
//这里判断是否已经解析过这个命名空间
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
//这里将用类路径来寻找xml文件,两个路径要对应起来才能找到
String xmlResource = type.getName().replace('.', '/') + ".xml";
// #1347
InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
if (inputStream == null) {
// Search XML mapper that is not in the module but in the classpath.
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e2) {
// ignore, resource is not required
}
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
//最后面调用了XMLMapperBuilder.parse方法
xmlParser.parse();
}
}
}
XMLMapperBuilder.parse 也是resource和url的方式最后调用的方法
public void parse() {
//这里解析整个mapper.xml,先判断该文件是否呗解析过
if (!configuration.isResourceLoaded(resource)) {
//传入mapper根节点进行解析
configurationElement(parser.evalNode("/mapper"));
//添加到集合中
configuration.addLoadedResource(resource);
//绑定Namespace里面的Class对象
bindMapperForNamespace();
}
//重新解析之前解析不了的节点
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
如果是resource和url两种方式,在解析完xml文件后就会结束了.然而package和class的两种方式还需要解析接口上面的Mybatis注解.
mapper.xml文件解析
在mapper.xml中,会有一下几种类型的子节点
cache – 该命名空间的缓存配置。
cache-ref – 引用其它命名空间的缓存配置。
resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
parameterMap – 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。
sql – 可被其它语句引用的可重用语句块。
insert – 映射插入语句。
update – 映射更新语句。
delete – 映射删除语句。
select – 映射查询语句
这里都是获取子节点值的常规操作,解析对应的节点,并将其添加到Configuration类中
private void configurationElement(XNode context) {
try {
//mapper的缓存信息,命名空间等会被临时保存到MapperBuilderAssistant中,最后把这些公用的信息在存到MappedStatement中
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"));
//该节点已经被废弃,尽量不要使用
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
//在解析增删改查节点时,每个节点都会生成一个mapperStatement对象并保存到configuration类中.
//mapperStatement保存这这个节点的全部信息,如id,fetchSize,timeout
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);
}
}
XMLMapperBuilder.buildStatementFromContext
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//解析xml节点
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
//xml语句有问题时 存储到集合中 等解析完能解析的再重新解析
configuration.addIncompleteStatement(statementParser);
}
}
}
解析增删改查.在解析增删改查节点时,每个节点都会生成一个mapperStatement对象并保存到配置文件类中。
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
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);
//将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false。
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
//替换Includes标签为对应的sql标签里面的值
includeParser.applyIncludes(context.getNode());
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
//解析配置的自定义脚本语言驱动 例如mybatis plus
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
//解析selectKey
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
//设置主键自增规则
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;
}
//解析Sql 根据sql文本来判断是否需要动态解析 如果没有动态sql语句且 只有#{}的时候 直接静态解析使用?占位 当有 ${} 不解析
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
//暗示驱动程序每次批量返回的结果行数
Integer fetchSize = context.getIntAttribute("fetchSize");
//超时时间
Integer timeout = context.getIntAttribute("timeout");
//引用外部 parameterMap,已废弃
String parameterMap = context.getStringAttribute("parameterMap");
//结果类型
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
//引用外部的 resultMap
String resultMap = context.getStringAttribute("resultMap");
//结果集类型,FORWARD_ONLY|SCROLL_SENSITIVE|SCROLL_INSENSITIVE 中的一种
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
//(仅对 insert 有用) 标记一个属性, MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
interface mapper接口解析
这里的方法与解析xml的类似,只不过数据是从注解中获取的。不在分析
void parseStatement(Method method) {
Class<?> parameterTypeClass = getParameterType(method);
LanguageDriver languageDriver = getLanguageDriver(method);
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
Options options = method.getAnnotation(Options.class);
final String mappedStatementId = type.getName() + "." + method.getName();
Integer fetchSize = null;
Integer timeout = null;
StatementType statementType = StatementType.PREPARED;
ResultSetType resultSetType = configuration.getDefaultResultSetType();
SqlCommandType sqlCommandType = getSqlCommandType(method);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = !isSelect;
boolean useCache = isSelect;
KeyGenerator keyGenerator;
String keyProperty = null;
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();
if (options.resultSetType() != ResultSetType.DEFAULT) {
resultSetType = options.resultSetType();
}
}