mybatis之Configuration解析
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置属性,可以在整个配置文件中用来替换需要动态配置的属性值 -->
<properties>
<property name= "" value "">
</properties>
<!-- 这是mybatis中极为重要的调整,它会改变mybatis的运行时行为, name不能自己定义mybatis有内置的name -->
<settings>
<setting name = "xxxx" value="" />
</settings>
<!-- 类型别名,可以为java类型设置一个缩写名字,仅用于xml中,意在降低冗余的书写 -->
<typeAliases>
<typeAlias alias = "xxx" type= "xx.xxx.xx" >
</typeAliases>
<!-- 自定义的类型处理器 -->
<typeHandlers>
<typeHandler handler = "xxxx" />
</typeHandler>
<!-- 自定义对象工厂, mybatis通过使用对象工厂,在获取结果集用来实例化对象 -->
<objectFactory type = "xxxx">
<!-- 可选 -->
<property name = "xxx" value = "xxx" />
</objectFactory>
<!-- 自定义插件 -->
<plugins>
<plugin interceptor = "xxxx">
<property name = "" value = "">
</plugin>
</plugins>
<!-- 环境类型 -->
<environments default = "dev">
<environment id = "dev">
<transactionManager type = "JDBC" >
<properyt name = "..." vlaue= "...">
</transactonManager>
<dataSource type= "POOLED">
<property name= "..." value = "...">
</dataSource>
</envitonment>
</environments>
<!-- 数据库厂商标识 -->
<databaseIdProvider type = "DB_VENDOR" >
<property name = "..." value = "...">
</databaseIdProvider>
<!-- mappers映射器 -->
<mappers>
<mapper resource = "xxxx" />
<mapper url = "xxx" />
<package name = "xxxx">
</mappers>
</configuration>
解析configuration的方法在XmlConfigurationBuilder中
public class XMLConfigBuiler extends BaseBuilder {
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);
}
}
}
configuration总览
properties
在获取sqlSessionFactory传入properties属性的源码分析
// 1. 获取到了SqlSessionFactory传入properties, properties设置配置key和value
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream, properties);
// 2. 调用方法: build(InputStream inputStream, String environment, Properties properties), 行为类是重载方法方式
public SqlSessionFactory build(InputStream inputStream, Properties properties) {
return build(inputStream, null, properties);
}
class public SqlSesstionFactoryBuilder {
//3. 实际调用的方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
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.
}
}
}
}
public class XMLConfigBuilder extends BaseBuilder {
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
// 方法传入的参数存放入Configuration的variables
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
}
从xml文件读取properties属性
<configuration>
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
</configuration>
//XMLConfigBuilder
propertiesElement(root.evalNode("properties"));
//按照顺序加载属性
public class XMLConfiguration extends BaseBuilder {
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
// 1.解析出从xml中配置的属性
Properties defaults = context.getChildrenAsProperties();
//2.properties 可以配置resources或url任意属性(二者只能选其一)
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.");
}
// Properties.putAll方法的底层是往HashTable中放key和value,同名key会覆盖值。
//3. 如果有设置resource或url属性并且有值,则覆盖xml中的属性配置
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
//4. 如果在获取sqlSessionFactory传入properties属性,则覆盖上面两步的属性值
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
//更新configuration的variables
configuration.setVariables(defaults);
}
}
}
从上面源码我们得知Mybatis加载属性的顺序是:
1.首先读取xml在properties元素内指定的属性
2.根据properties中的属性resource或url读取,并覆盖上面的同名属性
3.最后读取方法参数传递的属性,并覆盖之前读取过的同名属性
从源码中学到的编码小技巧
根据XmlConfiguration的构造器的实现,我们可以学到复用重载方法
class public XMLConfigBuilder {
public XMLConfigBuilder(Reader reader) {
this(reader, null, null);
}
public XMLConfigBuilder(Reader reader, String environment) {
this(reader, environment, null);
}
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
public XMLConfigBuilder(InputStream inputStream) {
this(inputStream, null, null);
}
public XMLConfigBuilder(InputStream inputStream, String environment) {
this(inputStream, environment, null);
}
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
// 根据缺省参数来重载出不同的构造器
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;
}
}
settings
当在settings中设置了属性的值,它会改变Configuration中指定属性的值,这会改变Mybatis的运行行为。
Properties settings = settingsAsProperties(root.evalNode("settings"));
public class XMLConfiBuilder extends BaseBuilder {
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
Properties props = context.getChildrenAsProperties();
//检查settings配置的属性是否存在于Configuration的属性
//使用mybatsi封装的反射方法
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配置设置属性值,没有的则设置默认值
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")));
}
}
typeAliases
typeAliases使用
typeAliases类型别名,在xml配置文件中配置类的别名(缩写),用来降低冗余的全限定类名书写。
我们为类com.xlj.CleanPolicy的全限定名设置别名CleanPolicy
<configuration>
<typeAliases>
<typeAlias type="com.xlj.CleanPolicy" alias="CleanPolicy" />
</typeAliases>
</configuration>
在CleanPolicyMapper.xml中使用别名
<mapper namespace="com.xlj.CleanPolicyMapper">
<!--type这里使用了类型别名 -->
<resultMap id="BaseResultMap" type="CleanPolicy">
<id column="id" jdbcType="BIGINT" property="id" />
<result column="name" jdbcType="VARCHAR" property="name" />
</resultMap>
<select id="selectOne" resultMap="BaseResultMap">
select id, name from clean_policy limit 1;
</select>
</mapper>
也可以指定包,设置别名。
<typeAliases>
<package name ="domain.blog">
</typeAliases>
在每一个包domain.blog在中的JavaBean,在没有注解的情况下, 会使用bean的首字母小写的非限定类名来作为它的别名。比如domain.blog.Author的别名为author,若有注解,则别名为其注解值,见下面例子。
@Alias("author")
public class Author {
...
}
Mybatis也为常见的Java类型内建了类型别名。他们都是不区分大小爹的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。
<!-- 使用内建的类型别名_int替代Java.lang.Interger -->
<select id="selectCnt" resultType="_int">
select count(1) from clean_policy;
</select>
别名 | 映射的类型 |
---|---|
_int | int |
_byte | byte |
_short | short |
更多参考Mybatis官方文档 | https://mybatis.org/mybatis-3/zh/configuration.html#typeAliases |
typeAliases源码
typeAliasesElement(root.evalNode("typeAliases"));
public class XMLConfigBuilder {
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的核心为HashMap存放key(别名)与value(类)
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
}
public TypeAliasRegistry {
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为HashMap
TYPE_ALIASES.put(key, value);
}
//上面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);
}
}
typeHandlers
typeHandlers类型处理器的作用是在设置参数或取结果集时,映射DB和java类型。mybatis为我们提供了大多常见的默认类型处理器。
类型处理器 | java类型 | JDBC类型 |
---|---|---|
IntergerTypeHandler | java.lang.Interger,int | 数据库兼容的NUMBERIC或SMALLINT |
StringTypeHandler | java.lang.String | CHAR,VERCHAR |
更多参考Mybatsi官方文档 | https://mybatis.org/mybatis-3/zh/configuration.html#typeHandlers |
参数处理器使用typeHandler类型处理器的源码
class ParameterMapping {
// 根据参数选择参数类型处理器
private void resolveTypeHandler() {
if (parameterMapping.typeHandler == null && parameterMapping.javaType != null) {
Configuration configuration = parameterMapping.configuration;
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
parameterMapping.typeHandler = typeHandlerRegistry.getTypeHandler(parameterMapping.javaType, parameterMapping.jdbcType);
}
}
}
假设我们的参数是Long类型的,那么调用的方法
//最终调用java.sql.PreparedStatement的setLong方法
class LongTypeHandler extends BaseTypeHandler<Long> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Long parameter, JdbcType jdbcType)
throws SQLException {
ps.setLong(i, parameter);
}
}
自定义类型处理器
你可以重写已有的类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型,具体做法为:实现org.apache.ibatis.type.TypeHandler接口,或继承一个很便利的类org.apche.ibatis.type.BaseTypeHandler,并且可以(可选的)将它映射到一个JDBC类型,
<typeHanlders>
<typeHandler jdbcType="" javaType="" handler="youTypeHandler">
</typeHandlers>
<mapper>
<resultMap>
<result column="" typeHandler="yourTypeHandler" property="">
</resultMap>
</mapper>
解析XML配置
typeHandlerElement(root.evalNode("typeHandlers"));
public class XMLConfigurationBuilder{
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 {
// 解析<typeHandler> </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 {
// 核心是hashmap,存储类型处理器
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
}
// 加载默认的类型处理器
public final class TypeHandlerRegistry {
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(Reader.class, new ClobReaderTypeHandler());
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(InputStream.class, new BlobInputStreamTypeHandler());
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());
// mybatis-typehandlers-jsr310
if (Jdk.dateAndTimeApiExists) {
Java8TypeHandlersRegistrar.registerDateAndTimeHandlers(this);
}
// issue #273
register(Character.class, new CharacterTypeHandler());
register(char.class, new CharacterTypeHandler());
}
}
如果自定义的类型处理器和默认类型的类型处理的类型一样,则自定义覆盖默认。
objectFactory
每次Mybatis创建结果对象的新实例时,它都会使用一个对象工厂(ObejctFactory)实例来完成实例化工作。默认的对象工厂需要做的仅仅是实例化目标类,要么通过无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现。
<!--使用无参数构造器 这里id会通过set属性的方法设值-->
<resultMap id="BaseResultMap" type="CleanPolicy">
<id column="id" jdbcType="BIGINT" property="id" />
<result column="name" jdbcType="VARCHAR" property="name" />
</resultMap>
<!--使用有参构造器 这里id会会通过构造器设值-->
<resultMap id="BaseResultMap" type="CleanPolicy">
<constructor>
<idArg column="id" javaType="BIGINT"/>
</constructor>
<result column="name" jdbcType="VARCHAR" property="name" />
</resultMap>
//例如下面是我们的一个查询语句,这里cleanPolicyMapper是一个接口
//当调用selectOne时,实际上调用的是MapperProxy代理,
// 这里返回结果对象实例化就是通过ObjectFactory对象工厂来创建的
CleanPolicy cleanPolicy2 = cleanPolicyMapper.selectOne();
自定义自己的对象工厂
// ExampleObjectFactory.java
public class ExampleObjectFactory extends DefaultObjectFactory {
public Object create(Class type) {
return super.create(type);
}
public Object create(Class type, List<Class> constructorArgTypes, List<Object> constructorArgs) {
T ret = super.create(type, constructorArgTypes, constructorArgs);
// 这里可以写自己的逻辑
return ret
}
public void setProperties(Properties properties) {
super.setProperties(properties);
}
public <T> boolean isCollection(Class<T> type) {
return Collection.class.isAssignableFrom(type);
}
}
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
<property name="someProperty" value="100"/>
</objectFactory>
源码分析
objectFactoryElement(root.evalNode("objectFactory"));
public class Configuration {
// 默认的对象工厂为 DefaultObjectFactory
protected ObjectFactory objectFactory = new DefaultObjectFactory();
}
public class XMLConfigurationBuilder {
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);
}
}
}
//获取到结果集后,创建resultMap,resultType指定的对象
public class DefaultResultSetHandler implements ResultSetHandler {
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
throws SQLException {
final Class<?> resultType = resultMap.getType();
final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
if (hasTypeHandlerForResultObject(rsw, resultType)) {
return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
} else if (!constructorMappings.isEmpty()) {
return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
} else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
return objectFactory.create(resultType);
} else if (shouldApplyAutomaticMappings(resultMap, false)) {
return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs, columnPrefix);
}
throw new ExecutorException("Do not know how to create an instance of " + resultType);
}
}
public class DefaultObjectFactory implements ObjectFactory, Serializable {
@Override
public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
// 实例化类,通过无参的构造方法
Class<?> classToCreate = resolveInterface(type);
// we know types are assignable
return (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
}
<T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
try {
Constructor<T> constructor;
if (constructorArgTypes == null || constructorArgs == null) {
constructor = type.getDeclaredConstructor();
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
return constructor.newInstance();
}
constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()]));
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
// 设值构造器的值
return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));
} catch (Exception e) {
StringBuilder argTypes = new StringBuilder();
if (constructorArgTypes != null && !constructorArgTypes.isEmpty()) {
for (Class<?> argType : constructorArgTypes) {
argTypes.append(argType.getSimpleName());
argTypes.append(",");
}
argTypes.deleteCharAt(argTypes.length() - 1); // remove trailing ,
}
StringBuilder argValues = new StringBuilder();
if (constructorArgs != null && !constructorArgs.isEmpty()) {
for (Object argValue : constructorArgs) {
argValues.append(String.valueOf(argValue));
argValues.append(",");
}
argValues.deleteCharAt(argValues.length() - 1); // remove trailing ,
}
throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);
}
}
}
plugins
在mybatis-config.xml中配置自定义插件。
<plugins>
<plugin interceptor="com.youselfPlugin">
<property name = "someProperty" value = "100" />
</plugin>
<plugins/>
class XMLConfigBuilder {
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();
//设置xml中配置的属性
interceptorInstance.setProperties(properties);
// 从xml定义的自定义插件,加入到mybatis中
configuration.addInterceptor(interceptorInstance);
}
}
}
}
public class Configuration {
public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}
}
更多看这篇博客: mybatis实现自定义插件
environments
mybatis可以配置成适应多种环境,这种机制有助于将sql映射应用于多种数据库之中,现实情况下有多种理由需要这么做,例如开发、测试、生产环境需要不同的配置;或者想在具有相同SCHEMA的多个生产数据库中使用相同的sql映射。还有许多类似使用场景。
不过要记住:尽管可以配置多个环境,但每个SqlSessionFactory实例只能选择一种环境。
来看看xml是如何配置的
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<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>
每一个environment对应一套环境
需要配置默认使用的环境id(id对应environment中的一种),
事物管理器的配置(比如type=JDBC)
数据源的配置(如 type= POOLED)
//environmentsElement(root.evalNode("environments"));
public class XMLConfiguration {
private String environment;
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
// 设置默认的环境名称id
environment = context.getStringAttribute("default");
}
// 循环获取配置的environment匹配default的环境
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("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.setEnvironment(environmentBuilder.build());
}
}
}
}
// 当前的环境id是否是配置的默认环境
private boolean isSpecifiedEnvironment(String id) {
if (environment == null) {
throw new BuilderException("No environment specified.");
} else if (id == null) {
throw new BuilderException("Environment requires an id attribute.");
} else if (environment.equals(id)) {
return true;
}
return false;
}
}
环境信息的使用,获取sqlSession的过程
public class DefaultSessionFactory implements SqlSessionFactory {
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 获取配置的环境信息
final Environment environment = configuration.getEnvironment();
//从环境信息获取配置的 事物管理器
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
}
如果没有配置环境信息,则启动的时候会报空指针异常(NullException)。
事物管理器有两种类型:JDBC、MANAGED
JDBC: 这个配置直接使用了JDBC的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
MANAGED: 这个配置几乎没有做什么,它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期,默认情况下它会关闭连接.
数据源有三种类型:
UNPOOLED: 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求的不高的简单应用程序来说,是一个很好的选择。性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就适合这种情形。
POOLED: 这种数据源的实现利用“池”的概念将JDBC连接对象组织起来,避免了创建新的连接实例时所必须的初始化和认证时间。这种方式很流行,能使并发web应用快速相应请求。
JNDI: 这个数据源实现时为了能和如EJB或应用服务器这类容器种使用,容器可以集中或在外部配置数据源,然后放置一个JNDI上下文的数据源引用。
databaseIdProvider
databaseIdProvider数据库厂商标识, Mybatis可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的databaseId属性。 (同sqlId不同databaseId来切换不同的数据库)
mybatis会加载带有匹配当前数据库databaseId属性和不带databaseId属性的语句。如果同时找到带有databaseId和不带databaseId的相同语句,则后者会被舍弃。
xml配置支持多数据厂商
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="MySQL" value="mysql"/>
<property name="Oracle" value="oracle" />
</databaseIdProvider>
<select id="selectCnt" resultType="_int" databaseId="mysql">
select * from clean_policy;
</select>
<select id="selectCnt" resultType="_int" databaseId="oracle">
select * from clean_policy_one;
</select>
mybatis会根据环境environments来选择当前的数据库厂商。
// databaseIdProviderElement(root.evalNode("databaseIdProvider"));
public class XMLConfiguration {
private void databaseIdProviderElement(XNode context) throws Exception {
DatabaseIdProvider databaseIdProvider = null;
// 如果配置了databaseIdProvider则读取配置信息
if (context != null) {
String type = context.getStringAttribute("type");
// awful patch to keep backward compatibility
if ("VENDOR".equals(type)) {
type = "DB_VENDOR";
}
Properties properties = context.getChildrenAsProperties();
databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
databaseIdProvider.setProperties(properties);
}
//获取配置的环境信息
Environment environment = configuration.getEnvironment();
if (environment != null && databaseIdProvider != null) {
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
configuration.setDatabaseId(databaseId);
}
}
}
public class VendorDatabaseIdProvider implements DatabaseIdProvider {
//查询配置的数据库厂商
@Override
public String getDatabaseId(DataSource dataSource) {
if (dataSource == null) {
throw new NullPointerException("dataSource cannot be null");
}
try {
return getDatabaseName(dataSource);
} catch (Exception e) {
log.error("Could not get a databaseId from dataSource", e);
}
return null;
}
private String getDatabaseName(DataSource dataSource) throws SQLException {
String productName = getDatabaseProductName(dataSource);
// 如果databaseIdProvider有配置别名,则databaseId使用别名
if (this.properties != null) {
for (Map.Entry<Object, Object> property : properties.entrySet()) {
if (productName.contains((String) property.getKey())) {
return (String) property.getValue();
}
}
// no match, return null
return null;
}
return productName;
}
//根据数据库连接信息获取到数据库厂商名称
private String getDatabaseProductName(DataSource dataSource) throws SQLException {
Connection con = null;
try {
con = dataSource.getConnection();
DatabaseMetaData metaData = con.getMetaData();
return metaData.getDatabaseProductName();
} finally {
if (con != null) {
try {
con.close();
} catch (SQLException e) {
// ignored
}
}
}
}
}
mappers
使用mybatis的xml方式,我们需要在mybatis.xml的mappers标签下配置告诉mybatis映射的Sql的那里。
mappers有四种方式来加载映射的sql。
1.url
2.resource
3.class
4.package
其中当rul,class,resource都配置配置,只加载其中一个,优先加载顺序是:resource、url、class。
单个配置了package会加载包下的所有类。
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
实际例子:
<mappers>
<mapper class="com.xuelongjiang.testanyone.mybatis.CleanPolicyMapper"/>
<mapper resource="CleanPolicyMapper.xml"/>
<package name="com.xuelongjiang.mypackage"/>
</mappers>
源码分析
mapperElement(root.evalNode("mappers"));
public class XMLConfigBuilder {
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 加载package的标签
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");
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) {
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<?> 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.");
}
}
}
}
}
}
加载package的标签
class configuration {
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
}
class MapperRegistry {
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
// 获取到包下的所有类
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
// 加载某个类
public <T> void addMapper(Class<T> type) {
// 映射sql的类必须是接口(mybatis代理使用的是jdk代理,jdk只能代理接口)
if (type.isInterface()) {
// 每个类只加载一次
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 创建接口的代理对象放入map
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
}
加载resource
// mapperParser.parse();
public class XMLMapperBuilder {
public void parse() {
// sql映射文件xml只能被加载一次
if (!configuration.isResourceLoaded(resource)) {
// 解析mapper文件中的标签select、update、insert、delete、sql.....
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
}
总结
1.Mybatis加载属性的顺序是: xml在properties元素内指定的属性;properties中的属性resource或url读取,并覆盖上面的同名属性;最后读取方法参数传递的属性,并覆盖之前读取过的同名属性
2.可以写一个全参数的基本方法,通过缺省其中的参数来实现重载
3.当在settings中设置了属性的值,它会改变Configuration中指定属性的值,这会改变Mybatis的运行行为。
4.typeAliases类型别名,在xml配置文件中配置类的别名(缩写),用来降低冗余的全限定类名书写。
5.typeHandlers类型处理器的作用是在设置参数或取结果集时,映射DB和java类型。mybatis为我们提供了大多常见的默认类型处理器。
6.objectFactory对象工厂用来创建结果集的对象,支持无参、有参
7.自定义插件可以拦截四大金刚
8.mybatis支持ci,stg.prd多环境配置连接信息
9.databaseIdProvider提供了根据不同数据库厂商来执行不同的sql
10.当在mapper中rul,class,resource都配置配置,只加载其中一个,优先加载顺序是:resource、url、class。
11.映射sql的类必须是接口(mybatis代理使用的是jdk代理,jdk只能代理接口)
12.桥接方法是JDK1.5引入泛型后,为了使java的泛型方法生成的字节码和1.5版本前的字节码相兼容,由编译器自动生成的方法。我们可以通过method.isBridge()方法来判断一个方法是否是桥接方法