一. 什么是 MyBatis
MyBatis 是一款优秀的持久层框架,支持自定义 SQL、存储过程以及高级映射。它免除了几乎所有 JDBC代码以及手动设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(普通老式 Java 对象)为数据库中的记录。
简单来说,MyBatis 就是帮助开发者将数据存取到数据库中。
它是一个半自动化的ORM框架,即对象关系映射。传统的JDBC操作有许多重复代码块,比如数据取出时的封装、数据库的建立连接等,而通过框架可以减少这些冗余的代码,提高开发效率。
二. MyBatis Demo
详细 Demo 内容请参见 — 测试:MyBatisDemo
三. 核心配置文件解析
mybatis-config.xml
<?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核心配置文件-->
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSl=trur&sueUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/khr/dao/UserMapper.xml"/>
</mappers>
</configuration>
核心配置文件中包含了能够影响 MyBatis 运行行为的设置和属性信息,配置文档的顶层结构如下图所示:
1. XML 声明和 DOCTYPE
<?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">
开头指定了版本和字符编码。DOCTYPE 声明指定了配置文件的类型和文档类型定义(DTD),用于验证 XML 文件的结构。
2. configuration 元素
<configuration>
< ! -- 配置内容 -- >
</configuration>
根元素 configuration,包含所有的 MyBatis 配置信息。
3. environments 元素
<environments default = "development">
< ! -- 环境配置 -- >
</environments>
environments 元素包含了一组环境配置,可以定义多个环境,如开发、测试、生产等,并指定一个默认环境。
4. environment 元素
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSl=trur&sueUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSl=trur&sueUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
定义了具体的环境配置,id 属性用于标识环境。MyBatis 可以配置成多种环境,但每个 sqlSessionFactory 实例只能选择一种环境,上述代码中有环境 development 和 test,默认使用development。
5. transactionManager 元素
<transactionManager type="JDBC"/>
该元素配置了事务管理器,type 属性可以设置为 "JDBC"(用于使用JDBC的提交和回滚)或 "MANAGED"(由容器管理事务)。
6. dataSource 元素
<dataSource type="POOLED">
< ! -- 数据库连接属性 -- >
</dataSource>
该元素配置了数据源,type 属性可以设置为 "POOLED"(使用内置的连接池)或 "UNPOOLED" (每次交互创建新连接)。
7. property 元素
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/your_database_name?useSSl=trur&sueUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/>
<property name="username" value="your_username"/>
<property name="password" value="your_password"/>
该元素用于配置数据库连接的属性,如 JDBC 驱动类名、数据库URL、用户名和密码等。
8. mappers 元素
<mappers>
<mapper resource="com/khr/dao/UserMapper.xml"/>
</mappers>
该元素用于注册 MyBatis 的映射文件,文件中包含了 SQL 映射语句。
mybatis-config.xml 是 MyBatis 工作的基础,它告诉 MyBatis 如何管理数据库连接、事务以及如何找到 SQL 映射文件。此处只是最基础的配置信息,还有其它高级选项,如日志、缓存、类型别名、对象关系映射等。
四. 类型别名(typeAliases)
类型别名(typeAliases)可以为 Java 类型设置一个缩写名字,仅用于 XML 配置,可以降低冗余的全限定类名书写。
方法一:给实体类起别名
<configuration>
<typeAliases>
<typeAlias type="com.khr.pojo.User" alias="User"/>
</typeAliases>
……
</configuration>
typeAliases 属性一定要写在 environments 属性前面,否则会报错。
设置之后即可在实体类配置文件中使用:
<!--select查询语句-->
// <select id="getUserList" resultType="com.khr.pojo.User">
<select id="getUserList" resultType="User">
select * from mybatis.user
</select>
方法二:指定包名
<configuration>
<typeAliases>
<package name="com.khr.pojo"/>
</typeAliases>
……
</configuration>
每一个在包 com.khr.pojo 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。比如 com.khr.pojo.User 的别名为 user;若有注解,则别名为其注解值。
@Alias("othername")
public class User {
……
}
因此,当实体类较少时,推荐使用第一种方式;较多时,使用第二种。
五. 设置(settings)
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。
设置名 | 描述 | 有效值 | 默认值 |
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true | false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 | true | false | false |
aggressiveLazyLoading | 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。 | true | false | false |
multipleResultSetsEnabled | 是否允许单个语句返回多结果集(需要数据库驱动支持)。 | true | false | true |
useColumnLabel | 使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察。 | true | false | true |
useGeneratedKeys | 允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。 | true | false | False |
autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。 | NONE, PARTIAL, FULL | PARTIAL |
autoMappingUnknownColumnBehavior | 指定发现自动映射目标未知列(或未知属性类型)的行为。
| NONE, WARNING, FAILING | NONE |
defaultExecutorType | 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(PreparedStatement); BATCH 执行器不仅重用语句还会执行批量更新。 | SIMPLE REUSE BATCH | SIMPLE |
defaultStatementTimeout | 设置超时时间,它决定数据库驱动等待数据库响应的秒数。 | 任意正整数 | 未设置 (null) |
defaultFetchSize | 为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。 | 任意正整数 | 未设置 (null) |
defaultResultSetType | 指定语句默认的滚动策略。(新增于 3.5.2) | FORWARD_ONLY | SCROLL_SENSITIVE | SCROLL_INSENSITIVE | DEFAULT(等同于未设置) | 未设置 (null) |
safeRowBoundsEnabled | 是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。 | true | false | False |
safeResultHandlerEnabled | 是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为 false。 | true | false | True |
mapUnderscoreToCamelCase | 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 | true | false | False |
localCacheScope | MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。 | SESSION | STATEMENT | SESSION |
jdbcTypeForNull | 当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。 某些数据库驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 | JdbcType 常量,常用值:NULL、VARCHAR 或 OTHER。 | OTHER |
lazyLoadTriggerMethods | 指定对象的哪些方法触发一次延迟加载。 | 用逗号分隔的方法列表。 | equals,clone,hashCode,toString |
defaultScriptingLanguage | 指定动态 SQL 生成使用的默认脚本语言。 | 一个类型别名或全限定类名。 | org.apache.ibatis.scripting.xmltags.XMLLanguageDriver |
defaultEnumTypeHandler | 指定 Enum 使用的默认 TypeHandler 。(新增于 3.4.5) | 一个类型别名或全限定类名。 | org.apache.ibatis.type.EnumTypeHandler |
callSettersOnNulls | 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值进行初始化时比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。 | true | false | false |
returnInstanceForEmptyRow | 当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集(如集合或关联)。(新增于 3.4.2) | true | false | false |
logPrefix | 指定 MyBatis 增加到日志名称的前缀。 | 任何字符串 | 未设置 |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未设置 |
proxyFactory | 指定 Mybatis 创建可延迟加载对象所用到的代理工具。 | CGLIB | JAVASSIST | JAVASSIST (MyBatis 3.3 以上) |
vfsImpl | 指定 VFS 的实现 | 自定义 VFS 的实现的类全限定名,以逗号分隔。 | 未设置 |
useActualParamName | 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1) | true | false | true |
configurationFactory | 指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration() 的方法。(新增于 3.2.3) | 一个类型别名或完全限定类名。 | 未设置 |
六. 映射器(mappers)
MyBatis 的行为定义完成后,需要再定义 SQL 映射语句。所以要告诉 MyBatis 到哪里去找到这些语句。最好的方法时直接告诉 MyBatis 到哪里去找,可以使用相对于类路径的资源引用,或完全限定资源定位符(URL),或类名和包名等。
< ! -- 使用相对于类路径的资源引用 -- >
<mappers>
<mapper resource ="com/khr/dao/UserMapper.xml"/>
</mappers>
< ! -- 使用完全限定资源定位符(URL) -- >
<mappers>
<mapper url ="file:///com/khr/dao/UserMapper.xml"/>
</mappers>
< ! -- 使用映射器接口实现类的完全限定类名 -- >
<mappers>
<mapper class ="com.khr.dao.UserMapper.xml"/>
</mappers>
< ! -- 将包内的映射器接口实现全部注册为映射器 -- >
<mappers>
<package name ="com.khr.dao"/>
</mappers>
使用后面三种方式时接口和它的 Mapper 配置文件必须同名且在同一包下。
七. XML 映射器
MyBatis 的真正强大在于它的语句映射。它跟具有相同功能的 JDBC 代码比较,节省了将近95%的代码量。
SQL 映射文件的几个顶级元素:
- cache:该命名空间的缓存配置。
- cache-ref:引用其它命名空间的缓存配置。
- resultMap:描述如何从数据库结果集中加载对象,最复杂最强大的元素。
- sql:可被其它语句引用的可重用语句块。
- insert:映射插入语句。
- update:映射更新语句。
- delete:映射删除语句。
- select:映射查询语句。
每个元素细节可查看官方文档 — MyBatis XML映射器
八. 动态 SQL
在 JDBC 或其它类似框架中,根据不同条件拼接 SQL 语句非常繁琐,例如拼接时要确保不能忘记添加必要的空格,还要主要去掉列表最后一个列名的逗号等。利用动态 SQL,可以大大简化这一过程。并且 M有Batis 3替换了之前的大部分元素,精简了元素种类,使得要学习的元素总类比之前的一半还少。
- if
- choose(when,otherwise)
- trim(where,set)
- foreach
1. if
使用 if 可以用来作为 SQL 语句的条件。
例如根据作者名字和博客名来查询博客,如果作者名字为空,那么就只根据博客名,反之亦然。
<select id = "findActiveBlogLike"
resultType = "Blog">
SELECT * FROM BLOG WHERE state = 'ACTIVE'
<if test = "title != null">
AND title like #{title}
</if>
<if test = "author != null and author.name != null">
AND author_name like #{author_name}
</if>
</select>
2. where
where 元素可以自动识别并拼接 SQL 语句,如 and、or等,会自动选择添加或移除。
例如下列代码,当且仅当子元素返回任何内容的情况下才会插入 where 元素,并且如果子句的开头为 "AND" 或 "OR",where 元素会将它们移除。
假设只满足第二个条件,则拼接的语句为 select * from blog where and title like #{title},显然多了一个 and,此时 where 元素会将其自动移除,使得语句正确。
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
3. choose
有时我们不想使用所有条件,只想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,类似于 Java 中的 switch 语句。
例如下列代码,传入了 title 就按 title 查询,传入了 author 就按 author,如果都没传入则返回 featured = 1 的 BLOG。
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
4. set
set 元素通常用于动态更新语句。
例如下列代码,set 元素会动态地在行首插入 SET 关键字,并删掉额外的逗号(使用条件语句给列赋值时引入的)。
假设只满足第一个 if,则 set 元素会自动去掉逗号,SQL 语句就变成 update Author username = #{username}。
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>
5. foreach
foreach 元素常用于对集合进行遍历的场景。
例如下列代码,如果 ids 集合包含 [1,2,3],那么查询 BLOG 表中 id 为1,2,3的博客信息。
就等价于 SQL 语句 select * from blog where 1=1 and (id=1 or id=2 or id=3)。
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from blog
<where>
<!--
collection:指定输入对象中的集合属性
item:每次遍历生成的对象
open:开始遍历时的拼接字符串
close:结束时拼接的字符串
separator:遍历对象之间需要拼接的字符串
select * from blog where 1=1 and (id=1 or id=2 or id=3)
-->
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id=#{id}
</foreach>
</where>
</select>
九. 作用域和生命周期
SqlSessionFactoryBuilder:该类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(局部方法变量)。可以重用它来创建多个 SqlSessionFactory 实例,但最好不要一直保留,以保证所有的 XML 解析资源可以被释放给更重要的事情。
SqlSessionFactory:一旦被创建就应该在应用的运行期间一直存在,没有理由丢弃或重新创建。SqlSessionFactory 的最佳作用域是应用作用域,最简单的就是使用单例模式或静态单例模式。
SqlSession:每个线程都应该有自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此不能被共享,最佳作用域是请求或方法作用域。不能将其引用放在一个类的静态域,甚至是一个类的实例变量也不行。也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,应该把这个关闭操作放到 finally 块中。
其中,SqlSessionFactory 可以创建多个 SqlSession,每个SqlSession 可以有多个 Mapper,每个Mapper 就代表一个具体业务。
十. Java API
使用 MyBatis 的主要 Java 接口就是 SqlSession,可以通过这个接口来执行命令,获取映射器示例和管理事务。SqlSession 由 SqlSessionFactory 实例创建,SqlSesstionFactory 对象包含创建 SqlSession 实例的各种方法,而SqlSessionFactory 本身由 SqlSessionFactoryBuilder 创建。
SqlSessionFactoryBuilder 有五个 build() 方法,用来从不同资源中创建 SqlSessionFactory 实例:
SqlSessionFactory build(InputStream inputStream)
SqlSessionFactory build(InputStream inputStream, String environment)
SqlSessionFactory build(InputStream inputStream, Properties properties)
SqlSessionFactory build(InputStream inputStream, String env, Properties props)
SqlSessionFactory build(Configuration config)
SqlSessionFactory 有六给方法创建 SqlSession 实例:
SqlSession openSession()
SqlSession openSession(boolean autoCommit)
SqlSession openSession(Connection connection)
SqlSession openSession(TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType)
SqlSession openSession(ExecutorType execType, boolean autoCommit)
SqlSession openSession(ExecutorType execType, Connection connection)
Configuration getConfiguration();
SqlSession 在 MyBatis 中时是非常强大的一个类,包含了所有执行语句、提交或回滚事务以及获取映射器实例的方法。该类的方法超过 20 个,因此详细阅读请参考官方文档:SqlSession 类方法
十一. 日志
MyBatis 通过使用内置的日志工厂提供日志功能。日志工厂会将日志工作委托给下面的实现:
- SLF4J
- Apache Commons Logging
- Log4j 2
- Log4j
- JDK logging
MyBatis 内置日志工厂会基于运行时检测信息选择日志委托实现,它会按上面罗列的顺序使用第一个查找到的实现,如果没有找到,则禁用日志功能。
可以在 mybatis-config.xml 配置文件中添加 setting 来选择日志实现:
<configuration>
<settings>
...
<setting name="logImpl" value="LOG4J"/>
...
</settings>
</configuration>
可选的值有:SLF4J、LOG4J、LOG4J2、JDK_LOGGING、COMMONS_LOGGING等。
配置一下log4j日志。
首先导入依赖:
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
然后在 resources 目录下创建 log4j.properties 配置文件:
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/logFile.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
在 mybatis-config.xml 中配置 log4j 为日志的实现:
<configuration>
<settings>
<!--标准日志工厂实现-->
<!-- <setting name="logImpl" value="STDOUT_LOGGING"/>-->
<!--log4j-->
<setting name="logImpl" value="LOG4J"/>
</settings>
</configuration>
运行结果:
更多详细的日志配置信息请参考官方文档:MyBatis 日志