本文只论mybatis本身,不涉及与spring整合,文中探讨了mybatis最新版本提供的全部配置项的作用。
首先要了解都有哪些配置项,mybatis的SqlSession来自SqlSessionFactory,SqlSessionFactory来自SqlSessionFactoryBuilder,从SqlSessionFactoryBuilder切入分析
构造SqlSessionFactoryBuilder用到了XMLConfigBuilder,然后看XMLConfigBuilder
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 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); } } 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"))); }
configuration节点为根节点。
可以配置10个子节点:properties、settings、typeAliases、plugins、objectFactory、objectWrapperFactory、environments、databaseIdProvider、typeHandlers、mappers。
properties
这些属性都是可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来传递。例如:
settings
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。
<!-- mybatis-config.xml --> <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>
懒加载
熟悉配置前需要知道什么是懒加载
public class Order { public Long id; public Long addressId; public Address address; }
public class Address { public Long id; public String name; }
<!-- addressMapper.xml --> <mapper namespace="addressMapperSpace"> <select id="getAddressById" parameterType="Long" resultType="Address"> select id,name from t_address where id = #{id} </select> </mapper>
<!-- orderMapper.xml --> <mapper namespace="..."> <resultMap id="orderMap" type="Order"> <id property="id" column="id" /> <association property="address" column="address_id" select="addressMapperSpace.getAddressById" /> </resultMap> <select id="getOrderById" resultMap="orderMap" parameterType="Long"> select id,address_id from t_order where id = #{id} <select> </mapper>
如果是懒加载,那么访问order的address属性时才会去查询address。
参数介绍
参数 | 官方中文描述 | 理解 | 可选值 | 默认值 |
---|---|---|---|---|
cacheEnabled | 全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存。 | mybatis缓存,不支持集群环境,必须设置成false。 | true | false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态 | 可以不设置 | true | false | false |
aggressiveLazyLoading | 当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载(参考lazyLoadTriggerMethods). | 当设置为true时,懒加载的对象可能被任何懒属性全部加载;否则,每个属性按需加载。一般不用。 可以不设置 | true | false | false (true in ≤3.4.1) |
lazyLoadTriggerMethods | 指定对象的哪个方法触发一次延迟加载。 | 在lazyLoadingEnabled=true时有效,调用本方法会使得所有延迟加载属性被加载,如果有多个懒加载属性,可以使用这个方法把所有懒加载属性一起加载了。 可以不设置 | 用逗号分隔的方法列表。 | equals,clone,hashCode,toString |
proxyFactory | 指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。 | mybatis延迟加载用的工具,旧版本使用的是CGLIB动态代理技术,新版本支持使用JAVASSIST(Javassist是一个运行时编译库,他能动态的生成或修改类的字节码)来完成。 可以不设置 | CGLIB | JAVASSIST | JAVASSIST (MyBatis 3.3 or above) |
multipleResultSetsEnabled | 是否允许单一语句返回多结果集(需要兼容驱动)。 | sql与ResultSet一对多的用法, 没找到用法。 可以不设置 | true | false | true |
useColumnLabel | 使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。 | 在Select字段的时候使用AS,用得上,由于默认true。 可以不设置 | true | false | true |
useGeneratedKeys | 允许 JDBC 支持自动生成主键,需要驱动兼容。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。 | 我们使用mysql数据库自增主键,在xml的insert块中如果使用useGeneratedKeys来获得生成的主键,那这个属性必须设置成true。如果使用以下方法,那也可以不设置。 <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Long"> SELECT LAST_INSERT_ID() </selectKey> | true | false | false |
autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)。 | 如果修改成FULL,会由于没及时更新model导致映射失败。 可以不设置 | NONE, PARTIAL, FULL | PARTIAL |
autoMappingUnknownColumnBehavior | 指定发现自动映射目标未知列(或者未知属性类型)的行为。
| 可以不设置 | NONE, WARNING, FAILING | NONE |
defaultExecutorType | 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。 | 1.设为"SIMPLE", 在执行dao.save()时,就相当于JDBC的stmt.execute(sql); 2.设为"REUSE", 在执行dao.save()时,相当于JDBC重用一条sql,再通过stmt传入多项参数值,然后执行stmt.executeUpdate()或stmt.executeBatch();重用sql的场景不太常见,因此用SIMPLE就可以了。 3.设为"BATCH", 在执行dao.save()时,相当于JDBC语句的 stmt.addBatch(sql),即仅仅是将执行SQL加入到批量计划。 所以此时不会抛出主键冲突等运行时异常,而只有临近commit前执行stmt.execteBatch()后才会抛出异常。 可以不设置 | SIMPLE REUSE BATCH | SIMPLE |
defaultStatementTimeout | 设置超时时间,它决定驱动等待数据库响应的秒数。 | 这是以秒为单位的全局sql超时时间设置,当超出了设置的超时时间时,会抛出SQLTimeoutException。建议设置一个合理值。 | 任意正整数 | Not Set (null) |
defaultFetchSize | 为驱动的结果集获取数量(fetchSize)设置一个提示值。此参数只可以在查询设置中被覆盖。 | mysql不支持fetchSize。 一般使用分页插件即可。 可以不设置 | 任意正整数 | Not Set (null) |
safeRowBoundsEnabled | 允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为false。 | 使用场景:session.select("...", null, new RowBounds(1, 2),resultHandler); 一般使用分页插件即可。 可以不设置 | true | false | false |
safeResultHandlerEnabled | 允许在嵌套语句中使用分页(ResultHandler)。如果允许使用则设置为false。 | 使用场景:session.select("...", null, new RowBounds(1, 2),resultHandler); 可以不设置 | true | false | true |
mapUnderscoreToCamelCase | 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 | 驼峰命名映射,手写mapper不去写resultMap时推荐开启。使用mybatis-generator时,不开启也ok。 | true | false | false |
localCacheScope | MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。 | mybatis缓存,不支持集群环境,这个就不用管了。 可以不设置 | SESSION | STATEMENT | SESSION |
jdbcTypeForNull | 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 | 正常情况下我们都配了。 可以不设置 | JdbcType 常量. 大多都为: NULL, VARCHAR and OTHER | OTHER (java.lang.Object) |
defaultScriptingLanguage | 指定动态 SQL 生成的默认语言。 | 虽然官方名称叫做LanguageDriver,其实叫做解析器可能更加合理。MyBatis 从 3.2 开始支持可插拔的脚本语言,因此你可以在插入一种语言的驱动(language driver)之后来写基于这种语言的动态 SQL 查询比如mybatis除了XML格式外,还提供了mybatis-velocity,允许使用velocity表达式编写SQL语句。可以通过实现LanguageDriver接口的方式来插入一种语言。 可以不设置 | 一个类型别名或完全限定类名。 | org.apache.ibatis.scripting.xmltags.XMLLanguageDriver |
defaultEnumTypeHandler | 默认的EnumTypeHandler存入数据库的是枚举的name, mybatis还提供了EnumOrdinalTypeHandler存入数据库的是枚举的位置。 这俩都不太好用,如果想要把数据库查询结果与枚举自动转换,可以自定义typeHandler来实现。在查询或操作数据时以枚举传输优势并不大,只提供对应的枚举也可满足需求。 可以不设置 | 一个类型别名或完全限定类名。 | org.apache.ibatis.type.EnumTypeHandler | |
callSettersOnNulls | 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。注意基本类型(int、boolean等)是不能设置成 null 的。 | 假设将数据从DB中查询出来如果将字段映射为Map,而不想封装成Bean。默认情况下,Mybatis对Map的解析生成, 如果值(value)为null的话,那么key也不会被加入到map中. 可以不设置 | true | false | false |
returnInstanceForEmptyRow | 当返回行的所有列都是空时,MyBatis默认返回null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集 (i.e. collectioin and association)。(从3.4.2开始) | 查询结果没有的时候,返回null是合理的,返回一个空对象容易硬气误会。 不要设置 | true | false | false |
logPrefix | 指定 MyBatis 增加到日志名称的前缀。 | 日志前缀,要不要看个人喜好。 可以不设置 | 任何字符串 | Not set |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | 不要设置 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | Not set |
vfsImpl | 指定VFS的实现 | VFS主要用来加载容器内的各种资源,比如jar或者class文件。mybatis提供了2个实现 JBoss6VFS 和 DefaultVFS,并提供了用户扩展点,用于自定义VFS实现,加载顺序是自定义VFS实现 > 默认VFS实现 取第一个加载成功的,默认情况下会先加载JBoss6VFS,如果classpath下找不到jboss的vfs实现才会加载默认VFS实现。 | 自定义VFS的实现的类全限定名,以逗号分隔。 | Not set |
useActualParamName | 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的工程必须采用Java 8编译,并且加上-parameters选项。(从3.4.1开始) | mybatis的全局配置useActualParamName决定了mapper中参数的写法,默认为true。此时无需再使用@Param。 Order getOrderByCondition (Long id,Long addressId) <select id="getOrderByCondition" resultType="Order" > select * from t_order where id = #{id} and addressId = #{addressId} </select> 如果是false那么可写成 <select id="getOrderByCondition" resultType="Order" > select * from t_order where id = #{0} and addressId = #{1} </select> 使用这个特性必须在jdk1.8场景。这是因为:在Java 8之前的版本,代码编译为class文件后,方法参数的类型是固定的,但参数名称却丢失了,这和动态语言严重依赖参数名称形成了鲜明对比。现在,Java 8开始在class文件中保留参数名,给反射带来了极大的便利。jdk8增加了类Parameter。 可以不设置 | true | false | true |
configurationFactory | 指定一个提供Configuration实例的类。 这个被返回的Configuration实例用来加载被反序列化对象的懒加载属性值。 这个类必须包含一个签名方法static Configuration getConfiguration(). (从 3.2.3 版本开始) | 此时mybatis全局的Configuration将被开发者手动指定。 建议不设置 | 类型别名或者全类名. | Not set |
typeAliases
类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,原理是用hashMap关联,存在的意义仅在于用来减少类完全限定名的冗余。例如:
<!-- mybatis-config.xml --> <typeAliases> <typeAlias alias="OrderMain" type="order.center.domain.OrderMain"/> </typeAliases> <!-- mybatis-config.xml --> <typeAliases> <package name="order.center.domain"/> </typeAliases>
typeHandlers
无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
下面是一个处理javaType=com.alibaba.fastjson.JSON时的例子
objectFactory
MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。 如果想覆盖对象工厂的默认行为,则可以通过创建自己的对象工厂来实现。比如:
// 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) { return super.create(type, constructorArgTypes, constructorArgs); } public void setProperties(Properties properties) { super.setProperties(properties); } public <T> boolean isCollection(Class<T> type) { return Collection.class.isAssignableFrom(type); } }
objectWrapperFactory
最新的官方文档中已经找不到这个配置项。原用来提供自定义的ObjectWrapper
plugins
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
-
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
-
ParameterHandler (getParameterObject, setParameters)
-
ResultSetHandler (handleResultSets, handleOutputParameters)
-
StatementHandler (prepare, parameterize, batch, update, query)
这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为如果在试图修改或重写已有方法的行为的时候,你很可能在破坏 MyBatis 的核心模块。 这些都是更低层的类和方法,所以使用插件的时候要特别当心。
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。
例如配置pageHelper:
<!-- mybatis-config.xml --> <plugins> <plugin interceptor="com.github.pagehelper.PageHelper"> <property name="dialect" value="mysql"/> <property name="offsetAsPageNum" value="false"/> <property name="rowBoundsWithCount" value="false"/> <property name="pageSizeZero" value="true"/> <property name="reasonable" value="false"/> <property name="supportMethodsArguments" value="false"/> <property name="returnPageInfo" value="none"/> </plugin> </plugins>
environments
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者共享相同 Schema 的多个生产数据库, 想使用相同的 SQL 映射。许多类似的用例。
不过要记住:尽管可以配置多个环境,每个 SqlSessionFactory 实例只能选择其一。
所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例,依此类推,记起来很简单:
-
每个数据库对应一个 SqlSessionFactory 实例
为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可。可以接受环境配置的两个方法签名是:
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);
如果忽略了环境参数,那么默认环境将会被加载,如下所示:
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, properties);
环境元素定义了如何配置环境。
databaseIdProvider
我们实际使用中,不同数据库大概率是不同数据源,很低概率出现同一个mapper两种数据库使用,因此这个配置项几乎不可能用上。
<!-- mybatis-config.xml --> <databaseIdProvider type="DB_VENDOR"> <property name="SQL Server" value="sqlserver"/> <property name="DB2" value="db2"/> <property name="Oracle" value="oracle" /> <property name="Mysql" value="mysql" /> </databaseIdProvider>
<!-- mapper.xml --> <insert id="insertTest" ...> INSERT INTO users(name, age) VALUES('zhangsan', 1), ('wangwu', 2), ('zhaoliu', 3); </insert> <insert id="insertTest" ... databaseId="oracle"> INSERT ALL INTO users VALUES('zhangsan', 1) INTO users VALUES ('wangwu', 2) INTO users VALUES ('zhaoliu', 3); </insert>