1. 简介
在掌握了Mybatis的基本使用方式之后,我们需要掌握Mybatis相关的:
- 参数性质
- 调优方式
- 日志
- 动态sql编写
同样的,官方文档仍然是最具备参考价值的。其可配置的内容如下,常用的属性已标出。
注意:所有的配置的标签必须按照以下顺序进行编写!
2. 参数配置
2.1 environments:
可以配置多个环境,但是对于一个模块来说,只可以使用一个环境。你可以在不同的场景使用不同的环境配置,比如测试时使用一种,实际部署时采用另一个。
每个环境都有自己的id属性
。通过在environments
标签的default属性
中选定使用的环境。子标签有:
-
transactionManager : 指定事务的类型,默认使用JDBC。
在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):- JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
- MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。
-
DataSource:指定连接数据库的方式,可以选择连接池和单次连接。有三种内建的数据源类型,也就是 type="[UNPOOLED|POOLED|JNDI]")。
<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>
<!--为测试配置环境-->
<environment id="test">
<transactionManager type="MANAGE">
<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>
2.2 properties:
为了方便我们和java项目中的配置文件进行 配合使用。**即将mybatis-config.xml中需要用户手写的参数,以配置文件的方式传递。**默认是db.properties.
通常我们在resource目录下创建db.properties文件:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8
username=root
password=123456
通过resource属性
引入该配置文件:
<configuration>
<!--引入配置文件,也可以在增加子标签,推荐使用外部文件-->
<properties resource="db.properties"></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>
</configuration>
2.3 类型别名——typeAliases
通过对全限定名进行别名化处理,简化在mapper.xml
配置文件中的对全限定名使用。通常有两种方式使用:
- 使用
typeAlias
子标签对单个全限定名进行别名化; - 扫描整个包下的类,在没有使用注解的情况下,会将包下的java Bean自动转化成对应的小写字母的别名,User---->user。
- 并且java类中有一些默认的别名,即我们经常使用的,他们不区分大小写。这也就是为什么我们之前使用map的时候,小写大写都可以。具体内容查看官方文档。要注意的是:如果你直接写int类型,其实这是一个别名,指向的是Integer类型,如果想使用基本数据类型的话,格式为
_int
。
<typeAliases>
<typeAlias type="com.yindarui.POJO.User" alias="user"></typeAlias>
<package name="com.yindarui.POJO"/>
</typeAliases>
使用包名的方式会使得无法修改想要的别名,我们可以在java源文件中使用注解来修改:
@Alias("user")
public class User{}
2.4 settings
这是mybatis最关键的参数设置,通过它我们可以开启和关闭许多功能。具体的内容可以去官方文档查看。
<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"/>
<!--处理数据库中的null-->
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
2.5 映射器(mappers)
MapperRepository:注册我们编写的Mapper.xml文件。注意,使用后两者就必须确保java源文件名和xml文件的文件名相同,并且还要在同一包下。
- 使用url来注册mapper:
<mappers>
<mapper resource="com/yindarui/Dao/user/UserMapper.xml"></mapper>
</mappers>
- 使用class文件来注册:
<mappers>
<!-- <mapper resource="com/yindarui/Dao/user/UserMapper.xml"></mapper>-->
<mapper class="com.yindarui.Dao.user.UserMapper"></mapper>
</mappers>
- 使用包扫描:
<mappers>
<!-- <mapper resource="com/yindarui/Dao/user/UserMapper.xml"></mapper>-->
<!-- <mapper class="com.yindarui.Dao.user.UserMapper"></mapper>-->
<package name="com.yindarui.Dao"/>
</mappers>
2.6 作用域和生命周期
涉及到管理并发的问题。生命周期主要是涉及到一些在使用mybatis的过程中的对象实例的创建方式。
1. SqlSessionFactoryBuilder
为了得到一个SqlSessionFactory,从配置文件加载出来。一经使用就可以丢弃。通常设置为局部变量。
2. SqlSessionFactory
用于创建许多SqlSession,因为用户会源源不断地创建SqlSession并进行数据库操作,所以,这个实例就必须一致存在,以持续提供服务。可以这样理解,工厂提供服务,在整个项目运行的过程中,工厂都不应该关闭。
&emsp所以,这个实例的最佳域为全局变量。我们可以使用单例模式来保证只有一份。
3. SqlSession
理解为和数据库建立了一个连接。这个连接会一直保持,直到程序调用close方法
。在之前的配置文件中我们也提到,我们可以设置dataSource 为 POOLED
。这样我们可以指定最大连接数和活动连接数。所以,整个过程相当于,sqlSession并不是在我们调用时才实例化,而是mybatis一开始就实例化了多个SqlSession,我们从他的池子中拿出使用。
所以该实例的作用域应该在类似于一个http请求的作用域中。即每次的http请求会导致一个SqlSession被拿出,并提供给用户使用。图片来自狂神说mybatis学习视频。
3. Mapper.xml文件
mybatis中的映射文件相对比较简单。常用的几个顶级元素包括:
3.1 resultMap
这个元素是sql标签的属性,用于将sql返回的结果集和java中操作的集合进行映射,是用户指定的一种映射规则。resultMap是 MyBatis 中最重要最强大的元素。通常适用于:
- sql数据库中字段和java bean中字段名不一致,导致返回结果无法匹配;
- sql返回结果过于复杂(多表联查),需要特殊处理。
字段名不一致
javabean:
// 注意,这里的password修改成了pwd,而数据库中的字段仍然是password
public class User {
private int id;
private String name;
private String pwd;
Test:
@Test
public void UserSelectTest() {
SqlSession sqlSession = MyBatisUtils.getSQLSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();
for (User item : userList) {
System.out.println(item.getName()+"的密码是:"+item.getPwd());
}
sqlSession.close();
}
结果:
其实原理就是mybatis对结果集合javabean的字段进行映射,如果有就赋值,如果没有找到对应则无法赋值。
3.2 使用resultMap
首先利用resultMap标签指明变量之间的映射,接着在sql语句标签中调用对应的 resultMap规则即可。你只需要指定名称不匹配的字段即可,剩下的mybatis会自动匹配。
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yindarui.Dao.user.UserMapper">
<resultMap id="getUserInfo" type="user">
<!-- <id property="id" column="id" />-->
<!-- <result property="username" column="username"/>-->
<result property="pwd" column="password"/>
</resultMap>
<select id="getUserList" resultMap="getUserInfo">
select * from user
</select>
</mapper>
结果:
4 Mybatis的日志
日志作为一个系统里记录各种操作和事件的文件,其作用非常巨大。我们通常使用的是在控制台输出的方式观察日志。
4.1 日志工厂
Mybatis 通过使用内置的日志工厂提供日志功能。内置日志工厂将会把日志工作委托给下面的实现之一:
- SLF4J
- Apache Commons Logging
- Log4j 2
- Log4j
- JDK logging
使用log4j
Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
导入log4j的包:
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
编写配置文件
建议直接在网上找现成的,然后按照自己的需求选择其中的一部分功能即可。
#将等级为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/kuang.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
在代码中使用log4j
public class UserTest {
// logger的生命周期是很长的,因为在整个操作的过程都需要他
private static Logger logger = Logger.getLogger(UserTest.class);
@Test
public void log4jTest() {
logger.info("hello world!!");
}
@Test
public void log4jTest2() {
SqlSession sqlSession = MyBatisUtils.getSQLSession();
logger.info("SqlSession被使用");
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();
for (User item : userList) {
logger.info(item.getName()+", 您的密码为:"+item.getPwd());
}
sqlSession.close();
logger.info("sqlSession关闭");
}
}
结果:
5. 注解开发
5.1 面向接口编程
为了实现代码的更好的解耦。关于接口的理解(来自于百度百科):
- 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离;
- 接口的本身反映了系统设计人员对系统的抽象理解。
- 接口应有两类:
- 第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);
- 第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface);
- 一个体有可能有多个抽象面。抽象体与抽象面是有区别的。
5.2 使用注解开发
使用步骤极其简单,但是对于复杂的sql的使用效果并不好,比如,上文提到的字段名和javabean属性名不一致时,就需要通过在sql中起别名的方式来解决,无法使用resultMap。注解开发底层使用了反射机制和动态代理设计模式。
步骤如下:
- 在接口的方法上使用注解编写sql:
@Select("select * from user") public List<User> getUserList();
- 在mybatis-config.xml文件中绑定接口:
<mappers> <mapper class="com.yindarui.Dao.user.UserMapper"/> </mappers>
- 测试,操作不变。
5.3 使用@Param注解
该注解可以理解为向mybatis注册了这个参数,这样在调用函数时就可以使用#{}
的方式来调用这个参数。#{}
可以防止sql注入。使用时请注意:
- 对于基本数据类型或者String类型,需要加上该注解;
- 其他的引用类型不需要加该注解也可以使用
#{}
的方式调用; - 如果只有一个基本参数类型的参数,可以不用该注解。
5.4 使用注解编写crud操作
在接口源文件中编写注解:
@Select("select * from user")
public List<User> getUserList();
//单个参数
@Select("select * from user where id = #{id}")
public User getUserById(@Param("id") int id);
// 多个参数
@Update("update user set password=#{password}, name=#{name} where id=#{id}")
public int updateUser(@Param("id") int id, @Param("name") String name,@Param("password") String password);
@Insert("insert into user(id, name, password) values(#{id},#{name},#{password})")
public int insertUser(User user);
测试:
@Test
public void test1() {
SqlSession sqlSession = MyBatisUtils.getSQLSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User userById = mapper.getUserById(1);
mapper.updateUser(1,"lisi", "123456");
mapper.insertUser(new User(3,"wamgwu", "233333"));
mapper.insertUser(new User(4,"zhangliu", "233333"));
mapper.insertUser(new User(5,"yindarui", "233333"));
List<User> userList = mapper.getUserList();
sqlSession.close();
}
5.5 Lombok
lombok是一个IEDA的插件,主要是简化开发人员对实体类的操作,避免了POJO中实体类的复杂且冗余度高的代码编写。但是**,不支持构造方法的重载,我们可以手动重载**。
使用步骤
- 下载lombok的插件
- 导入jar包
- 使用注解开发。
注解内容:
- @Getter and @Setter
- @FieldNameConstants
- @ToString
- @EqualsAndHashCode
- @AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor
- @Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog,@Flogger, @CustomLog
- @Data
- @Builder
- @SuperBuilder
- @Singular
- @Delegate
- @Value
- @Accessors
- @Wither
- @With
- @SneakyThrows
- @val
- @var
- experimental @var
- @UtilityClass
@Data
自动生成有参、无参构造,getter、setter方法,toString方法,hashCode方法,canEquals、equals方法。
你也可以使用单个注解(比如@Getter and @Setter )来生成指定的方法。
6. 复杂的数据环境——多表联查
多表联查主要涉及两种关系:
- 多对一:从多个记录出发查询,且多条记录中的某些字段是别的表中的内容,这时候后,我们必须从别的表中读取出某些字段信息并作为返回值。即从某些记录的某些属性出发,会查询出另一个记录。此时我们需要使用
association标签
。 - 一对多:从一条记录出发,这一条记录中的某个信息关联了别的表中的内容。此时,我们需要从别的表中读取多个记录并返回。即,依照某一记录中的某些属性,会查询出一个记录的集合。此时需要使用
collection标签
。
其实本质上,还是解决返回字段和属性名不一致的问题。只不过并不只是属性名不一致,而是存在一对多和多个对一个的情况。
这一部分会涉及到许多属性,感觉官网说的不太好理解,我总结一下,这些属性是理解resultMap的关键。 - column:字段。对应数据库中查询的内容;
- property:属性。对应javabean中的属性;
- id:唯一标识一个resultMap的标记;
- type:用于指定resultMap匹配的java类名;
- ofType:指定collection中的泛型类型;
- JavaType:指定association中的关联的类型名。
6.1 测试
基于学生和老师的关系,多个学生对应一个老师,一个老师管理多个学生。
测试表的关系如下:
mysql> desc student;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(10) | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | | NULL | |
| tid | int(10) | NO | MUL | NULL | |
+-------+-------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)
mysql> desc teacher;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(10) | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)
多对一测试
此时如果我们希望查询出学生的老师姓名就需要多表联查。从sql的角度出发,根据外键tid关联teacher表中的id字段,查询出name即可。此时返回的字段是学生编号、姓名和老师姓名。
使用mybatis查询时,我们返回的字段和实体类是不对应的。所以我们还要使用resultMap
属性来进行映射。但是我们是基于一个表中的一个字段来联查另一个表,此时需要使用association标签
。
其实这部分的难点就在于,学生的Teacher是一个对象。在理解sql语句含义的基础上,其实就我们利用resultMap来指定对应的java类,并用字段值填充实例对象的属性值。
实体类:
Student.java
public class Student {
private int id;
private String name;
// 返回的是teacher对象
private Teacher teacher;
}
Teacher.java
public class Teacher {
private int id;
private String name;
}
mapper.xml文件:
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yindarui.Dao.student.StudentMapper">
<!--多表联查的sql语句-->
<select id="getStudentTeacherInfo" resultType="student" resultMap="StudentResultMap">
select s.id as sid,
s.name as sname,
t.id as ttid,
t.name as tname
from student s
left join teacher t
on s.tid = t.id;
</select>
<!--编写结果集映射规则-->
<resultMap id="StudentResultMap" type="student">
<id column="sid" property="id"/>
<result column="sname" property="name"/>
<association property="teacher" javaType="teacher">
<id column="ttid" property="id"/>
<result column="tname" property="name"/>
</association>
</resultMap>
</mapper>
查询结果:
一对多测试
其实和上面的思路一致,只不过是需要获取一个集合。我们使用collection
标签来做。核心思想就是我指定并匹配另一个java类型,并用字段值填充实例对象的属性值。
Student.java
public class Student {
private int id;
private String name;
private Teacher teacher;
}
Teacher.java
public class Teacher {
private int id;
private String name;
private List<Student> students;
}
TeacherMapper.xml
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yindarui.Dao.teacher.TeacherMapper">
<select id="getTeacherInfoById" resultMap="studentResultMap">
select t.id as tid,
t.name as tname,
s.id as sid,
s.name as sname
from teacher t
left join student s
on t.id = s.tid
</select>
<resultMap id="studentResultMap" type="teacher">
<id column="tid" property="id"/>
<result column="tname" property="name"/>
<collection property="students" ofType="student">
<id column="sid" property="id"/>
<result column="sname" property="name"/>
</collection>
</resultMap>
</mapper>
测试方法:
@Test
public void TeachTest01() {
SqlSession sqlSession = com.yindarui.Utils.MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacherInfoById = mapper.getTeacherInfoById(1);
sqlSession.close();
}
结果:
7. SQL片段
有些时候我们可以把一些sql的片段提取出来作为公用,提高代码的复用性。操作步骤:
- 使用sql标签提取公共的部分
- 在需要使用的位置使用include属性引用。
8. 动态SQL
根据不同的条件自动拼接不同的sql语句。动态SQL运行我们在sql的角度来执行一些逻辑相关的操作。
MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
8.1 if语句
即为我们提供条件判断的操作。测试内容为拼接name字段作为查询条件。
8.2 测试
接口:
public List<Teacher> getTeacherByCondi(Map<String, Object> map);
mapper.xml文件:
<select id="getTeacherByCondi" parameterType="map" resultType="Teacher" >
select * from teacher
where 1 = 1
<if test="name != null">
and name = #{name}
</if>
</select>
测试方法1:存在sql拼接
@Test
public void TeacherTestForIf() {
SqlSession sqlSession = com.yindarui.Utils.MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Map<String, Object> map = new HashMap();
map.put("name", "yindarui");
List<Teacher> teacherByCondi = mapper.getTeacherByCondi(map);
sqlSession.close();
}
结果:
测试方法2:name没有指定
@Test
public void TeacherTestForIf() {
SqlSession sqlSession = com.yindarui.Utils.MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Map<String, Object> map = new HashMap();
// map.put("name", "yindarui");
List<Teacher> teacherByCondi = mapper.getTeacherByCondi(map);
sqlSession.close();
}
8.3 choose和when——switch case
和if类似,但是只能选择一种条件返回。而if标签可以满足多个条件。测试内容为从name和tid字段选择一个作为查询条件。
接口:
public List<Student> getStudentList(Map<String, Object> map);
mapper.xml:
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yindarui.Dao.student.StudentMapper">
<select id="getStudentList" resultType="student">
select * from student
where 1 = 1
<choose>
<when test="name != null">
and name = #{name}
</when>
<when test="tid != null">
and tid = #{tid}
</when>
</choose>
</select>
</mapper>
测试方法:
@Test
public void stuTestForChoose() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("name", "zhangsan");
map.put("tid", 2);
List<Student> studentList = mapper.getStudentList(map);
sqlSession.close();
}
结果:
8.4 where
使用where标签可以将我们在where前添加的1=1
条件给去掉。如果直接不加的话,会导致语句的拼接错误。
<select id="getStudentList" resultType="student">
select * from student
<where>
<choose>
<when test="name != null">
and name = #{name}
</when>
<when test="tid != null">
and tid = #{tid}
</when>
</choose>
</where>
</select>
8.5 trim
如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。
8.6 set
用于动态更新语句的类似解决方案叫做 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>
8.7 foreach
这部分建议阅读官方文档,已经写的很详细了。不再做说明。
9 缓存
9.1 一级缓存
将查询的数据缓存在内存中,不删除,下次请求相同的信息就直接返回给用户。可以缓解高并发的带来的时延问题。对于经常查询且不经常修改的数据,可以存入缓存。
在mybatis中,缓存分成两层,一级缓存会默认开启,这个缓存被称为本地缓存。他只会存在在一次会话。在用户获取一个SqlSession的过程中发挥作用。即在SqlSession的建立到close方法的调用期间,这个缓存都是发挥作用的。
9.2 二级缓存
一级缓存是基于SqlSession的。二级缓存是基于namespace的,即在一个mapper中生效(一个mapper可以有很多方法)。如果当前会话被关闭(SqlSession),此时一级缓存就没了,但是他的结果会被存入二级缓存。
二级缓存的开启需:
*在核心配置文件中开启全局缓存:
<setting name="cacheEnabled" value="true"/>
- 在mapper.xml中添加:
<cache />
其缓存的规则是:
- 映射语句文件中的所有 select 语句的结果将会被缓存。别的语句的结果没有缓存的意义。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
同时,缓存还具备一些属性可以设置:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
注意:缓存的对象是只读的,这意味着不会对缓存中的数据进行数据同步操作。所以,一旦磁盘中的数据发生了修改,缓存中对应的内容就必须丢弃,否则会发生脏读的现象。当缓存满了就会清除缓存。
可用的清除策略有:
- LRU – 最近最少使用:移除最长时间不被使用的对象。
- FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
- WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
9.3 缓存机制
9.4 自定义缓存
mybatis提供了用户自定义缓存的功能。只需要实现org.apache.ibatis.cache.Cache 接口,就可以自己定义一更加高级的缓存机制。在mapper.xml中添加:
<cache type="com.domain.something.MyCustomCache"/>