前言
上篇文章向大家展示了MyBatis的简单Demo,本篇将为大家介绍mybatis的核心配置文件。
在此之前我们回顾一下mybatis 是如何加载配置文件的,是以SqlSessionFactoryBuilder对象为程序的入口进行的。下面我们来看看mybatis的核心配置文件包括哪些内容:
//1、读取配置文件
String resource = "org/apache/ibatis/mytest/config/mybatis-config.xml";
Reader reader = Resources.getResourceAsReader(resource);
// 2 创建 SqlSessionFactory 工厂:使用 SqlSessionFactoryBuilder 构建者
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
// 3 使用工厂生产 SqlSession 对象
SqlSession sqlSession = factory.openSession();
mybatis-config.xml
configuration 为顶层节点,其余节点均为configuration节点的子节点。下面来逐一进行分析:
<configuration>
<properties></properties> <!--属性-->
<settings></settings> <!--设置相关-->
<typeAliases></typeAliases> <!--别名-->
<typeHandlers></typeHandlers> <!--类型处理器-->
<objectFactory></objectFactory> <!--对象工厂-->
<plugins></plugins> <!--插件-->
<environments default=""> <!-- 环境配置-->
<environment id=""> <!-- 环境变量-->
<transactionManager></transactionManager> <!--事务管理器-->
<dataSource></dataSource> <!--数据源-->
</environment>
</environments>
<databaseIdProvider></databaseIdProvider> <!--数据库厂商的标识-->
<mappers></mappers> <!--映射器-->
</configuration>
properties
<!-- 方法一: 从外部指定properties配置文件
1. 使用resource属性指定
<properties resource="dbConfig.properties"></properties>
2. 还可通过url属性指定url
<properties url="file...."></properties>
-->
<!-- 方法二: 直接使用property标签进行配置 -->
<properties>
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/sakila"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--使用上面 property 标签配置的属性 通过 ${key} 的形式-->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
下面我们来一探究竟,mybatis是如何来解析 properties 标签的。
跟随 SqlSessionFactoryBuilder 对象的build方法,可以看到会调用parser.parse() 方法进行解析,最终会调用到parseConfiguration 方法,下面进行跟踪。
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
// 解析config.xml (mybatis 解析配置文件使用的是 java dom)
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 解析config.xml 里面的节点
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.
}
}
}
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
//解析properties元素
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);
}
}
// 解析properties
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
// 这里注意顺序,xml 配置中的优先
Properties defaults = context.getChildrenAsProperties();
// 获取property 节点上resource属性的值
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
// 这里注意 resource 和 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.");
}
// 把解析处的properties信息set进properties对象
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
//将configuration对象中已配置的Properties属性与刚刚解析的融合在一起
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
// 放进configuration对象
configuration.setVariables(defaults);
}
}
settings
一个完整的settings标签设置如下:
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
所有的可设置值列举,为了节省篇幅就不给大家列举出来了,这里给大家提供官网的详细列举链接:点我查看mybatis settings 属性详细列举值
typeAliases
typeAliases 类型别名,意为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。下面来看看typeAliases 是如何使用的:
<typeAliases>
<!--通过直接指定package的名字, mybatis会自动扫描你指定包下面的java对象,
并且默认设置一个别名,默认的名字为: java对象的首字母小写的非限定类名来作为它的别名。
也可在java对象 加上注解@Alias 来自定义别名, 例如: @Alias(actor)-->
<!-- <package name="org.apache.ibatis.mytest.entity"/>-->
<typeAlias alias="actor" type="org.apache.ibatis.mytest.entity.Actor"/>
</typeAliases>
上面介绍了typeAliases 的作用以及用法,下面看看mybatis 是如何实现的
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// package 方式 mybatis会扫描指定的package
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
// TypeAliasRegistry 管理别名
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
// 子节点是typealias
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);
}
}
}
}
}
public class TypeAliasRegistry {
// 这个map就是用来存储我们配置的别名 key 为别名,value是对应的class 对象
private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();
// mybatis 默认为我们注册的别名,这些可以直接使用
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);
}
// 保存别名的本质方法,其实就是向保存别名的map中增加数据
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() + "'.");
}
TYPE_ALIASES.put(key, value);
}
}