这篇文章中,我们将讲解配置文件中 properties,typeAliases,settings和environments这些节点的解析过程。
一 properties的解析
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
//解析properties的子节点,并将这些子节点内容转为属性对象Properties
Properties defaults = context.getChildrenAsProperties();
//获取properties节点中resource的属性值
String resource = context.getStringAttribute("resource");
//获取properties节点中url的属性值
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.");
}
if (resource != null) {
// 从文件系统中加载并解析属性文件,此时会覆盖在xml中配置的properties子节点的同名key-value
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
// 从url中解析并加载属性文件
defaults.putAll(Resources.getUrlAsProperties(url));
}
// 获取configuration中已经定义的属性对象Properties
Properties vars = configuration.getVariables();
if (vars != null) {
// configuration中的key-value会覆盖上面两种情况中的key-value
defaults.putAll(vars);
}
// 将解析出的内容set到parser中
parser.setVariables(defaults);
// 将解析出的内容set到configuration中,configuration会装载所解析的配置文件中所有的节点内容,后面会使用到这个对象
configuration.setVariables(defaults);
}
}
二 settings的解析
先看下settings的配置,下面只是settings配置中的一部分:
<!-- settings是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 -->
<settings>
<!-- 该配置影响的所有映射器中配置的缓存的全局开关。默认值true -->
<setting name="cacheEnabled" value="true"/>
<!--延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。默认值false -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 是否允许单一语句返回多结果集(需要兼容驱动)。 默认值true -->
<setting name="multipleResultSetsEnabled" value="true"/>
</settings>
源码部分:
private void settingsElement(XNode context) throws Exception {
if (context != null) {
// 获取settings节点下所有的子节点信息,然后封装成Properties对象
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
// 获取Configuration的元信息对象
MetaClass metaConfig = MetaClass.forClass(Configuration.class);
for (Object key : props.keySet()) {
// 检测Configuration中是否存在相关的属性,如果不存在,那么抛出异常
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
//以下是把获取到的setting信息封装到Configuration中,所以才需要上面的检测
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
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"), true));
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.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.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}
}
二 typeAliases的解析
先看下在配置文件中的写法:
<typeAliases>
<typeAlias alias="goods" type="com.yht.mybatisTest.entity.Goods"/>
</typeAliases>
源码部分:
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 {
// 如果子节点是typeAlias
String alias = child.getStringAttribute("alias"); //获取alias的属性值
String type = child.getStringAttribute("type"); //获取type的属性值
try {
// 获取type对应的类型
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);
}
}
}
}
}
进入registerAlias方法:
public void registerAlias(Class<?> type) {
// 如果在配置文件中没有配置alias属性,这里会获取type的类名
String alias = type.getSimpleName();
// 获取注解上的别名
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
alias = aliasAnnotation.value();
}
// 进入此方法
registerAlias(alias, type);
}
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); // issue #748
// 如果TYPE_ALIASES已经存在该别名,并且对应的类型不为空,同时已经存在的类型不等于将要注册的类型value,那么抛出异常
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是一个HashMap
TYPE_ALIASES.put(key, value);
}
接着看下Mybatis内部常见别名注册:
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);
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);
}
四 environments的解析
在mybatis中事务管理器和数据源是在environments中配置的,配置如下:
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
</dataSource>
</environment>
</environments>
对应的源码如下:
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
// 获取environments中default的属性值
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
// 获取id的属性
String id = child.getStringAttribute("id");
// 判断子节点id的属性和父节点environments的default属性是否相同,相同返回true,否则返回false
if (isSpecifiedEnvironment(id)) {
// 解析transactionManager节点
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 解析DataSource节点
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
// 获取DataSource对象
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
// 构建environment对象并设置到configuration中
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
现在还有一个问题值得思考:对于<property name="driver" value="${driver}" /> 中,value值是如何被赋值的?接下里,我们跟踪源码进行分析:
进入XMLConfigBuilder类的environmentsElement方法,有这么一行代码: DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));是解析DataSource节点的,上述的过程就是在这个方法中发生的,进入该方法:
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
// 获取DataSource的type属性
String type = context.getStringAttribute("type");
// 解析DataSource所有的子节点信息并封装为属性对象Properties,进入该方法
Properties props = context.getChildrenAsProperties();
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
进入context.getChildrenAsProperties();方法:
public Properties getChildrenAsProperties() {
Properties properties = new Properties();
//进入getChildren()方法
for (XNode child : getChildren()) {
String name = child.getStringAttribute("name");
String value = child.getStringAttribute("value");
if (name != null && value != null) {
properties.setProperty(name, value);
}
}
return properties;
}
public List<XNode> getChildren() {
List<XNode> children = new ArrayList<XNode>();
NodeList nodeList = node.getChildNodes();
if (nodeList != null) {
for (int i = 0, n = nodeList.getLength(); i < n; i++) {
Node node = nodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
// 对DataSource中所有的子节点构建成XNode对象,并放到children集合中,进入这个构造方法
children.add(new XNode(xpathParser, node, variables));
}
}
}
return children;
}
// 这个是DataSource的子节点的构造方法,具体的说就是把 <property name="driver" value="${driver}" /> 构造成一个XNode节点
public XNode(XPathParser xpathParser, Node node, Properties variables) { this.xpathParser = xpathParser; this.node = node; this.name = node.getNodeName(); this.variables = variables;
//value赋值过程在这个方法中 this.attributes = parseAttributes(node); this.body = parseBody(node); }
private Properties parseAttributes(Node n) {
Properties attributes = new Properties();
// 获取子节点的所有属性 如:[name="driver", value="${driver}"]
NamedNodeMap attributeNodes = n.getAttributes();
if (attributeNodes != null) {
for (int i = 0; i < attributeNodes.getLength(); i++) {
Node attribute = attributeNodes.item(i);
// 对${dirver}的解析赋值在parse方法中
String value = PropertyParser.parse(attribute.getNodeValue(), variables);
attributes.put(attribute.getNodeName(), value);
}
}
return attributes;
}
// 根据varibles中已经存储的值,对给定的${driver},在varibles中找到driver对应的真实值,并进行替换
public static String parse(String string, Properties variables) { VariableTokenHandler handler = new VariableTokenHandler(variables); GenericTokenParser parser = new GenericTokenParser("${", "}", handler); return parser.parse(string); }
五 TypeHandler的解析
当向数据库中存储或者取出数据时,需要将数据库中的字段类型和java类型做一个转换,比如从数据库中取出的CHAR类型,转换为java中的String类型,这个功能就委托给类型处理器TypeHandler来处理。
mybatis已经提供了一些常见的类型处理器,如StringTypeHandler,ArrayTypeHandler,LongTypeHandler等,能够满足大多数的开发需求。对于某些特殊的需求,我们也可以自定义类型处理器,需要继承
BaseTypeHandler这个抽象类。下面我们自定义一个MyStringTypeHandler类型处理器,用于扩展StringTypeHandler的功能:
package com.yht.mybatisTest.typeHandlers;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
/**
* @author chenyk
* @date 2018年8月22日
*/
public class MyStringTypeHandler extends BaseTypeHandler<String>{
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
throws SQLException {
System.out.println("新增逻辑");
ps.setString(i, parameter);
}
@Override
public String getNullableResult(ResultSet rs, String columnName)
throws SQLException {
System.out.println("新增逻辑");
return rs.getString(columnName);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
System.out.println("新增逻辑");
return rs.getString(columnIndex);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
System.out.println("新增逻辑");
return cs.getString(columnIndex);
}
}
类型处理器在配置文件中有两种配置方法:
<!-- 手动配置 -->
<typeHandlers>
<typeHandler jdbcType="CHAR" javaType="String" handler="com.yht.mybatisTest.typeHandlers.MyStringTypeHandler" />
</typeHandlers>
<!-- 自动扫描 -->
<typeHandlers>
<package name="com.yht.mybatisTest.typeHandlers"/>
</typeHandlers>
看源码部分:
private void typeHandlerElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) {
// 扫描指定的包 然后注册TypeHandler String typeHandlerPackage = child.getStringAttribute("name"); typeHandlerRegistry.register(typeHandlerPackage); } else {
// 获取javaType属性值,jdbcType属性值,handler属性值,然后注册TypeHandler 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); } } } } }
根据不同的情况,注册TypeHandler共有四种方法,这里我们选一种进行分析:typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) { if (javaType != null) { Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType); if (map == null) { map = new HashMap<JdbcType, TypeHandler<?>>();
// 存储JavaType到Map<JdbcType,TypeHandler>的映射 TYPE_HANDLER_MAP.put(javaType, map); } map.put(jdbcType, handler); if (reversePrimitiveMap.containsKey(javaType)) { register(reversePrimitiveMap.get(javaType), jdbcType, handler); } }
//存储所有的TypeHandler ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler); }
mybatis内置的TypeHandler有哪些呢?贴出源码:
private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class); private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new HashMap<Type, Map<JdbcType, TypeHandler<?>>>(); private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this); private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>(); 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()); register(Float.class, new FloatTypeHandler()); register(float.class, new FloatTypeHandler()); register(JdbcType.FLOAT, new FloatTypeHandler()); register(Double.class, new DoubleTypeHandler()); register(double.class, new DoubleTypeHandler()); register(JdbcType.DOUBLE, new DoubleTypeHandler()); register(String.class, new StringTypeHandler()); register(String.class, JdbcType.CHAR, new StringTypeHandler()); register(String.class, JdbcType.CLOB, new ClobTypeHandler()); register(String.class, JdbcType.VARCHAR, new StringTypeHandler()); register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler()); register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler()); register(String.class, JdbcType.NCHAR, new NStringTypeHandler()); register(String.class, JdbcType.NCLOB, new NClobTypeHandler()); register(JdbcType.CHAR, new StringTypeHandler()); register(JdbcType.VARCHAR, new StringTypeHandler()); register(JdbcType.CLOB, new ClobTypeHandler()); register(JdbcType.LONGVARCHAR, new ClobTypeHandler()); register(JdbcType.NVARCHAR, new NStringTypeHandler()); register(JdbcType.NCHAR, new NStringTypeHandler()); register(JdbcType.NCLOB, new NClobTypeHandler()); register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler()); register(JdbcType.ARRAY, new ArrayTypeHandler()); register(BigInteger.class, new BigIntegerTypeHandler()); register(JdbcType.BIGINT, new LongTypeHandler()); register(BigDecimal.class, new BigDecimalTypeHandler()); register(JdbcType.REAL, new BigDecimalTypeHandler()); register(JdbcType.DECIMAL, new BigDecimalTypeHandler()); register(JdbcType.NUMERIC, new BigDecimalTypeHandler()); register(Byte[].class, new ByteObjectArrayTypeHandler()); register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler()); register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler()); register(byte[].class, new ByteArrayTypeHandler()); register(byte[].class, JdbcType.BLOB, new BlobTypeHandler()); register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler()); register(JdbcType.LONGVARBINARY, new BlobTypeHandler()); register(JdbcType.BLOB, new BlobTypeHandler()); register(Object.class, UNKNOWN_TYPE_HANDLER); register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER); register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER); register(Date.class, new DateTypeHandler()); register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler()); register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler()); register(JdbcType.TIMESTAMP, new DateTypeHandler()); register(JdbcType.DATE, new DateOnlyTypeHandler()); register(JdbcType.TIME, new TimeOnlyTypeHandler()); register(java.sql.Date.class, new SqlDateTypeHandler()); register(java.sql.Time.class, new SqlTimeTypeHandler()); register(java.sql.Timestamp.class, new SqlTimestampTypeHandler()); // issue #273 register(Character.class, new CharacterTypeHandler()); register(char.class, new CharacterTypeHandler()); }
到现在为止,对于配置文件中常见节点的解析过程做了分析,接下来在下一篇文章中我们继续对映射文件的解析过程进行讲解。