封面:洛小汐
做者:潘潘 java
作大事和作小事的难度是同样的。二者都会消耗你的时间和精力,因此若是决心作事,就要作大事,要确保你的梦想值得追求,将来的收获能够配得上你的努力。mysql
上一篇文章 《Mybatis系列全解(三):Mybatis简单CRUD使用介绍》 ,咱们基本上手了 Mybatis 的增删改查操做,也感觉到 Mybatis 的简单高效舒美,可是确定有部分朋友对于 Mybatis 的配置文件只是了解基本组成和大体用法,尚无一套完整的结构记忆,因此本篇文章咱们将详细的介绍 Mybatis 的配置全貌,毕竟 Mybatis 的配置文件对于整个 Mybatis 体系的构建与支撑有着深远的影响。git
Mybatis系列全解脑图分享,持续更新中github
一、为何要使用配置文件sql
二、Mybatis 配置全貌数据库
三、XML 核心配置apache
四、XML 映射文件windows
五、总结数组
试想,若是没有配置文件,咱们的应用程序将只能沿着固定的姿态运行,几乎不能作任何动态的调整,那么这不是一套完美的设计,由于咱们但愿拥有更宽更灵活的操做空间和更多的兼容度,同时也能解决硬编码等问题,因此咱们须要有配置文件,对应用程序进行参数预设和设置初始化工做。缓存
那咱们为什么钟情XML?
首先,固然是 XML 配置文件自己就足够优秀,格式规范,存储小,跨平台,读取快...等等,所谓窈窕淑女,谁人不爱。
其次,也是一个重要影响因素,就是各大领域大佬的支持,像微软、像Java系...等等,世上本无路,只是走的人多了,也就成了路 ( 这句话是鲁迅老先生说的)。
因此,Mybatis选择搭配XML配置,实属合理。
Mybatis框架自己,理论上就一个配置文件,其实也只须要一个配置文件,即mybatis-config.xml (固然文件名容许自由命名),只不过这个配置文件其中的一个属性mappers(映射器),因为可能产生过多的SQL映射文件,因而咱们物理上单独拓展出来,容许使用者定义任意数量的 xxxMapper.xml 映射文件。
把SQL映射文件单独配置,是有好处的,一是灵活度上容许任意拓展,二也避免了其它无需常常变更的属性配置遭遇误改。
咱们看看Mybatis官网给出的配置文件层次结构:
实际配置文件XML内容以下,除了约束头 <?xml> 与 <!DOCTYPE>,
其他标签元素都是 Mybatis 的核心配置属性 :
<?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> <!-- 一、属性:例如jdbc.properties --> <properties resource="jdbc.properties"></properties> <!-- 二、设置:定义全局性设置,例如开启二级缓存 --> <settings> <setting name="cacheEnabled" value="true"/> </settings> <!-- 三、类型名称:为一些类定义别名 --> <typeAliases> <typeAlias type="com.panshenlian.pojo.User" alias="user"></typeAlias> </typeAliases> <!-- 四、类型处理器:定义Java类型与数据库中的数据类型之间的转换关系 --> <typeHandlers></typeHandlers> <!-- 五、对象工厂 --> <objectFactory type=""></objectFactory> <!-- 六、插件:mybatis的插件,支持自定义插件 --> <plugins> <plugin interceptor=""></plugin> </plugins> <!-- 七、环境:配置mybatis的环境 --> <environments default="development"> <!-- 环境变量:支持多套环境变量,例如开发环境、生产环境 --> <environment id="development"> <!-- 事务管理器:默认JDBC --> <transactionManager type="JDBC" /> <!-- 数据源:使用链接池,并加载mysql驱动链接数据库 --> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/mybatis" /> <property name="username" value="root" /> <property name="password" value="123456" /> </dataSource> </environment> </environments> <!-- 八、数据库厂商标识 --> <databaseIdProvider type=""></databaseIdProvider> <!-- 九、映射器:指定映射文件或者映射类 --> <mappers> <mapper resource="UserMapper.xml" /> </mappers> </configuration>
必须注意:Mybatis配置文件的属性位置顺序是 固定 的,不容许 颠倒顺序,不然 Mybatis 在解析 XML 文件的时候就会抛出异常,这个与 Mybatis 框架启动加载配置信息顺序有关,后续咱们源码分析会讲到。
以上基本可以清晰看明白 Mybatis 配置文件的层次结构关系,咱们简单画一张脑图:
基本是须要咱们掌握 9 大顶级元素配置,其中标记 橘红色 的属性配置,因为涉及 插件 和 动态SQL ,插件配置能够应用于分页与功能加强等,动态SQL例如 if 标签、where 标签、foreach标签等,初步理解为应用于SQL语句拼接。这两块属于 Mybatis 的两个特性,咱们后续单独详细进行梳理讨论。
咱们的核心配置文件 configuration(配置)做为最顶级节点,其他 9 大属性都必须嵌套在其内,对于内部 9 大节点,咱们逐一讲解:
属性标签,显而易见就是提供属性配置,可进行动态替换,通常能够在 Java 属性文件中配置,例如 jdbc.properties 配置文件 ,或经过 properties 元素标签中的子元素 property 来指定配置。
举例咱们须要配置数据源信息,采用 property 标签能够这样配置:
<properties> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/myDB"/> <property name="username" value="user1"/> <property name="password" value="123456"/> </properties>
设置好的属性能够在整个配置文件中用来替换须要动态配置的属性值。好比:
<dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource>
或者咱们使用 Java 中的属性配置文件,把属性配置元素具体化到一个属性文件中,而且使用属性文件的 key 名做为占位符。例如 jdbc.properties
driver=com.mysql.jdbc.Driver url=jdbc\:mysql\://127.0.0.1\:3306/myDB username=root password=123456
使用时咱们把属性文件引入,并使用文件中定义的占位符,例如 db.driver :
<!-- 引入属性配置文件 --> <properties resource="jdbc.properties"></properties> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource>
可是问题来了,当咱们既使用 *.properties 配置文件,同时又设置了 property 元素值,Mybatis 会使用哪边配置的属性值呢? 例如这种状况 :
<properties resource="jdbc.properties"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/myDB"/> <property name="username" value="user1"/> <property name="password" value="123456"/> </properties>
这里,若是在 property 标签元素与 jdbc.properties 文件中同时存在相同属性,那么属性文件将会覆盖 property 标签元素的属性,例如最终 username属性值会使用 jdbc.properties 文件中设置的 root,而不会使用属性元素设置的 user1 。这样实际为配置提供了诸多灵活选择。
另外,properties 元素容许配置 resource 属性或 url 属性,只能二选一,要么使用 resource 指定本地的配置文件,要么使用 url 指定远程的配置文件,由于 Mybatis 在加载配置时,若是发现 url 与 resource 同时存在,会抛出异常禁止。
<!-- 配置resource--> <properties resource="xxx.properties"> <property name="driver" value="com.mysql.jdbc.Driver"/> </properties> <!-- 配置url--> <properties url="http://xxxx"> <property name="driver" value="com.mysql.jdbc.Driver"/> </properties>
还有一种状况,像 Mybatis 在解析配置的时候,也能够在 Java 代码中构建属性 java.util.Properties 属性对象并传递到 SqlSessionFactoryBuilder.build() 方法中,例如:
// 构建属性对象 Properties props = new Properties(); props.setProperty("driver","com.mysql.jdbc.Driver"); props.setProperty("url","jdbc:mysql://127.0.0.1:3306/myDB"); props.setProperty("username","user1"); props.setProperty("password","123456"); // 传递属性构建 SqlSessionFactory SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, props);
那么这三种方式都容许配置,那在属性配置重复的状况下,优先级别是怎样呢?
properties 优先级
一、第一优先级:在 Java 代码中构建的 properties 属性对象;
二、第二优先级:经过属性 resource 或 url 读取到的本地文件或远程文件;
三、第三优先级:直接在 properties 内部子标签元素 property 中设置的属性。
注意,在实际开发中,为了不给后期维护形成困扰,建议使用单一种配置方式。
settings 标签元素,是 MyBatis 中极为重要的调整设置,它们会动态改变 MyBatis 的运行时行为,这些配置就像 Mybatis 内置的许多功能,当你须要使用时能够根据须要灵活调整,而且 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"/> <... more .../> </settings>
属性cacheEnabled
属性lazyLoadingEnabled
属性 aggressiveLazyLoading
属性 multipleResultSetsEnabled
属性 useColumnLabel
属性 useGeneratedKeys
属性 autoMappingBehavior
属性 autoMappingUnknownColumnBehavior
属性 defaultExecutorType
属性 defaultStatementTimeout
属性 defaultFetchSize
属性 defaultResultSetType
属性 safeRowBoundsEnabled
属性 safeResultHandlerEnabled
属性 mapUnderscoreToCamelCase
属性 localCacheScope
属性 jdbcTypeForNull
属性 lazyLoadTriggerMethods
属性 defaultScriptingLanguage
属性 defaultEnumTypeHandler
属性 callSettersOnNulls
属性 returnInstanceForEmptyRow
属性 logPrefix
属性 logImpl
属性 proxyFactory
属性 vfsImpl
属性 useActualParamName
属性 configurationFactory
属性 shrinkWhitespacesInSql
settings 支持了特别多功能支持,其实常规开发中使用到的属性项不会特别多,除非项目有特殊要求,因此建议你们把这些设置当作字典便可,没必要详记 每个属性使用,须要时翻阅研读。
类型别名能够给 Java 类型设置一个简称。 它仅用于 XML 配置,意在下降冗余的全限定类名书写,由于书写类的全限定名太长了,咱们但愿有一个简称来指代它。类型别名在 Mybatis 中分为 系统内置 和 用户自定义 两类,Mybatis 会在解析配置文件时把 typeAliases 实例存储进入 Configuration 对象中,须要使用时直接获取。
通常咱们能够自定义别名,例如:
<typeAliases> <typeAlias alias="Author" type="domain.blog.Author"/> <typeAlias alias="Blog" type="domain.blog.Blog"/> </typeAliases>
像这样配置时,咱们就能够在任何须要使用 domain.blog.Author 的地方,直接使用别名 author 。
可是,若是遇到项目中特别多 Java 类须要配置别名,怎么更快的设置呢?
能够指定一个包名进行扫描,MyBatis 会在包名下面扫描须要的 Java Bean,好比:
<typeAliases> <package name="domain.blog"/> </typeAliases>
每个在包 domain.blog 中的 Java Bean,在没有注解的状况下,会使用 Bean 的首字母小写的非限定类名来做为它的别名。 好比 domain.blog.Author 的别名为 author;如有 注解 ,则别名为其自定义的注解值。见下面的例子:
@Alias("myAuthor") public class Author { ... }
Mybatis 已经为许多常见的 Java 类型内建了相应的类型别名。下面就是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采起了特殊的命名风格,能够发现 基本类型 的别名前缀都有下划线 ‘_’,而基本类型的 包装类 则没有,这个须要注意:
咱们能够经过源码查看内置的类型别名的注册信息。
具体源码路径在 org.apache.ibatis.type.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); }
别名是不区分大小写的,同时也支持数组类型,只须要加 “[]” 便可使用,好比 Long 数组别名咱们能够用 long[] 直接代替,例如在实际开发中,int 、INT 、integer 、INTEGER 都是表明 Integer , 这里主要因为 MyBatis 在注册别名的时候会所有转为小写字母进行存储,另外以上列表 无需牢记,仅仅在须要使用的时候查阅便可,基本也均可以看得明白。
MyBatis 在设置预处理SQL语句(PreparedStatement)中所须要的 参数 或从 结果集 ResultSet 中获取对象时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。
类型处理器,主要用于处理 Java 类型与 JDBC 类型的映射匹配关系处理,下表描述了一些默认的类型处理器。
咱们能够经过源码查看内置的类型别名的注册信息。
具体源码路径在 org.apache.ibatis.type.TypeHandlerRegistry # 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()); }
从 3.4.5 开始,MyBatis 默认支持 JSR-310(日期和时间 API) ,能够在以上源码上看到新增支持。
通常,你能够重写已有的类型处理器,
或根据业务须要建立你本身的类型处理器,
以处理不支持的类型或非标准的类型。
具体作法为:
一、实现 org.apache.ibatis.type.TypeHandler
接口;
二、继承 org.apache.ibatis.type.BaseTypeHandler
类。
自己 BaseTypeHandler 类做为抽象类就已经实现了 TypeHandler 接口。
因此咱们看到接口 TypeHandler 定义了四个方法:
public interface TypeHandler<T> { void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; T getResult(ResultSet rs, String columnName) throws SQLException; T getResult(ResultSet rs, int columnIndex) throws SQLException; T getResult(CallableStatement cs, int columnIndex) throws SQLException; }
从方法名 setParameter 和 getResult 咱们就能够知道,是发生在预编译时设置参数(增删改查传入参数)与查询结果集后转换为 Java 类型时,类型处理器发挥做用。
具体实现以下,先自定义类型处理器类 MyExampleTypeHandler :
// MyExampleTypeHandler.java @MappedJdbcTypes(JdbcType.VARCHAR) public class MyExampleTypeHandler extends BaseTypeHandler<String> { @Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, parameter); } @Override public String getNullableResult(ResultSet rs, String columnName) throws SQLException { return rs.getString(columnName); } @Override public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return rs.getString(columnIndex); } @Override public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return cs.getString(columnIndex); } }
自定义类已设定:JdbcType.VARCHAR 与 String 类作映射转换(注解和泛型已体现)。
其次,在核心配置文件中设置类型处理器:
<!-- mybatis-config.xml --> <typeHandlers> <typeHandler handler="org.mybatis.example.MyExampleTypeHandler"/> </typeHandlers>
或者不使用注解方式的话,取消 @MappedJdbcTypes(JdbcType.VARCHAR) 注解,直接在 xml 配置中指定 jdbcType 与 javaType 映射 :
<!-- mybatis-config.xml --> <typeHandlers> <typeHandler jdbcType="VARCHAR" javaType="string" handler="org.mybatis.example.MyExampleTypeHandler"/> </typeHandlers>
记住, typeHandler 的配置方式优先级高于注解配置方式。
这里,自定义类型处理器将会覆盖已有的处理 Java String 类型的属性以及 VARCHAR 类型的参数和结果的类型处理器,基本以上步骤就已经自定了 JdbcType.VARCHAR 与 String类作映射转换。
其实到这里,咱们基本也就完成了类型处理器的自定义转换,可是有一种状况,就是咱们但愿咱们自定义的类型处理器只处理某一个 Java 实体中的 JdbcType.VARCHAR 与 String 类映射转换,其它实体的处理仍是使用系统内置的转换,很简单,咱们只须要把以上两步都去掉,在自定义类型处理类的注解@javaType和@MappedJdbcTypes都移除,配置文件中把 typehandler 属性配置移除,直接在映射文件中编写:
<resultMap id="MyResultMap" type="com.panshenlian.pojo.User"> <!-- id为int类型,可是没指定自定义类型处理器,不受影响--> <id column="id" property="id" /> <!-- username为String类型,可是没指定自定义类型处理器,不受影响--> <id column="username" property="username" /> <!-- password为String类型,可是没指定自定义类型处理器,不受影响--> <id column="password" property="password" /> <!-- birthday为String类型,指定自定义类型处理器,受影响!--> <id column="birthday" property="birthday" typeHandler="com.panshenlian.typeHandler.MyStringHandler"/> </resultMap> <select id="findAll" resultType="com.panshenlian.pojo.User" resultMap="MyResultMap"> select * from User </select>
User 实体参考:
package com.panshenlian.pojo; /** * @Author: panshenlian * @Description: 用户实体 * @Date: Create in 2:08 2020/12/07 */ public class User { private int id; private String username; private String password; private String birthday; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getBirthday() { return birthday; } public void setBirthday(String birthday) { this.birthday = birthday; } }
最终自定义类型处理器,只会对 birthday 字段产生影响,其他字段均不受影响。
自定义类型处理器很灵活,只有当指定对应的 Java 类型和 Jdbc 类型时,处理器才会具体生效,不然 Mybatis 会默认匹配系统内置的类型处理器。
另外,当咱们自定义不少类型处理器时,系统支持配置包扫描的方式查找类型处理器:
<!-- mybatis-config.xml --> <typeHandlers> <package name="org.mybatis.example"/> </typeHandlers>
注意在使用自动发现功能的时候,只能经过注解方式来指定 JDBC 的类型。
你能够建立可以处理多个类的泛型类型处理器。为了使用泛型类型处理器, 须要增长一个接受该类的 class 做为参数的构造器,这样 MyBatis 会在构造一个类型处理器实例的时候传入一个具体的类。
//GenericTypeHandler.java public class GenericTypeHandler<E extends MyObject> extends BaseTypeHandler<E> { private Class<E> type; public GenericTypeHandler(Class<E> type) { if (type == null) throw new IllegalArgumentException("Type argument cannot be null"); this.type = type; } ...
处理枚举类型
若想映射枚举类型 Enum
,则须要从 EnumTypeHandler
或者 EnumOrdinalTypeHandler
中选择一个来使用。
好比说咱们想存储取近似值时用到的舍入模式。默认状况下,MyBatis 会利用 EnumTypeHandler
来把 Enum
值转换成对应的名字。
注意
EnumTypeHandler
在某种意义上来讲是比较特别的,其它的处理器只针对某个特定的类,而它不一样,它会处理任意继承了Enum
的类。不过,咱们可能不想存储名字,相反咱们的 DBA 会坚持使用整形值代码。那也同样简单:在配置文件中把
EnumOrdinalTypeHandler
加到typeHandlers
中便可, 这样每一个RoundingMode
将经过他们的序数值来映射成对应的整形数值。
<!-- mybatis-config.xml --> <typeHandlers> <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="java.math.RoundingMode"/> </typeHandlers>
但要是你想在一个地方将 Enum
映射成字符串,在另一个地方映射成整形值呢?
自动映射器(auto-mapper)会自动地选用 EnumOrdinalTypeHandler
来处理枚举类型, 因此若是咱们想用普通的 EnumTypeHandler
,就必需要显式地为那些 SQL 语句设置要使用的类型处理器。
下一篇文章咱们才开始介绍映射器 mapper.xml 文件,若是你首次阅读映射器概念,可能须要先跳过这里先去了解 mapper.xml 文件配置,再回头过来看。
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.apache.ibatis.submitted.rounding.Mapper"> <resultMap type="org.apache.ibatis.submitted.rounding.User" id="usermap"> <id column="id" property="id"/> <result column="name" property="name"/> <result column="funkyNumber" property="funkyNumber"/> <result column="roundingMode" property="roundingMode"/> </resultMap> <select id="getUser" resultMap="usermap"> select * from users </select> <insert id="insert"> insert into users (id, name, funkyNumber, roundingMode) values ( #{id}, #{name}, #{funkyNumber}, #{roundingMode} ) </insert> <resultMap type="org.apache.ibatis.submitted.rounding.User" id="usermap2"> <id column="id" property="id"/> <result column="name" property="name"/> <result column="funkyNumber" property="funkyNumber"/> <result column="roundingMode" property="roundingMode" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/> </resultMap> <select id="getUser2" resultMap="usermap2"> select * from users2 </select> <insert id="insert2"> insert into users2 (id, name, funkyNumber, roundingMode) values ( #{id}, #{name}, #{funkyNumber}, #{roundingMode, typeHandler=org.apache.ibatis.type.EnumTypeHandler} ) </insert> </mapper>
注意,这里的 select 语句强制使用 resultMap
来代替 resultType
。
每次 MyBatis 建立结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工做。 默认的对象工厂须要作的仅仅是实例化目标类,要么经过默认无参构造方法,要么经过存在的参数映射来调用带有参数的构造方法。 若是想覆盖对象工厂的默认行为,能够经过建立本身的对象工厂来实现。好比:
// ExampleObjectFactory.java public class ExampleObjectFactory extends DefaultObjectFactory { public Object create(Class type) { return super.create(type); } public Object create(Class type, List constructorArgTypes, List constructorArgs) { return super.create(type, constructorArgTypes, constructorArgs); } public void setProperties(Properties properties) { super.setProperties(properties); } public boolean isCollection(Class type) { return Collection.class.isAssignableFrom(type); } }
<!-- mybatis-config.xml --> <objectFactory type="org.mybatis.example.ExampleObjectFactory"> <property name="someProperty" value="100"/> </objectFactory>
ObjectFactory 接口很简单,它包含两个建立用的方法,一个是处理默认构造方法的,另一个是处理带参数的构造方法的。 最后,setProperties 方法能够被用来配置 ObjectFactory,在初始化你的 ObjectFactory 实例后, objectFactory 元素体中定义的属性会被传递给 setProperties 方法。
正常状况下咱们不须要使用到,或者说不建议使用,除非业务上确实须要对一个特殊实体初始构造作一个默认属性值配置等处理,其他状况不推荐使用,避免产生不可控风险。
MyBatis 容许你在映射语句执行过程当中的某一点进行拦截调用。默认状况下,MyBatis 容许使用插件来拦截的方法调用包括:
插件功能主要开放拦截的对象就是以上列举的 Mybatis 四大组件,后续咱们讲 Mybatis 核心API 的时候或者单独介绍自定义插件的时候会详细说明,这里你们能够先大体了解,包括数据分页、操做日志加强、sql 性能监控等均可以经过插件实现,不过会存储改造的风险,毕竟这些都是核心的 API 。
这四大类中方法具体能够经过查看每一个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 若是你想作的不只仅是监控方法的调用,那么你最好至关了解要重写的方法的行为。 由于在试图修改或重写已有方法的行为时,极可能会破坏 MyBatis 的核心模块。 这些都是更底层的类和方法,因此使用插件的时候要特别小心。
经过 MyBatis 提供的强大机制,使用插件是很是简单的,只需实现 Interceptor 接口,并指定想要拦截的类,方法,参数(因为有多态的状况)便可。
// ExamplePlugin.java @Intercepts({ @Signature( type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})}) public class ExamplePlugin implements Interceptor { private Properties properties = new Properties(); public Object intercept(Invocation invocation) throws Throwable { // implement pre processing if need Object returnObject = invocation.proceed(); // implement post processing if need return returnObject; } public void setProperties(Properties properties) { this.properties = properties; } }
<!-- mybatis-config.xml --> <plugins> <plugin interceptor="org.mybatis.example.ExamplePlugin"> <property name="someProperty" value="100"/> </plugin> </plugins>
上面的插件将会拦截在 Executor 实例中全部的 “update” 方法调用, 这里的 Executor 是负责执行底层映射语句的内部对象。
覆盖配置类 「 谨慎使用,存在风险 」
除了用插件来修改 MyBatis 核心行为之外,还能够经过彻底覆盖配置类来达到目的。只需继承配置类后覆盖其中的某个方法,再把它传递到 SqlSessionFactoryBuilder.build(myConfig) 方法便可。再次重申,这可能会极大影响 MyBatis 的行为,务请慎之又慎。
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);
environments 元素定义了如何配置环境。
<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>
注意一些关键点:
默认环境和环境 ID 顾名思义。 环境能够随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID。
事务管理器(transactionManager)
在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):
<transactionManager type="MANAGED"> <property name="closeConnection" value="false"/> </transactionManager>
若是你正在使用 Spring + MyBatis,则没有必要配置事务管理器,由于 Spring 模块会使用自带的管理器来覆盖前面的配置。这两种事务管理器类型都不须要设置任何属性。它们实际上是类型别名,换句话说,你能够用 TransactionFactory 接口实现类的全限定名或类型别名代替它们。
public interface TransactionFactory { default void setProperties(Properties props) { // 从 3.5.2 开始,该方法为默认方法 // 空实现 } Transaction newTransaction(Connection conn); Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit); }
在事务管理器实例化后,全部在 XML 中配置的属性将会被传递给 setProperties() 方法。你的实现还须要建立一个 Transaction 接口的实现类,这个接口也很简单:
public interface Transaction { Connection getConnection() throws SQLException; void commit() throws SQLException; void rollback() throws SQLException; void close() throws SQLException; Integer getTimeout() throws SQLException; }
使用这两个接口,你能够彻底自定义 MyBatis 对事务的处理。
数据源(dataSource)
dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 链接对象的资源。
大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但若是要启用延迟加载特性,就必须配置数据源。
有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):
UNPOOLED– 这个数据源的实现会每次请求时打开和关闭链接。虽然有点慢,但对那些数据库链接可用性要求不高的简单应用程序来讲,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来讲,使用链接池并不重要,这个配置就很适合这种情形。UNPOOLED 类型的数据源仅仅须要配置如下 5 种属性:
driver
– 这是 JDBC 驱动的 Java 类全限定名(并非 JDBC 驱动中可能包含的数据源类)。url
– 这是数据库的 JDBC URL 地址。username
– 登陆数据库的用户名。password
– 登陆数据库的密码。defaultTransactionIsolationLevel
– 默认的链接事务隔离级别。defaultNetworkTimeout
– 等待数据库操做完成的默认网络超时时间(单位:毫秒)。查看 java.sql.Connection#setNetworkTimeout()
的 API 文档以获取更多信息。做为可选项,你也能够传递属性给数据库驱动。只需在属性名加上“driver.”前缀便可,例如:
driver.encoding=UTF8
这将经过 DriverManager.getConnection(url, driverProperties) 方法传递值为
UTF8
的encoding
属性给数据库驱动。
POOLED– 这种数据源的实现利用“池”的概念将 JDBC 链接对象组织起来,避免了建立新的链接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。
除了上述提到 UNPOOLED 下的属性外,还有更多属性用来配置 POOLED 的数据源:
poolMaximumActiveConnections
– 在任意时间可存在的活动(正在使用)链接数量,默认值:10poolMaximumIdleConnections
– 任意时间可能存在的空闲链接数。poolMaximumCheckoutTime
– 在被强制返回以前,池中链接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒)poolTimeToWait
– 这是一个底层设置,若是获取链接花费了至关长的时间,链接池会打印状态日志并从新尝试获取一个链接(避免在误配置的状况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒)。poolMaximumLocalBadConnectionTolerance
– 这是一个关于坏链接容忍度的底层设置, 做用于每个尝试从缓存池获取链接的线程。 若是这个线程获取到的是一个坏的链接,那么这个数据源容许这个线程尝试从新获取一个新的链接,可是这个从新尝试的次数不该该超过 poolMaximumIdleConnections
与 poolMaximumLocalBadConnectionTolerance
之和。 默认值:3(新增于 3.4.5)poolPingQuery
– 发送到数据库的侦测查询,用来检验链接是否正常工做并准备接受请求。默认是“NO PING QUERY SET”,这会致使多数数据库驱动出错时返回恰当的错误消息。poolPingEnabled
– 是否启用侦测查询。若开启,须要设置 poolPingQuery
属性为一个可执行的 SQL 语句(最好是一个速度很是快的 SQL 语句),默认值:false。poolPingConnectionsNotUsedFor
– 配置 poolPingQuery 的频率。能够被设置为和数据库链接超时时间同样,来避免没必要要的侦测,默认值:0(即全部链接每一时刻都被侦测 — 固然仅当 poolPingEnabled 为 true 时适用)。JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器能够集中或在外部配置数据源,而后放置一个 JNDI 上下文的数据源引用。这种数据源配置只须要两个属性:
initial_context
– 这个属性用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))。这是个可选属性,若是忽略,那么将会直接从 InitialContext 中寻找 data_source 属性。data_source
– 这是引用数据源实例位置的上下文路径。提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找。JNDI 可理解是一种仿 windows 注册表形式的数据源。
和其余数据源配置相似,能够经过添加前缀“env.”直接把属性传递给 InitialContext。好比:
env.encoding=UTF8
这就会在 InitialContext 实例化时往它的构造方法传递值为 UTF8
的 encoding
属性。
你能够经过实现接口 org.apache.ibatis.datasource.DataSourceFactory
来使用第三方数据源实现:
public interface DataSourceFactory { void setProperties(Properties props); DataSource getDataSource(); }
org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory
可被用做父类来构建新的数据源适配器,好比下面这段插入 C3P0 数据源所必需的代码:
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory; import com.mchange.v2.c3p0.ComboPooledDataSource; public class C3P0DataSourceFactory extends UnpooledDataSourceFactory { public C3P0DataSourceFactory() { this.dataSource = new ComboPooledDataSource(); } }
为了令其工做,记得在配置文件中为每一个但愿 MyBatis 调用的 setter 方法增长对应的属性。 下面是一个能够链接至 PostgreSQL 数据库的例子:
<dataSource type="org.myproject.C3P0DataSourceFactory"> <property name="driver" value="org.postgresql.Driver"/> <property name="url" value="jdbc:postgresql:mydb"/> <property name="username" value="postgres"/> <property name="password" value="root"/> </dataSource>
MyBatis 能够根据不一样的数据库厂商执行不一样的语句,这种多厂商的支持是基于映射语句中的 databaseId
属性。 MyBatis 会加载带有匹配当前数据库 databaseId
属性和全部不带 databaseId
属性的语句。 若是同时找到带有 databaseId
和不带 databaseId
的相同语句,则后者会被舍弃。 为支持多厂商特性,只要像下面这样在 mybatis-config.xml 文件中加入 databaseIdProvider
便可:
<databaseIdProvider type="DB_VENDOR" />
databaseIdProvider 对应的 DB_VENDOR 实现会将 databaseId 设置为 DatabaseMetaData#getDatabaseProductName()
返回的字符串。 因为一般状况下这些字符串都很是长,并且相同产品的不一样版本会返回不一样的值,你可能想经过设置属性别名来使其变短:
<databaseIdProvider type="DB_VENDOR"> <property name="SQL Server" value="sqlserver"/> <property name="DB2" value="db2"/> <property name="Oracle" value="oracle" /> </databaseIdProvider>
在提供了属性别名时,databaseIdProvider 的 DB_VENDOR 实现会将 databaseId 设置为数据库产品名与属性中的名称第一个相匹配的值,若是没有匹配的属性,将会设置为 “null”。 在这个例子中,若是 getDatabaseProductName()
返回“Oracle (DataDirect)”,databaseId 将被设置为“oracle”。
你能够经过实现接口 org.apache.ibatis.mapping.DatabaseIdProvider
并在 mybatis-config.xml 中注册来构建本身的 DatabaseIdProvider:
public interface DatabaseIdProvider { default void setProperties(Properties p) { // 从 3.5.2 开始,该方法为默认方法 // 空实现 } String getDatabaseId(DataSource dataSource) throws SQLException; }
既然 MyBatis 的行为已经由上述元素配置完了,咱们如今就要来定义 SQL 映射语句了。 但首先,咱们须要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并无提供一个很好的解决方案,因此最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可使用相对于类路径的资源引用,或彻底限定资源定位符(包括 file:///
形式的 URL),或类名和包名等。例如:
<!-- 使用相对于类路径的资源引用 --> <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>
在 XML 核心配置文件介绍中,咱们介绍了映射文件 mapper.xml 的引入。
对于 Mapper 具体的映射配置文件,是 Mybatis 最复杂、最核心的组件,其中的标签内容体系也是特别详实,包括它的参数类型、动态SQL、定义SQL、缓存信息等等,咱们在下一篇文章中再进行梳理讨论,这里咱们简单引出。
本来我计划把核心配置文件和映射器 mapper 文件放一块讲,可是发现内容太多太多了,基本核心配置文件就已经讲得有点拖堂了,虽然这几大顶级标签使用起来已经绝不费力。SQL 映射器配置文件,咱们后续更新,这块基本是和咱们平常打交道最高频的操做。
本篇完,本系列下一篇咱们讲《 Mybatis系列全解(五):全网最全!详解Mybatis的Mapper映射文件 》。
BIU ~ 文章持续更新,微信搜索「潘潘和他的朋友们」第一时间阅读,随时有惊喜。本文会在 GitHub https://github.com/JavaWorld 收录,热腾腾的技术、框架、面经、解决方案,咱们都会以最美的姿式第一时间送达,欢迎 Star。