MyBatis 入门教程之二
大纲
- MyBatis 入门教程之一
- MyBatis 入门教程之二
- MyBatis 入门教程之三
- MyBatis 入门教程之四
- MyBatis 入门教程之五
- MyBatis 入门教程之六
- MyBatis 入门教程之七
- MyBatis 入门教程之八
前言
本节所需的案例代码,可以直接从 GitHub 下载对应章节 mybatis-lesson-3
。
MyBatis 全局配置文件
MyBatis 默认的全局配置文件是 mybatis-config.xml
,且 XML 配置文件里的标签是有顺序的,由前到后依次是 properties, settings, typeAliases, typeHandlers, objectFactory, objectWrapperFactory, reflectorFactory, plugins, environments, databaseIdProvider, mappers
。
属性(properties)
在企业级开发中,往往会将数据库的连接信息写到单独的配置文件中,这样日后方便统一管理。MyBatis 为此提供了 properties
标签,用于读取外部的的 Properties 配置文件,读取到的属性值可以在整个 MyBatis 配置文件中用来替换需要动态配置的属性值。
- config.properties
1 | dataSource.user=root |
- mybatis-config.xml
1 | <configuration> |
提示
上述例子中的 driver
将会由 properties 标签的子标签中设置的相应值来替换。,而 url
、username
和 password
属性将会由 config.properties
配置文件中对应的值来替换,这样就为配置提供了诸多灵活选择。
值得一提的是,开发人员还可以在 SqlSessionFactoryBuilder.build()
方法中传入属性值,示例代码如下:
1 | SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, props); |
如果一个属性在不只一个地方进行了配置,那么 MyBatis 将按照下面的顺序来加载:
- 首先读取在 properties 标签中指定的属性。
- 然后根据 properties 标签中的
resource
属性读取类路径下配置文件,或者根据url
属性指定的路径读取配置文件,并覆盖之前读取过的同名属性。 - 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。
- 因此,通过方法参数传递的属性具有最高优先级,
resource/url
属性中指定的配置文件次之,最低优先级的则是 properties 标签中指定的属性。
设置(settings)
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为,完整的配置属性请看这里。配置示例如下:
1 | <configuration> |
类型别名(typeAliases)
类型别名可以为 Java 类型设置一个缩写名字。它仅用于 XML 配置中,意在降低冗余的全限定类名书写,方便引用某个类。例如:
1 | <configuration> |
当在 XML 里这样配置时,Employee
可以用在任何使用 com.clay.mybatis.bean.Employee
的地方。例如 SQL 映射文件里的 resultType
可以在直接使用类型别名,这样可以省去全限定类名的书写,即 resultType="com.clay.mybatis.bean.Employee"
。
1 | <mapper namespace="com.clay.mybatis.dao.EmployeeMapper"> |
值得一提的是,在类很多的情况下,还可以指定一个包名,MyBatis 会在包名(包括子包)下面搜索需要的 Java Bean。例如:
1 | <configuration> |
每一个在包 com.clay.mybatis.bean
中的 Java Bean,在没有使用 @Alias
注解的情况下,默认会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 com.clay.mybatis.bean.Employee
的默认别名为 employee
;若有注解,则别名为其注解值。
1 |
|
提示
- 在 MyBatis 中,类型别名不区分大小写
- 当 Java 包和子包里有相同名称的类时,使用包名作为类型别名则会出错,这时候应该使用
@Alias
注解来解决类型别名冲突的问题 - MyBatis 已经为许多常见的 Java 类型内建了相应的类型别名,它们都是不区分大小写的,并为了应对原始类型的命名重复,采取了特殊的命名风格,查看内建的类型别名列表
类型处理器(typeHandlers)
MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时,都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。
默认的类型处理器
MyBatis 内置了一些默认的类型处理器,完整的类型处理器列表如下:
自定义类型处理器
在项目开发中,可以实现 org.apache.ibatis.type.TypeHandler
接口或者继承 org.apache.ibatis.type.BaseTypeHandler
类,以此来重写已有的类型处理器或创建自己的类型处理器来处理不支持的或非标准的类型。
- 自定义类型处理器
1 |
|
- 注册类型处理器
1 | <configuration> |
使用上述自定义的类型处理器将会覆盖已有的处理 Java String 类型的属性以及 VARCHAR
类型的参数和结果的类型处理器。特别注意,MyBatis 不会通过检测数据库元信息来决定使用哪种 JDBC 类型,所以必须在参数和结果映射中指明字段是 VARCHAR
类型,以使其能够绑定到正确的类型处理器上,这是因为 MyBatis 直到 SQL 语句被执行时才清楚数据类型。
日期时间类型处理器
日期和时间的处理,在 JDK1.8 以前一直是个头疼的问题。我们通常使用 JSR-310(日期和时间 API) 规范领导者 Stephen Colebourne 创建的 Joda-Time
来操作。JDK 1.8 已经实现了全部的 JSR-310 规范,而且 MyBatis 从 3.4.5 开始,默认支持 JSR-310 规范。因此在日期时间处理上,我们可以使用 MyBatis 基于 JSR-310 编写的各种日期时间类型处理器。MyBatis 3.4.5 以前的版本需要我们手动注册这些处理器,以后的版本则都是自动注册的。手动注册的步骤如下:
- 引入类型转换的依赖
1 | <dependency> |
- 注册日期时间类型处理器
1 | <configuration> |
提示
在 POJO 类里面,可以使用 java.sql.Date
、java.sql.Timestamp
、java.util.Date
分别映射数据库的 date
、timestamp
、datetime
类型,但是这些类许多方法都已经过时。Java 1.8 API 中的 LocalDate
、LocalDateTime
、LocalTime
现在则比较常用。
对象工厂(objectFactory)
每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现。
- 自定义对象工厂
1 | public class ExampleObjectFactory extends DefaultObjectFactory { |
- 注册对象工厂
1 | <configuration> |
ObjectFactory 接口很简单,它包含两个创建实例用的方法,一个是处理默认无参构造方法的,另外一个是处理带参数的构造方法的。 另外,setProperties
方法可以被用来配置 ObjectFactory,在初始化自定义的 ObjectFactory 实例后,objectFactory
标签内定义的属性会被传递给 setProperties
方法。
插件(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
接口,并指定想要拦截的方法即可。
- 自定义插件
1 |
|
- 注册插件
1 | <configuration> |
上面自定义的插件将会拦截在 Executor 实例中所有的 update
方法调用,这里的 Executor 是负责执行底层映射语句的内部对象。
环境配置(environments)
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中,现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射,还有许多类似的使用场景。值得一提的是,尽管 MyBatis 可以配置多个环境,但每个 SqlSessionFactory
实例只能选择一种环境。所以,如果想连接两个数据库,就需要创建两个 SqlSessionFactory
实例,每个数据库对应一个;而如果是三个数据库,就需要三个实例,依此类推。
1 | <configuration> |
注意一些关键点:
- 默认使用的环境 ID(比如:
default="development"
) - 每个
environment
标签定义的环境 ID(比如:id="development"
) - 事务管理器的配置(比如:
type="JDBC"
) - 数据源的配置(比如:
type="POOLED"
) - 默认环境和环境 ID 顾名思义。环境 ID 可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID
为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder
即可,可以接受环境配置的两个方法是:
1 | SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment); |
如果忽略了环境参数,那么将会加载默认环境,如下所示:
1 | SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader); |
事务管理器
在 MyBatis 中有两种类型的事务管理器(transactionManager),也就是 type="[JDBC | MANAGED]"
。
JDBC
JDBC 事务管理器直接使用了 JDBC 的提交和回滚功能,它依赖从数据源获得的连接来管理事务作用域。默认情况下,为了与某些驱动程序兼容,它在关闭连接时启用自动提交。然而,对于某些驱动程序来说,启用自动提交不仅是不必要的,而且是一个代价高昂的操作。因此,从 3.5.10
版本开始,可以通过将 skipSetAutoCommitOnClose
属性设置为 true
来跳过这个步骤。例如:
1 | <transactionManager type="JDBC"> |
MANAGED
MANAGED 事务管理器几乎没做什么。它不会提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection
属性设置为 false
来阻止默认的关闭行为。例如:
1 | <transactionManager type="MANAGED"> |
提示
如果使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。
数据源
dataSource 标签使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。大多数 MyBatis 应用程序会配置数据源,虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。MyBatis 提供了三种内建的数据源类型,也就是 type="[UNPOOLED | POOLED | JNDI]"
。
UNPOOLED
UNPOOLED 数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。
driver
– 这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能包含的数据源类)。url
– 这是数据库的 JDBC URL 地址。username
– 登录数据库的用户名。password
– 登录数据库的密码。defaultTransactionIsolationLevel
– 默认的连接事务隔离级别。defaultNetworkTimeout
– 等待数据库操作完成的默认网络超时时间(单位:毫秒)。
作为可选项,你也可以传递属性给数据库驱动。只需在属性名加上 driver.
前缀即可,例如:driver.encoding=UTF8
。这将通过 DriverManager.getConnection(url, driverProperties)
方法传递值为 UTF8 的 encoding
属性给数据库驱动。
POOLED
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
JNDI 数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。这种数据源配置只需要两个属性:
initial_context
– 这个属性用来在 InitialContext 中寻找上下文,即initialContext.lookup(initial_context))
。这是个可选属性,如果忽略,那么将会直接从 InitialContext 中寻找data_source
属性。data_source
– 这是引用数据源实例位置的上下文路径。提供了initial_context
配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找。
JNDI 和其他数据源配置类似,可以通过添加前缀 env.
直接把属性传递给 InitialContext。比如 env.encoding=UTF8
,这将会在 InitialContext 实例化时往它的构造方法传递值为 UTF8 的 encoding
属性。
通过过实现接口 org.apache.ibatis.datasource.DataSourceFactory
来自定义第三方数据源的实现:
1 | public interface DataSourceFactory { |
org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory
可被用作父类来构建新的数据源适配器,比如下面这段插入 C3P0
数据源所必需的代码:
1 | import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory; |
为了令自定义的数据源工作,记得在配置文件中为每个希望 MyBatis 调用的 setter
方法增加对应的属性。下面是一个可以连接至 PostgreSQL 数据库的例子:
1 | <dataSource type="org.myproject.C3P0DataSourceFactory"> |
数据库厂商标识(databaseIdProvider)
MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId
属性。 MyBatis 会加载带有匹配当前数据库 databaseId
属性和所有不带 databaseId
属性的语句。如果同时找到带有 databaseId
和不带 databaseId
的相同语句,则后者会被舍弃。为支持多厂商特性,只要像下面这样在 mybatis-config.xml
文件中加入 databaseIdProvider
即可:
1 | <databaseIdProvider type="DB_VENDOR" /> |
databaseIdProvider
对应的 DB_VENDOR 实现(VendorDatabaseIdProvider)会将 databaseId
设置为 DatabaseMetaData#getDatabaseProductName()
返回的字符串。由于通常情况下这些字符串都非常长,而且相同产品的不同版本会返回不同的值,此时通过设置属性别名来使其变短:
1 | <configuration> |
提示
在上述的配置中,name
是 getDatabaseProductName()
返回字符串内容的一部分,value
则是属性别名。
在提供了属性别名时,databaseIdProvider
的 DB_VENDOR 实现会将 databaseId
设置为数据库产品名与属性中的名称第一个相匹配的值,如果没有匹配的属性,将会设置为 null
。举个例子,假设 getDatabaseProductName()
返回的字符串内容为 “Oracle (DataDirect)”,则 databaseId
将被设置为 oracle
。最后需要在编写 SQL 语句时,通过 databaseId
指定属性别名,例如:
1 | <select id="getById" resultType="Employee" databaseId="mysql"> |
映射器(mappers)
既然 MyBatis 的行为已经由上述标签配置完了,那么现在就需要定义 SQL 映射语句了。但首先,需要告诉 MyBatis 到哪里去找到这些语句。在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。MyBatis 可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:///
形式的 URL),或类名和包名等。例如:
- 使用相对于类路径的资源引用
提示
如果在 SQL 映射文件中,namespace
是 DAO 接口的全限定类名,那么 MyBatis 会找到对应的 DAO 接口,然后为其生成代理对象。
1 | <configuration> |
- 使用完全限定资源定位符(URL)
提示
如果在 SQL 映射文件中,namespace
是 DAO 接口的全限定类名,那么 MyBatis 会找到对应的 DAO 接口,然后为其生成代理对象。
1 | <configuration> |
- 使用映射器接口实现类的完全限定类名
特别注意
- 第一种情况:没有 SQL 映射文件,SQL 语句全部通过注解的方式写在 DAO 接口里,此时可以直接使用此方式
- 第二种情况:有 SQL 语句写在 SQL 映射文件里,此时必须满足以下两个条件,否则 MyBatis 无法正常映射 SQL 语句
- SQL 映射文件必须与 Java 接口文件同名
- SQL 映射文件必须与 Java 接口文件放在同一个文件夹下(包)
1 | <configuration> |
- 将包内的映射器接口实现全部注册为映射器
特别注意
上述在 "使用映射器接口实现类的完全限定类名" 方式中,介绍到的两种情况也适用于此方式
1 | <configuration> |
提示
- 比较重要的、复杂的 DAO 接口,建议将 SQL 语句写在 SQL 映射文件里
- 不重要的、简单的 DAO 接口,为了快速开发项目,可以通过注解将 SQL 语句写在 DAO 接口里