一、引入
1.使用案例
<!-- Mybatis依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<!-- MySQL依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
<scope>runtime</scope>
</dependency>
private SqlSessionFactory getFactory(){
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
return factory;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Test
public void m1(){
SqlSessionFactory factory = getFactory();
SqlSession sqlSession = factory.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
List<Car> cars = mapper.listAll();
}
2.猜想mybatis是如何设计的
上面很简单的几行代码,介绍了如何单独使用mybatis,那么我们猜想一下mybatis
的底层是如何运行的?
1.定位到mybatis-config.xml
并读取装载。获取输入流InputStream
2.解析输入流InputStream
,把mybatis-config.xml
配置文件和mapper
文件中的配置项解析,校验,保存起来
3.创建sqlSessionFactory
对象,在我们的印象里,session
就是一次会话,所以我们可以理解sqlSessionFactory
就是个工厂类,就专门创建sqlSession
对象,并且这个sqlSessionFactory
工厂类是唯一不变的(单例)。
4.创建sqlSession
,SqlSession
中肯定保存了配置文件内容信息和执行数据库相关的操作。
5.获取userMapper
对象,但是UserMapper
是接口,并且没有实现类。怎么就可以调用其方法呢?这里猜想可能用到了动态代理。
6.userMapper
接口中的方法是如何关联到SQL的,这个猜想可能是有个专门映射的类,另外,肯定使用到了接口全路径名+方法名称,这个才能确保方法和SQL关联(主要是使用的时候,都是方法名必须和SQL中statementId一致,由此猜想的)。
7.最后底层使用JDBC去操作数据库。
8.作为一个持久化框架,很有可能会使用到缓存,用来存储每次查询数据。
二、XMLConfigBuilder
1.调用链
上面的案例代码中我们看到了SqlSessionFactoryBuilder
类通过加载配置文件mybatis-config.xml
,创建了SqlSessionFacotry
工厂类,SqlSessionFactoryBuilder
该类主要的作用就是创建工厂对象,于是需要通过代码读取mybatis-config.xml
,读取文件是需要XMLConfigBuilder
类,通过parse()
来初始化,然后通过build()
来把configuration
保存为全局对象
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null);
}
public SqlSessionFactory build(Reader reader, Properties properties) {
return build(reader, null, properties);
}
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
// 创建XMLConfigBuilder对象
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 调用parse()方法
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.
}
}
}
}
// 上面的代码通过build()把生成的 configuration 设置成全局对象
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
上面代码首先创建了XMLConfigBuilder
类,首先我们看一下其构造方法,就是进行了一些属性赋值,然后调用了parse()
,该方法加载configuration
节点下的子节点,然后传入parseConfiguration()
,通过该方法加载全局配置文件
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;
}
//外部调用此方法对mybatis全局配置文件进行解析
public Configuration parse() {
// 全局配置文件只允许被加载一次
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 加载configuration标签 并且传入方法
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
//此方法就是解析configuration节点下的子节点
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
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);
}
}
从上面的代码中我们可以看到mybatis-config.xml
允许我们设置的属性,同样我们也可以从org.apache.ibatis.builder.xml
的mybatis-3-config.dtd
中看到,此文件我们在jar包找到,也可以在mapper
映射文件上面的引用可以找到,它其实是一个编码提醒和约束
<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>
2.全局配置Configuration
Mybatis会把配置都存入到Configuration
里面,作用域为全局,所以会把加载mybatis-config.xml
后的全部配置都存入到configuration
里面
Mybatis配置体系
我们另说一下该类的结构,首先从XMLConfigBuilder
的构造方法开始
// 我们该类来读取配置文件
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
// 重点来这里,我们在这里调用了父类的构造方法,然后创建了一个Configuration对象
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
接下来我们看一下XMLConfigBuilder
的父类BaseBuilder
,构造方法就是进行了简单的赋值,我们的configuration
也是存储在这里,那么为什么会把configuration
存储在这里,放在XMLConfigBuilder
里面不好吗,BaseBuilder
的子类不光有XMLConfigBuilder
,还有XMLMapperBuilder
等等,各个XXXBuild结尾的类都是为了创建,然后把创建的结果统一存储到父类里面,这样正是为了实现配置全局统一
public abstract class BaseBuilder {
// 配置对象在父类中
protected final Configuration configuration;
protected final TypeAliasRegistry typeAliasRegistry;
protected final TypeHandlerRegistry typeHandlerRegistry;
// 构造方法
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
// 该类和configuration里面的类型别名注册 为同一个
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
// 类型解析器也为同一个
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
// 其余代码省略
}
上面,接下来我们看一下Configuration
结构
package org.apache.ibatis.session;
public class Configuration {
/**
* MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中,
* 比如设置不同的开发、测试、线上配置,在每个配置中可以配置事务管理器和数据源对象.
*/
protected Environment environment;
// 允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为false。
protected boolean safeRowBoundsEnabled = false;
// 允许在嵌套语句中使用分页(ResultHandler)。如果允许使用则设置为false
protected boolean safeResultHandlerEnabled = true;
// 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。
protected boolean mapUnderscoreToCamelCase = false;
// 当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载(参考lazyLoadTriggerMethods).
protected boolean aggressiveLazyLoading = true;
// 是否允许单一语句返回多结果集(需要兼容驱动)
protected boolean multipleResultSetsEnabled = true;
// 允许 JDBC 支持自动生成主键,需要驱动兼容。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。
protected boolean useGeneratedKeys = false;
// 使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。
protected boolean useColumnLabel = true;
//配置全局性的cache开关
protected boolean cacheEnabled = true;
/*指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。
注意基本类型(int、boolean等)是不能设置成 null 的。*/
protected boolean callSettersOnNulls = false;
//指定 MyBatis 增加到日志名称的前缀。
protected String logPrefix;
//指定 MyBatis 所用日志的具体实现,未指定时将自动查找
protected Class <? extends Log> logImpl;
/*MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。
默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。
若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。*/
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
/*当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。
某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。*/
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
//指定哪个对象的方法触发一次延迟加载。
protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
//设置超时时间,它决定驱动等待数据库响应的秒数。
protected Integer defaultStatementTimeout;
/*配置默认的执行器。
SIMPLE 就是普通的执行器;
REUSE 执行器会重用预处理语句(prepared statements);
BATCH 执行器将重用语句并执行批量更新。*/
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
/**
* 指定 MyBatis 应如何自动映射列到字段或属性。
* NONE 表示取消自动映射;
* PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。
* FULL 会自动映射任意复杂的结果集(无论是否嵌套)。
*/
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
//这里配置的属性可以在整个配置文件中使用来替换需要动态配置的属性值
protected Properties variables = new Properties();
//对象创建工厂,默认的实现类DefaultObjectFactory,用来创建对象,比如传入List.class,利用反射返回ArrayList的实例
protected ObjectFactory objectFactory = new DefaultObjectFactory();
//对象包装工厂,默认实现类是DefaultObjectWrapperFactory,包装Object实例
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
//注册Mapper
protected MapperRegistry mapperRegistry = new MapperRegistry(this);
//延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。
protected boolean lazyLoadingEnabled = false;
//指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具
protected ProxyFactory proxyFactory;
//数据库类型id,MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性
protected String databaseId;
/**
* 指定一个提供Configuration实例的类. 这个被返回的Configuration实例是用来加载被反序列化对象的懒加载属性值.
* 这个类必须包含一个签名方法static Configuration getConfiguration(). (从 3.2.3 版本开始)
*/
protected Class<?> configurationFactory;
//拦截器链
protected final InterceptorChain interceptorChain = new InterceptorChain();
//TypeHandler注册
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
//别名和具体类注册
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
//这个是指定解析的驱动,比如你可以使用velocity模板引擎来替代xml文件,默认是XMLLanguageDriver,也就是使用xml文件来写sql语句
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
//对应Mapper.xml里配置的Statement
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
//对应Mapper.xml里配置的cache
protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
//对应Mapper.xml里的ResultMap
protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
//对应Mapper.xml里的ParameterMap
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
//主键生成器
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");
//存储已经加载过的mapper xml资源,见MapperAnnotationBuilder#loadXmlResource
protected final Set<String> loadedResources = new HashSet<String>();
//存储已经解析过的mapper对应的xml节点
protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
//存储所有未处理的
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
//存储所有未处理的缓存信息
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
//存储所有未处理ResultMap的映射信息
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();
/*
* A map holds cache-ref relationship. The key is the namespace that
* references a cache bound to another namespace and the value is the
* namespace which the actual cache is bound to.
*/
protected final Map<String, String> cacheRefMap = new HashMap<String, String>();
public Configuration(Environment environment) {
this();
this.environment = environment;
}
public Configuration() {
//通过使用TypeAliasRegistry来注册一些类的别名
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);
}
}
上面我们看到通过XMLConfigBuilder
加载配置,然后封装到configuration
里面,那么接下来我们看一下是如何加载,这里面会介绍一些常用的配置项
3.属性(properties)源码
上面我们看到parseConfiguration()
中调用了propertiesElement()
加载properties
节点,通过该方法我们看到加载属性的途径,也看到其优先级的顺序,接下来我们看一下源码:
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
// 获取全部子节点,getChildrenAsProperties()也是通过遍历来获取
Properties defaults = context.getChildrenAsProperties();
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属性
if (resource != null) {
// 这里看到将外部配置文件put到defaults,如果key相同会进行覆盖,
// 代表外部配置文件的优先级比配置文件的要高
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
// variables在我们初始化的时候,可以通过其构造方法传入,但是也可以为空
// 其为configuration全局作用域的配置属性
Properties vars = configuration.getVariables();
if (vars != null) {
// 通过这里可以看到 通过方法传入的属性 是最高的
defaults.putAll(vars);
}
// 设置到转换类里面
parser.setVariables(defaults);
// 设置到全局配置configuration里面
configuration.setVariables(defaults);
}
}
4.设置(settings)源码
看一下关于settings的源码,发现用到了两行
Properties settings = settingsAsProperties(root.evalNode("settings"));
settingsElement(settings);
settingsAsProperties()
用于获取设置,并且判断该设置是否存在
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
// 创建Configuration的反射对象 然后下面赋值的时候 判断值是否存在
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
// 通过循环 遍历该设置是否存在
for (Object key : props.keySet()) {
// metaConfig是上面创建的,包含类的信息,因为我们需要借助反射来存储类信息
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;
}
settingsElement()
添加设置,发现其源码就是把设置添加到configuration
中,如果值不存在就设置默认值
private void settingsElement(Properties props) throws Exception {
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.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")));
@SuppressWarnings("unchecked")
Class<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>)resolveClass(props.getProperty("defaultEnumTypeHandler"));
configuration.setDefaultEnumTypeHandler(typeHandler);
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"));
@SuppressWarnings("unchecked")
Class<? extends Log> logImpl = (Class<? extends Log>)resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}
5.类型别名(typeAliases)源码
通过typeAliasesElement()
加载别名,方法很简单,就是循环遍历
private void typeAliasesElement(XNode parent) {
if (parent != null) {
// 循环节点
for (XNode child : parent.getChildren()) {
// 子节点配置的是包
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
// 注册该包下的所有子类
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
// 子节点配置的是具体类
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);
}
}
}
}
}
上面代码其实就是通过遍历注册类,接下来看一下调用注册方法的类TypeAliasRegistry
,在该类的最上面首先看到了一个Map,其中存储我们所有的别名,然后其构造方法默认注册了一些别名,这就是mybatis默认给我们提供的,我们可以直接使用
public class TypeAliasRegistry {
// 所有的别名都存在这个map里
private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();
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);
// 代码太长 省略了一些
}
}
TypeAliasRegistry
这个类一共才一百多行,下面主要就是关于registerAlias()
,有好几个重载,用于各种条件注册,仔细看一下很好理解
// 方法1
// 用于通过报名注册
public void registerAliases(String packageName){
// 调用方法2
registerAliases(packageName, Object.class);
}
// 方法2
// 获取到包名,取到所有子类,遍历注册
public void registerAliases(String packageName, Class<?> superType){
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<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()) {
// 调用方法3
registerAlias(type);
}
}
}
// 方法3
// 通过类型注册,别名自动生成
public void registerAlias(Class<?> type) {
String alias = type.getSimpleName();
// 判断该类存在@Alias注解
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
alias = aliasAnnotation.value();
}
// 调用方法4
registerAlias(alias, type);
}
// 方法4
// 最底层的方法 通过别名和类型注册
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 (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
}
// 这里是重点 添加到map里面
TYPE_ALIASES.put(key, value);
}
// 方法5
// 通过别名和类路径注册
public void registerAlias(String alias, String value) {
try {
registerAlias(alias, Resources.classForName(value));
} catch (ClassNotFoundException e) {
throw new TypeException("Error registering type alias "+alias+" for "+value+". Cause: " + e, e);
}
}
6.插件(plugins)源码
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);
// 添加插件
configuration.addInterceptor(interceptorInstance);
}
}
}
6.对象工厂(objectFactory)源码
看一下这个源码,方法很简单,主要就是初始化对象工厂,然后赋值到全局配置中
private void objectFactoryElement(XNode context) throws Exception {
if (context != null) {
// 获取到自定义的对象工厂类路径
String type = context.getStringAttribute("type");
// 获取到数据
Properties properties = context.getChildrenAsProperties();
// 实例化对象
ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance();
factory.setProperties(properties);
configuration.setObjectFactory(factory);
}
}
7.环境配置(environments)源码
通过environmentsElement()
来加载环境
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
// 默认环境
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
// 环境主键
String id = child.getStringAttribute("id");
// isSpecifiedEnvironment() 该方法主要去匹配environment和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());
}
}
}
}
通过dataSourceElement()
方法创建数据源工厂
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
// 获取数据库类型
String type = context.getStringAttribute("type");
// 子节点
Properties props = context.getChildrenAsProperties();
// 通过resolveClass解析类然后通过反射创建
// resolveClass()该方法在TypeAliasRegistry类,因为type可能是别名,所以把方法放到了这个类里面
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
8.数据库厂商标识(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
// 糟糕的补丁保持向后兼容性;可能由于之前的版本不是DB_VENDOR,所以加上这段代码。
if ("VENDOR".equals(type)) {
type = "DB_VENDOR";
}
Properties properties = context.getChildrenAsProperties();
databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
databaseIdProvider.setProperties(properties);
}
// 从environment中获取数据源,交给databaseIdProvider获取数据库ID。
Environment environment = configuration.getEnvironment();
if (environment != null && databaseIdProvider != null) {
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
configuration.setDatabaseId(databaseId);
}
}
9.类型处理器(typeHandlers)源码
通过typeHandlerElement()
来加载类型处理器,该方法和类型别名的方法大体一致,也是循环遍历注册
private void typeHandlerElement(XNode parent) throws Exception {
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
Class<?> typeHandlerClass = resolveClass(handlerTypeName);
// 判断注册
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
上面会把类型处理器注册到typeHandlerRegistry
,接下来我们看一下这个类,首先同样在该类的最上面,也是看到了一些Map,然后其构造方法也是默认注册了一些类型处理器,这个Mybatis默认给我们提供的
public final class TypeHandlerRegistry {
// key为jdbc的类型
private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
// key为Java类型 value为map 代表1个Java类型对应多个jdbc类型
private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();
// 未知的类型处理器
private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);
// key为类型 value为类型处理器
private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();
private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
public TypeHandlerRegistry() {
register(Boolean.class, new BooleanTypeHandler());
register(boolean.class, new BooleanTypeHandler());
register(JdbcType.BOOLEAN, new BooleanTypeHandler());
register(JdbcType.BIT, new BooleanTypeHandler());
register(Byte.class, new ByteTypeHandler());
register(byte.class, new ByteTypeHandler());
register(JdbcType.TINYINT, new ByteTypeHandler());
register(Short.class, new ShortTypeHandler());
register(short.class, new ShortTypeHandler());
register(JdbcType.SMALLINT, new ShortTypeHandler());
register(Integer.class, new IntegerTypeHandler());
register(int.class, new IntegerTypeHandler());
register(JdbcType.INTEGER, new IntegerTypeHandler());
register(Long.class, new LongTypeHandler());
register(long.class, new LongTypeHandler());
// 省略一些代码
}
我们继续看TypeHandlerRegistry
下面的注册方法register()
,同样包含很多重载方法
// 注册
// 这里会检测一个@MappedJdbcTypes注解
private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
if (mappedJdbcTypes != null) {
for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
register(javaType, handledJdbcType, typeHandler);
}
if (mappedJdbcTypes.includeNullJdbcType()) {
register(javaType, null, typeHandler);
}
} else {
register(javaType, null, typeHandler);
}
}
// 其它的重载方法都会调用到该方法
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
if (javaType != null) {
// 从map中取出
Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
// 如果不存在 则创建 存入
if (map == null || map == NULL_TYPE_HANDLER_MAP) {
map = new HashMap<JdbcType, TypeHandler<?>>();
TYPE_HANDLER_MAP.put(javaType, map);
}
// 存在则直接put
map.put(jdbcType, handler);
}
// 上面没有java类型 则按照class存入map
ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
}
// 这里会检测一个@MappedTypes注解
public void register(Class<?> typeHandlerClass) {
boolean mappedTypeFound = false;
MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
if (mappedTypes != null) {
for (Class<?> javaTypeClass : mappedTypes.value()) {
register(javaTypeClass, typeHandlerClass);
mappedTypeFound = true;
}
}
if (!mappedTypeFound) {
register(getInstance(null, typeHandlerClass));
}
}
10.映射文件(mapper)源码
通过mapperElement()
加载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");
// 第一种情况 通过resource获取mapper
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) {
// 第二种情况 通过url获取mapper
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) {
// 第三种情况 通过类路径获取mapper
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.");
}
}
}
}
}