mybatis
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
一、配置mybatis
-
使用maven新建项目,需要引入mybatis,mysql和junit(用于测试)三个依赖
-
创建核心配置文件
在resources文件下新建一个xml文件并写入以下内容,其中property标签的value属性需要修改为之前使用jdbc连接数据库时使用的数据
注意点 :在xml中写url时用到的&符号需要转义,要写成&;
<?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>
<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=false&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
</configuration>
-
编写数据库表中对应的实体类以及对应操作的Dao层接口(Dao层现在叫Mapper 另外不需要编写接口实现类)
-
编写静态mybatisUtils类用来获取SqlSession对象。
package com.tuan.utils; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.IOException; import java.io.InputStream; public class MybatisUtils { private static SqlSessionFactory sqlSessionFactory; static { //使用mybatis第一步获取SqlSessionFactory String resource = "org/mybatis/example/mybatis-config.xml"; InputStream inputStream = null; try { inputStream = Resources.getResourceAsStream(resource); } catch (IOException e) { e.printStackTrace(); }finally { if(inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } public static SqlSession getSession() { return sqlSessionFactory.openSession(); } }
-
在mybatis中接口实现类使用一个xml文件代替。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.tuan.map.UserMapper"> <select id="getUserList" resultType="com.tuan.pojo.User"> select * from mybatis.user </select> </mapper>
-
执行sql,这里使用junit测试执行
package com.tuan.dao; import com.tuan.Dao.UserMapper; import com.tuan.pojo.User; import com.tuan.utils.MybatisUtils; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import java.util.List; public class UserDaoTest { @Test public void test() { //获得SqlSession对象 SqlSession sqlSession = MybatisUtils.getSession(); //方式一:使用getMapper UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<User> user = userMapper.getUserList(); for(User u : user) { System.out.println(u.toString()); } sqlSession.close(); } }
-
在第二步中建立的核心配置文件中写入mapper标签创建对应的接口实现xml的映射
-
执行获得结果
-
可能遇到的问题:
1.xml资源被拦截不会生成在target文件中:
解决方案:在项目pom.xml中加入Builder标签
<build> <resources> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> </build>
2.【[ERROR] Some problems were encountered while processing the POMs: [ERROR] ‘packaging’ with value ‘jar’ is invalid. Aggregator projects require ‘pom’ as packaging. @ line 3, column 110】
解决方案:报这个问题,主要是出现在父子工程多模块的时候,需要在父模块中指定打包方式为pom,最后一级加不加都行。
二、mybatis实现增删改查
首先需要确保接口实现类的xml文件中的命名空间namespace要和mapper接口对应
1.查询语句select
-
id:对应的namespace接口中的方法
-
resultType:sql语句中的返回值类型
-
parameterType:参数的类型
<select id="getUserList" resultType="com.tuan.pojo.User" > select * from mybatis.user </select>
<select id="getUserById" resultType="com.tuan.pojo.User" parameterType="int"> select * from mybatis.user where id = #{id} </select>
2.增删改语句
- 插入数据insert
<insert id="addUser" parameterType="com.tuan.pojo.User">
insert into mybatis.user (id,name,pwd) values(#{id},#{name},#{pwd});
</insert>
-
修改用户update
<update id="updateUser" parameterType="com.tuan.pojo.User"> update mybatis.user set name=#{name},pwd=#{pwd} where id = #{id}; </update>
-
删除用户delete(与上面类似不再写实例)
注意点:所有的增删改语句在java代码中都需要使用SqlSession的commit方法提交事务
sqlSession.commit();
SqlSession sqlSession = MybatisUtils.getSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User(6,"dog刘博","147852");
userMapper.addUser(user);
sqlSession.commit();
sqlSession.close();
三、设置参数类型为Map
当我们数据库中的字段过多,使用实体类要传递很多参数时可以使用Map作为参数,将需要传递的值放入Map中并在xml中通过key值取出对应的value。
接口中定义:
User getUserByID(Map<String,Object> map);
xml中配置:
<select id="getUserByID" resultType="com.tuan.pojo.User" parameterType="map">
select * from user where id = #{id}
</select>
测试代码:
public void getUserByID() {
SqlSession sqlSession = MybatisUtils.getSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Map<String,Object> map = new HashMap<String,Object>();
map.put("id",2);
User user = userMapper.getUserByID(map);
System.out.println(user);
sqlSession.close();
}
四、mybatis配置解析
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
在编写核心配置文件时标签的位置也要按照以下先后顺序编写,否则会报错
- configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- environment(环境变量)
- databaseIdProvider(数据库厂商标识)
- mapper(映射器)
1.环境配置environments
<!-- default默认使用的环境 -->
<environments default="development">
<!-- id环境名 -->
<environment id="development">
<!--transactionManager事务管理器,mybatis默认有两种事务管理器:JDBC和MANAGED,一般使用JDBC,因为JDBC使用了 事务提交和回滚 -->
<transactionManager type="JDBC"/>
<!-- dataSource数据源 一般都使用POOLED-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
2.属性properties
我们可以通过properties属性实现引用配置文件
方法一
① 先在resources目录下编写一个配置文件db.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8
username=root
password=123456
② 在核心配置文件中写入properties标签映射db.properties
db.properties在resource目录下不用再写路径
③ 在environment标签中的property中使用el表达式直接获取到db.properties中的值
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<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>
方法二
另外也可以在核心配置文件的properties标签中使用property标签直接写入键值对
<properties>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</properties>
如果在这里同时使用方法一和方法二写入了相同的key,那会优先使用外部配置文件里的value,即最后获取到的是db.properties中的值
3.配置类型别名typeAliases
类型别名可为 Java 类型设置一个缩写名字,用于降低冗余的全限定类名书写。
①使用typeAlias为指定的一个类起别名
<typeAliases>
<typeAlias type="com.tuan.pojo.User" alias="User"></typeAlias>
</typeAliases>
②使用package扫描一个包,包下的所有类都可以直接使用类名作为参数/返回类型而不用全限定。
<typeAliases>
<package name="com.tuan.pojo"/>
</typeAliases>
③使用注解在类中直接定义别名
@Alias("User")
public class User {...}
-
实体类较少时使用第一种,实体类较多时使用第二种
-
第一种可以自定义别名,第二种只能叫做类名
-
存在注解的情况下只能使用注解作为别名(注解优先级最高)
-
别名不区分大小写
4.映射器Mapper
映射器负责注册绑定我们的Mapper接口
①使用resource完全限定名绑定接口对应实现类的xml
<mappers>
<mapper resource="com/tuan/Dao/UserMapper.xml"/>
</mappers>
②使用class完全限定名绑定对应的接口
<mappers>
<mapper class="com.tuan.Dao.UserMapper"></mapper>
</mappers>
③使用package扫描一整个包
<mappers>
<package name="com.tuan.dao"/>
</mappers>
使用方法二和方法三必须保证:
接口必须和对应mapper的xml文件同名且在一个包下,即必须保证:
五、结果集映射ResultMap
ResultMap主要负责解决实体类属性名和数据库中的字段名不一致的情况
解决方法一:在sql语句中使用as别名解决
<select id="getUserById" resultType="com.tuan.pojo.User" parameterType="int">
select id,name,pwd as password from mybatis.user where id = #{id}
</select>
解决办法二:使用ResultMap结果集映射
在要使用Resultmap映射的地方就可以用resultMap属性替代resultType属性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UuispPA0-1647414028899)(typora.assets/image-20211105141934432.png)]
<resultMap id="user" type="com.tuan.pojo.User">
<result column="id" property="id"></result>
<result column="name" property="name"></result>
<result column="pwd" property="password"></result>
</resultMap>
<select id="getUserById" resultMap="user" parameterType="int">
select * from mybatis.user where id = #{id}
</select>
六、日志
日志是mybatis中的排错功能
使用日志需要在核心配置文件中设置settings标签的logImpl属性,它具有以下值:
SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
我们只需要使用其中的部分:
-
NO_LOGGING
不使用日志
-
STDOUT_LOGGING
官方自带日志,配置后可以直接使用无需导包,所有的信息都会在控制台输出
-
LOG4J (重点)
1.先导入log4j的包
<!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
2.在核心配置文件中设置setting的logImpl属性为LOG4J
<settings> <setting name="logImpl" value="LOG4J"/> </settings>
3.资源目录下创建log4j.properties
log4j.rootLogger = debug,stdout,D,E log4j.appender.stdout = org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target = System.out log4j.appender.stdout.layout = org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n log4j.appender.D = org.apache.log4j.DailyRollingFileAppender log4j.appender.D.File = D://logs/log.log log4j.appender.D.Append = true log4j.appender.D.Threshold = DEBUG log4j.appender.D.layout = org.apache.log4j.PatternLayout log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n log4j.appender.E = org.apache.log4j.DailyRollingFileAppender log4j.appender.E.File =D://logs/error.log log4j.appender.E.Append = true log4j.appender.E.Threshold = ERROR log4j.appender.E.layout = org.apache.log4j.PatternLayout log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
最后运行会生成log文件里面写有指定格式的日志
七、分页
1.使用limit分页
-
接口定义
List<User> selectUserLimit(int start,int num);
-
xml对应实现
<select id="selectUserLimit" resultMap="UserMap" parameterType="int">
select * from user limit #{param1},#{param2}
</select>
- 测试时传入参数即可
2.使用RowBounds类实现分页
3.使用分页工具类(如Mybatis PageHelper)
八、使用注解开发
使用注解可以避免配置接口对应的xml文件,但对于复杂的sql使用注解会难以实现
//获取全部用户信息
@Select("select * from user")
List<User> getUserList();
//通过id查询用户
@Select("select * from user where id=#{id}")
User getUserById(@Param("id") int id);
关于@param()注解:
一般情况下建议基本类型使用该注解,引用类型不使用该注解。我们在sql中使用#{}引用的就是此注解中的属性名
九、联表查询
1. 多对一的处理
场景:每个学生对应一个老师,学生和老师表通过老师的id字段连接
方式一、按照查询嵌套处理
对结果集进行映射,映射的是另一个select语句
<select id="selectStudent" resultMap="studentTeacher">
select * from student;
</select>
<resultMap id="studentTeacher" type="Student">
<result property="id" column="id"></result>
<result property="name" column="name"></result>
<association property="teacher" column="tid" javaType="Teacher" select="selectTeacher"/>
</resultMap>
<select id="selectTeacher" resultType="Teacher">
select * from teacher where id=#{id};
</select>
方式二、按照结果嵌套处理
对结果进行映射
<select id="getStudent" resultMap="StudentTeacher">
select s.id sid,s.name sname,t.name tname
from student s,teacher t
where s.tid=t.id
</select>
<resultMap id="StudentTeacher" type="Student">
<result property="id" column="sid"></result>
<result property="name" column="sname"></result>
<association property="teacher" javaType="Teacher" >
<result property="name" column="tname"></result>
</association>
</resultMap>
2.一对多的处理
方式一、按照结果嵌套处理
<select id="selectTeacher" resultMap="TeacherStudent">
select t.id tid,t.name tname,s.id sid,s.name sname
from teacher t,student s
where t.id=#{tid} and t.id=s.tid;
</select>
<resultMap id="TeacherStudent" type="Teacher">
<result property="id" column="tid"></result>
<result property="name" column="tname"></result>
<collection property="students" ofType="Student">
<result property="name" column="sname"></result>
<result property="id" column="sid"></result>
<result property="tid" column="tid"></result>
</collection>t
</resultMap>
十、动态 SQL
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
if
使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。比如:
<select id="findActiveBlogWithTitleLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>
这条语句提供了可选的查找文本功能。如果不传入 “title”,那么所有处于 “ACTIVE” 状态的 BLOG 都会返回;如果传入了 “title” 参数,那么就会对 “title” 一列进行模糊查找并返回对应的 BLOG 结果(细心的读者可能会发现,“title” 的参数值需要包含查找掩码或通配符字符)。
如果希望通过 “title” 和 “author” 两个参数进行可选搜索该怎么办呢?首先,我想先将语句名称修改成更名副其实的名称;接下来,只需要加入另一个条件即可。
<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>
choose、when、otherwise
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
还是上面的例子,但是策略变为:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回标记为 featured 的 BLOG(这可能是管理员认为,与其返回大量的无意义随机 Blog,还不如返回一些由管理员精选的 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>
trim、where、set
前面几个例子已经方便地解决了一个臭名昭著的动态 SQL 问题。现在回到之前的 “if” 示例,这次我们将 “state = ‘ACTIVE’” 设置成动态条件,看看会发生什么。
<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>
</select>
如果没有匹配的条件会怎么样?最终这条 SQL 会变成这样:
SELECT * FROM BLOG
WHERE
这会导致查询失败。如果匹配的只是第二个条件又会怎样?这条 SQL 会是这样:
SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’
这个查询也会失败。这个问题不能简单地用条件元素来解决。这个问题是如此的难以解决,以至于解决过的人不会再想碰到这种问题。
MyBatis 有一个简单且适合大多数场景的解决办法。而在其他场景中,可以对其进行自定义以符合需求。而这,只需要一处简单的改动:
<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>
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。
用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:
<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>
这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
来看看与 set 元素等价的自定义 trim 元素吧:
<trim prefix="SET" suffixOverrides=",">
...
</trim>
注意,我们覆盖了后缀值设置,并且自定义了前缀值。
foreach
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!
提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
至此,我们已经完成了与 XML 配置及映射文件相关的讨论。下一章将详细探讨 Java API,以便你能充分利用已经创建的映射配置。
script
要在带注解的映射器接口类中使用动态 SQL,可以使用 script 元素。比如:
@Update({"<script>",
"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}",
"</script>"})
void updateAuthorValues(Author author);
bind
bind
元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文。比如:
<select id="selectBlogsLike" resultType="Blog">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM BLOG
WHERE title LIKE #{pattern}
</select>
多数据库支持
如果配置了 databaseIdProvider,你就可以在动态代码中使用名为 “_databaseId” 的变量来为不同的数据库构建特定的语句。比如下面的例子:
<insert id="insert">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
<if test="_databaseId == 'oracle'">
select seq_users.nextval from dual
</if>
<if test="_databaseId == 'db2'">
select nextval for seq_users from sysibm.sysdummy1"
</if>
</selectKey>
insert into users values (#{id}, #{name})
</insert>
动态 SQL 中的插入脚本语言
MyBatis 从 3.2 版本开始支持插入脚本语言,这允许你插入一种语言驱动,并基于这种语言来编写动态 SQL 查询语句。
可以通过实现以下接口来插入一种语言:
public interface LanguageDriver {
ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}
实现自定义语言驱动后,你就可以在 mybatis-config.xml 文件中将它设置为默认语言:
<typeAliases>
<typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/>
</typeAliases>
<settings>
<setting name="defaultScriptingLanguage" value="myLanguage"/>
</settings>
或者,你也可以使用 lang
属性为特定的语句指定语言:
<select id="selectBlog" lang="myLanguage">
SELECT * FROM BLOG
</select>
或者,在你的 mapper 接口上添加 @Lang
注解:
public interface Mapper {
@Lang(MyLanguageDriver.class)
@Select("SELECT * FROM BLOG")
List<Blog> selectBlog();
}
十一、缓存
MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。
默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:
<cache/>
并在核心配置文件中的settings标签中加上:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
开启全局缓存
基本上就是这样。这个简单语句的效果如下:
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
提示 缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。
这些属性可以通过 cache 元素的属性来修改。比如:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
可用的清除策略有:
LRU
– 最近最少使用:移除最长时间不被使用的对象。FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
默认的清除策略是 LRU。
flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
提示 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。
使用自定义缓存
除了上述自定义缓存的方式,你也可以通过实现你自己的缓存,或为其他第三方缓存方案创建适配器,来完全覆盖缓存行为。
<cache type="com.domain.something.MyCustomCache"/>
这个示例展示了如何使用一个自定义的缓存实现。type 属性指定的类必须实现 org.apache.ibatis.cache.Cache 接口,且提供一个接受 String 参数作为 id 的构造器。 这个接口是 MyBatis 框架中许多复杂的接口之一,但是行为却非常简单。
public interface Cache {
String getId();
int getSize();
void putObject(Object key, Object value);
Object getObject(Object key);
boolean hasKey(Object key);
Object removeObject(Object key);
void clear();
}
为了对你的缓存进行配置,只需要简单地在你的缓存实现中添加公有的 JavaBean 属性,然后通过 cache 元素传递属性值,例如,下面的例子将在你的缓存实现上调用一个名为 setCacheFile(String file)
的方法:
<cache type="com.domain.something.MyCustomCache">
<property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>
你可以使用所有简单类型作为 JavaBean 属性的类型,MyBatis 会进行转换。 你也可以使用占位符(如 ${cache.file}
),以便替换成在配置文件属性中定义的值。
从版本 3.4.2 开始,MyBatis 已经支持在所有属性设置完毕之后,调用一个初始化方法。 如果想要使用这个特性,请在你的自定义缓存类里实现 org.apache.ibatis.builder.InitializingObject
接口。
public interface InitializingObject {
void initialize() throws Exception;
}
提示 上一节中对缓存的配置(如清除策略、可读或可读写等),不能应用于自定义缓存。
请注意,缓存的配置和缓存实例会被绑定到 SQL 映射文件的命名空间中。 因此,同一命名空间中的所有语句和缓存将通过命名空间绑定在一起。 每条语句可以自定义与缓存交互的方式,或将它们完全排除于缓存之外,这可以通过在每条语句上使用两个简单属性来达成。 默认情况下,语句会这样来配置:
<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>
鉴于这是默认行为,显然你永远不应该以这样的方式显式配置一条语句。但如果你想改变默认的行为,只需要设置 flushCache 和 useCache 属性。比如,某些情况下你可能希望特定 select 语句的结果排除于缓存之外,或希望一条 select 语句清空缓存。类似地,你可能希望某些 update 语句执行时不要刷新缓存。
cache-ref
回想一下上一节的内容,对某一命名空间的语句,只会使用该命名空间的缓存进行缓存或刷新。 但你可能会想要在多个命名空间中共享相同的缓存配置和实例。要实现这种需求,你可以使用 cache-ref 元素来引用另一个缓存。
<cache-ref namespace="com.someone.application.data.SomeMapper"/>