如同前两篇所说,mybatis提供了一个Builder构建SqlSessionFactory对象
org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties)
而在构建SqlSessionFactoryBuilder对象时,最重要的是生成Configuration对象,这个Configuration是mybatis的集大成者,基本所有重要的接口都在这个里面了
以下进行一些自己看源码的见解
Configuration对象的构建入口在
org.apache.ibatis.builder.xml.XMLConfigBuilder#parse
当然了你也完成可以自己new,但是这个是我们使用mybatis时一种比较常用的方式,会配合对mybatis-config.xml的解析步骤
XMLConfigBuilder采用了建造者设计模式来封装解析xml里面的Configuration对象,后面你就会发现mybatis经常使用这种建造者设计模式
先看下parse方法的处理
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
其实最重要的是parseConfiguration(parser.evalNode("/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);
}
}
是不是一目了然呢,每个子方法去解析不同的xml标签数据,当然了这个解析也是需要api处理的,不过这部分我就没怎么研究了,有兴趣的可以看看对xml是怎么研究的
这一篇我主要是对properties、settings、typeAliases这三个节点的解析说明
首先是properties,看下里面的代码
properties
private void propertiesElement(XNode context) throws Exception {
//如果context标签不为空
if (context != null) {
//首先先加载property的属性
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
//然后是加载配置的url或者resource配置的properties属性的文件
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.");
}
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
//最后是配置用户直接传输进来的
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
//设置到configuration的variables对象中去
parser.setVariables(defaults);
configuration.setVariables(defaults);
//所以属性的优先级为,通过configuration直接输入的-> 配置文件中的 -> xml属性标题中的
}
}
这个是一些环境变量,后面mybatis可以使用的,当然了你也可以使用,只要拿到configuration的setVariables属性即可
配置方法有3中
1. 从mybatis-config.xml配置,格式如下
<properties>
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
2. 从mybatis-config.xml配置,格式如下
<properties resource="org/mybatis/example/config.properties"></properties>
上面两种都是xml文件配置的,可以参考官网
https://mybatis.org/mybatis-3/zh/configuration.html#properties
3. 从代码层面添加
org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.util.Properties)
构建SqlSessionFactory的时候可以传入Properties,那个Properties就会自动放到configuration对象中
三种方法的优先级为
3 -> 2 -> 1
其实你看到源码的话你自然就明白优先级了....
settings
这个是全局的一些属性配置,其实就是Configuration对象的一些字段
来看下方法是咋做的
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
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;
}
这个就是通过反射的机制去判断当前有没有对应值的set方法,没有的话就直接抛出异常了,这个就不细说了,比较简单,MetaClass 是mybatis封装的一个关于类元数据的对象,不属于本章内容
settings的参数含义还是可以参考官网文档
https://mybatis.org/mybatis-3/zh/configuration.html#settings
这里说一个比较典型的参数
mapUnderscoreToCamelCase : 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。
这个比较典型,就是否自动把 user_name的数据库字段自动映射到 userName的Java属性上,所以我们一般建表尽量要遵循规范,查询数据就不用定义别名了
不过奇怪的是这个属性默认是关闭的,你需要手工配置一下启动.......
typeAliases
别名处理器,主要功能是为了把一些比较长的名字通过短名字映射,然后你在后续使用中就可以使用短名字来使用,还是先来看看具体的源码解析
配置方式还是可以参考官网
https://mybatis.org/mybatis-3/zh/configuration.html#typeAliases
主要有两种配置方式
1. 通过包配置,mybatis会自动把下面的所有类都进行注册别名
<typeAliases>
<package name="domain.blog"/>
</typeAliases>
2. 通过具体的指定配置
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
</typeAliases>
2.1 其中的alias可以不指定,不指定的话会通过类的最短名字来作为别名,也就是author
2.2 还可以在Author标记上一个注解@Alias来指定别名,也是mybatis提供的
好了下面是具体的解析源码
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) {
//如果别名没配,就需要根据Java类型去做判断了,或者根据@Alias注解来解析
typeAliasRegistry.registerAlias(clazz);
} else {
//两个都配了解析是最简单的,往里面注册就行
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
其实还是挺好理解的,有兴趣的话可以看到里面的方法看他是怎么做的
1. 比如当你只配置type,没配置alias的时候是这样处理的
public void registerAlias(Class<?> type) {
//先获取类型的简单名称
String alias = type.getSimpleName();
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
//如果类上面标记了Alias注解就用注解里面的,否则就是类型的简称
alias = aliasAnnotation.value();
}
//调用通用的注册方法
registerAlias(alias, type);
}
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()) {
//等于执行你没配置alias属性的情况
registerAlias(type);
}
}
}
至于怎么拿到包下面所有类的,这里就不说了,因为我自己也没看进去
3. 具体的注册方法也可以简单看下,最终的注册其实都会到这里来
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() + "'.");
}
//往这个类型放置一个别名,其实就是一个HashMap
TYPE_ALIASES.put(key, value);
}
至此,这个也说完了,其实也不复杂,很好理解
typeAliases初始别名
别名数据进行了两部分初始化
1. 在TypeAliasRegistry自己构造的时候初始化了很多对应关系,可以看到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);
}
2. TypeAliasRegistry是被集成在Configuration对象使用的,里面一开始就创建了TypeAliasRegistry这个对象
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
然后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);
}
其中有一个别名是我经常使用到的
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
这个是日志的打印器,配合前面的setting进行这样的配置
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
那么sql就会打印到控制台了,调试的时候我经常用这个
当然这里能拿到是因为这个别名是在Configuration对象被创建的时候就加载了,所以可以拿到
其实这个源码下面并不复杂,如果单纯看的话