MyBatis与MyBatis-Plus
MyBatis
简介
MyBatis 是一款优秀的 持久层框架。它支持 定制化 SQL、存储过程以及高级映射。
MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或 注解 来配置和映射 原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为 数据库中的记录。
持久化
持久化就是将程序的数据在 持久状态 和 瞬时状态 转化的过程。
由于内存有断电之后数据就会丢失的特点,有一些对象不能丢失,需要将其持久化。
持久层
- 完成持久化工作的代码块就是持久层。
传统的 JDBC 代码太复杂,MyBatis 简化了将数据存入数据库这一过程。MyBatis 将 sql 和代码分离,提高了可维护性;提供了映射标签,支持对象与数据库的字段关系的映射(ORM,Object Relation Mapping);提供 xml 标签,支持编写动态 sql。
写一个 MyBatis 程序
搭建数据库
CREATE DATABASE IF NOT EXISTS mybatis;
USE mybatis;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
id INT NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
passwd VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE = InnoDB;
INSERT INTO `user`(id, `name`, passwd)
VALUES (1, '黄铁', '123456'),
(2, '张三', '123456'),
(3, '李四', '123456');
SELECT * FROM `user`;
新建项目
导入相关依赖
<dependencies>
<!-- 连接mysql的驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.3</version>
</dependency>
</dependencies>
编写核心配置文件
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
XML 配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器(TransactionManager)。
核心配置文件的写法直接按照MyBatis官网MyBatis 3 入门的配置:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- mybatis 核心配置文件 -->
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 每个mapper.xml都需要在该配置文件中注册-->
<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>
编写 mybatis 工具类
/**
* 从sqlSessionFactory获取sqlSession
* sqlSession 完全包含了面向数据库执行SQL命令所需的所有方法
*/
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
编写代码
实体类
public class User {
private int id;
private String name;
private String passwd;
public User () {
}
public User(int id, String name, String passwd) {
this.id = id;
this.name = name;
this.passwd = passwd;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", passwd='" + passwd + '\'' +
'}';
}
}
DAO 接口
public interface UserDao {
List<User> getUserList();
}
接口实现类由原来的 UserDaoImpl 转变为一个 Mapper 配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace 会绑定一个对应的DAO/Mapper接口 -->
<mapper namespace="com.hunter.dao.UserDao">
<!-- 返回值是list时,resultType填写 List中存放的对象类型 -->
<select id="getUserList" resultType="com.hunter.pojo.User">
select * from user
</select>
</mapper>
测试
public class UserDaoTest {
@Test
public void test() {
// 获得sqlSession对象,try语句在结束时会自动关闭资源,这称为try-with-resources语句
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
// 执行sql
UserDao userDao = sqlSession.getMapper(UserDao.class);
List<User> userList = userDao.getUserList();
for (User user : userList) {
System.out.println(user);
}
}
}
}
增加 CRUD
将 UserDao 变更为 UserMapper。
id
对应
namespace中的方法名。parameterType
参数类型。
resultType
sql 语句执行的返回类型。返回值类型是
List时,resultType 填写List中存放的对象类型。
<select id="getUserById" parameterType="int" resultType="com.hunter.pojo.User">
select * from user where id = #{id}
</select>
<!-- 对象中的属性,可以直接取出 -->
<insert id="addUser" parameterType="com.hunter.pojo.User">
insert into user (id, name, passwd) values (#{id}, #{name}, #{passwd})
</insert>
<update id="updateUser" parameterType="com.hunter.pojo.User">
update user set name = #{name}, passwd = #{passwd} where id = #{id}
</update>
<delete id="deleteUser" parameterType="int">
delete from user where id = #{id}
</delete>
测试
注意:增删改需要提交事务
@Test
public void should_success_when_delete_user() {
// 获得sqlSession对象,try语句在结束时会自动关闭资源,这称为try-with-resources语句
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
// 执行sql
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
int result = userMapper.deleteUser(4);
if (result > 0) {
System.out.println("删除成功");
}
// 提交事务
sqlSession.commit();
}
}
用 Map 传参
如果实体类或数据库中的表,字段或者参数过多,可以考虑使用 Map:parameterType="map",传参 geng。
/**
* 修改用户密码
*
* @param map 通过map传参,无需构造一个完整的user
* @return 影响行数
*/
int updateUserPasswd(Map<String, Object> map);
<!-- parameterType 为 map 时,属性名无需和数据库表的字段一一对应 -->
<insert id="updateUserPasswd" parameterType="map">
INSERT INTO user (id, passwd) VALUES (#{userId}, #{password});
</insert>
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("userId", 1);
map.put("password", "666666");
userMapper.updateUserPasswd(map);
}
配置解析
核心配置文件
MyBatis官方示例使用的文件名:mybatis-config.xml,可以沿用。
各标签的顺序必须如下。
< configuration > (配置)
- < properties > (属性)
- < settings > (设置)
- < typeAliases > (类型别名)
- < typeHandlers > (类型处理器)
- < objectFactory > (对象工厂)
- < plugins > (插件)
- mybatis-generator-core
- mybatis-plus
- 通用 mapper
- < environments > (环境配置)
- < environment > (环境变量)
- < transactionManager > (事务管理器)
- < dataSource > (数据源)
- < environment > (环境变量)
- < mappers > (映射器)
环境配置 environments
<transactionManager>MyBatis 中有两种类型的事务管理器(type = “JDBC|MANAGED”)
如果使用 Spring + MyBatis,则 没有必要配置事务管理器。
<dataSource>UNPOOLEDPOOLED池化数据源JNDI
属性 properties
可以通过 properties 属性来实现 引用配置文件。
这些属性都是 可外部配置且动态替换的,既可以在典型的 Java 属性文件中(如 db.properties),也可通过 properties 元素的子元素(property)来传递。
编写一个配置文件 db.properties
<!-- 引入外部配置文件,
如果外部配置文件有和子元素property相同的字段,外部配置文件更优先 -->
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="123456"/>
</properties>
类型别名 typeAliases
类型别名是为 Java 类型设置一个短的名字,用于 减少类完全限定名的冗余。
<!-- 给实体类起别名 --> <typeAliases> <typeAlias type="com.hunter.pojo.User" alias="User"/> </typeAliases>也可以 指定一个包名,MyBatis 会在包名下搜索需要的 Java Bean,比如扫描实体类的包,它的别名就是 首字母小写的类名。
<!-- 指定包名 --> <typeAliases> <package name="com.hunter.pojo"/> </typeAliases>这种方式无法自定义别名,如果要自定义,需要在实体类上增加注解:
import org.apache.ibatis.type.Alias; @Alias("Hello") public class User {...}
设置 settings
可以指定 MyBatis 所用日志的具体实现,未指定时,将自动查找。
<settings> <setting name="logImpl" value="LOG4J"/> </settings>
映射器 mappers
方式一:注册 mapper.xml 文件【推荐使用】
<!-- 每个mapper.xml都需要在该配置文件中注册--> <mappers> <mapper resource="UserMapper.xml"/> </mappers>方式二:使用 class 文件绑定注册
<mappers> <mapper class="com.hunter.dao.UserMapper"/> </mappers>- 接口和 Mapper 配置文件 必须同名
- 接口和 Mapper 配置文件 必须在同一个包下
方式三:使用扫描包进行注册绑定
<mappers> <package name="com.hunter.dao"/> </mappers>- 接口和 Mapper 配置文件 必须同名
- 接口和 Mapper 配置文件 必须在同一个包下
生命周期和作用域
生命周期和作用域至关重要,错误的使用会导致严重的 并发问题。
SqlSessionFactoryBuilder
一旦创建了 SqlSessionFactory,就不再需要它,因此作为局部变量即可。
SqlSessionFactory
可以理解为数据库连接池,一旦被创建,就应该在应用运行期间一直存在,没有任何理由丢弃或重新创建另一个实例,多次重建 SqlSessionFactory 被视为一种代码 坏习惯。因此最简单的就是 使用单例模式 或者 静态单例模式。
SqlSession
连接到连接池的 一个请求。用完之后需要马上关闭,避免资源的占用。
结果集映射 resultMap
可以解决 对象的属性名和数据库中字段名不一致 的问题。
<mapper namespace="com.hunter.dao.UserMapper">
<!-- 结果集映射 -->
<resultMap id="UserMap" type="User">
<!-- column 数据库中的字段,property 实体类中的属性 -->
<result column="id" property="id"/>
<result column="name" property="name"/>
<result column="passwd" property="pwd"/>
</resultMap>
<!-- 返回值类型是List时,resultType填写List中存放的对象类型 -->
<select id="getUserList" resultMap="UserMap">
SELECT * FROM user;
</select>
</mapper>
resultMap 是 Mybatis 中最强大的元素。其设计思想是,简单的语句不需要配置显式的结果映射,复杂的语句只需要描述它们的关系。但是 现实不仅仅只有这么简单的关系。
日志工厂
初学者定位异常的手段,通常是 控制台输出 和 debug,而日志定位是现实项目的成熟方式。
STDOUT_LOGGING 标准日志输出
<settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>
LOG4J
Log4j 是 Apache 的开源项目,可以控制日志信息输送的目的地是控制台、文件、GUI 组件。
- 可以控制每条日志的输出格式。
- 通过定义每条日志信息的级别,能够更加细致地控制日志的生成过程。
- 通过 配置文件 来灵活地配置,不需要修改应用的代码。
导包
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.20.0</version> </dependency>创建
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/mybatis_learning.log # 每个log文件的大小,超过之后会生成一个新的日志,KB、MB、GB log4j.appender.file.MaxFileSize=1MB log4j.appender.file.Threshold=DEBUG # 输出的文件格式 log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=【%p】【%d{yyyy-MM-dd hh:mm:ss}】【%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 为日志的实现
<settings> <setting name="logImpl" value="LOG4J"/> </settings>
简单使用
在要使用 log4j 的类中,导入包
org.apache.log4j.Logger日志对象,参数为当前类的 class
public static Logger logger = Logger.getLogger(UserMapperTest.class);日志级别
logger.info("进入了test log4j"); logger.debug("进入了test log4j"); logger.error("进入了test log4j");
分页
分页能减少数据的处理量。
方式一 使用 MySQL 中的 LIMIT 语句
SELECT prod_name
FROM products
LIMIT 5 OFFSET 4; -- 从第4行开始,取5行;等价于 LIMIT 4, 5
MySQL 检索出来的 第一行 是行 0 而不是行 1。
接口
/** * 分页查询用户 * * @return List<User> */ List<User> getUserListByLimit(Map<String, Integer> map);Mapper.xml
<!-- 分页 --> <select id="getUserListByLimit" parameterType="map" resultType="user"> SELECT * FROM user LIMIT #{startIndex}, #{pageSize}; </select>测试
@Test public void should_success_when_get_user_list_by_limit() { // 获得sqlSession对象,try语句在结束时会自动关闭资源,这称为try-with-resources语句 try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) { // 执行sql UserMapper userMapper = sqlSession.getMapper(UserMapper.class); Map<String, Integer> map = new HashMap<>(); map.put("startIndex", 1); map.put("pageSize", 2); List<User> userList = userMapper.getUserListByLimit(map); for (User user : userList) { System.out.println(user); } } }
方式二 RowBounds
接口
/** * 分页查询用户 * * @return List<User> */ List<User> getUserListByRowBounds();Mapper.xml
<!-- 分页 --> <select id="getUserListByRowBounds" resultType="user"> SELECT * FROM user; </select>测试
@Test public void should_success_when_get_user_list_by_row_bounds() { // 获得sqlSession对象,try语句在结束时会自动关闭资源,这称为try-with-resources语句 try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) { RowBounds rowBounds = new RowBounds(1, 2); List<User> userList = sqlSession.selectList("com.hunter.dao.UserMapper.getUserListByRowBounds", null, rowBounds); for (User user : userList) { System.out.println(user); } } }
方式三 分页插件 pagehelper
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.2</version>
</dependency>
使用注解开发
在真正的开发中,很多时候我们会选择 面向接口编程,因为面向接口是一种 解耦 的方式,易扩展、能提高复用率。
分层开发中,上层不用关注具体的实现,大家都遵守共同的标准,使得开发变得容易,规范性更好。
注解在接口上的实现
@Select("SELECT * FROM user") List<User> getUserList(); @Insert("INSERT INTO user(id, name, passwd) VALUES (#{id}, #{name}, #{passwd})") int addUser(User user); @Update("UPDATE user SET name=#{name}, passwd=#{passwd} WHERE id=#{id}") int updateUser(User user); @Delete("DELETE FROM user WHERE id=#{uid}") int deleteUser(@Param("uid") int id);需要在核心配置文件中绑定接口
<mappers> <mapper class="com.hunter.dao.UserMapper"/> </mappers>
关于 @Param() 注解
- 基本类型 的参数或者
String类型,需要加上 - 引用类型不需要加
- 如果 只有一个基本类型,可以忽略,但是建议还是加上
- 在 SQL 中引用的,就是在
@Param()中设定的属性名
复杂查询
搭建数据库表
CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB;
INSERT INTO teacher(`id`, `name`) VALUES (1, 'H老师');
CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB;
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');
测试环境搭建
- 导入 lombok
- 新建实体类 Teacher、Student
- 建立 Mapper 接口
- 建立 Mapper.xml
- 在核心配置文件中绑定注册 Mapper.xml 文件(方式很多)
- 测试查询
多对一处理
举例:多个学生归属一个老师(如班主任概念)
方式一 嵌套查询处理
<!-- namespace 会绑定一个对应的DAO/Mapper接口 -->
<mapper namespace="com.hunter.dao.StudentMapper">
<!-- 结果集映射 -->
<resultMap id="StudentWithTeacherName" type="student">
<!-- column 数据库中的字段,property 实体类中的属性 -->
<result column="id" property="id"/>
<result column="name" property="name"/>
<!-- 复杂的属性需要单独处理:
对象:association
集合:collection
-->
<!--
column:在查询的结果集中,用该列值作为查询条件执行下一条SQL语句
select: 下一条执行的语句
javaType:将执行的结果集封装成某个Java类型
-->
<association property="teacher" column="tid" select="getTeacher" javaType="teacher"/>
</resultMap>
<!-- 使用结果集映射resultMap 映射数据库的字段和实体类的属性 -->
<select id="getStudentList" resultMap="StudentWithTeacherName">
SELECT *
FROM student;
</select>
<select id="getTeacher" resultType="teacher">
SELECT *
FROM teacher
WHERE id = #{tid};
</select>
</mapper>
方式二 联表查询处理
<!-- namespace 会绑定一个对应的DAO/Mapper接口 -->
<mapper namespace="com.hunter.dao.StudentMapper">
<!-- 结果集映射 -->
<resultMap id="StudentWithTeacherName" type="student">
<!-- column 数据库中的字段,property 实体类中的属性 -->
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<!-- 复杂的属性需要单独处理:
对象:association
集合:collection
-->
<!-- javaType:将执行的结果集封装成某个Java类型 -->
<association property="teacher" javaType="teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
</association>
</resultMap>
<!-- 使用结果集映射resultMap 映射数据库的字段和实体类的属性 -->
<select id="getStudentList" resultMap="StudentWithTeacherName">
SELECT s.id AS sid, s.name AS sname, t.id AS tid, t.name AS tname
FROM student AS s
INNER JOIN teacher AS t
ON s.tid = t.id;
</select>
</mapper>
一对多处理
举例:一个老师拥有多个学生
联表查询
<mapper namespace="com.hunter.dao.TeacherMapper">
<select id="getTeacher" resultMap="teacherWithStudents">
SELECT s.id AS sid, s.name AS sname, t.id AS tid, t.name AS tname
FROM student AS s
INNER JOIN teacher AS t
ON s.tid = t.id AND t.id = #{tid};
</select>
<!-- 结果集映射 -->
<resultMap id="teacherWithStudents" type="teacher">
<!-- column 数据库中的字段,property 实体类中的属性 -->
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!-- 复杂的属性需要单独处理:
对象:association
集合:collection
-->
<!-- 集合中的泛型信息,使用ofType获取 -->
<collection property="studentList" ofType="student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
</mapper>
动态 SQL
动态 SQL 指 根据不同的条件,生成不同的 SQL 语句。其实动态 SQL 就是在拼接 SQL 语句,只要保证 SQL 的正确性,按照 SQL 的格式去排列组合 就可以了。
搭建数据库表
CREATE TABLE `blog`(
`id` VARCHAR(50) NOT NULL COMMENT '博客id',
`title` VARCHAR(100) NOT NULL COMMENT '博客标题',
`author` VARCHAR(30) NOT NULL COMMENT '博客作者',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`views` INT(30) NOT NULL COMMENT '浏览量'
)ENGINE=INNODB;
测试环境搭建
导入 lombok
新建实体类 Blog
建立 Mapper 接口
建立 Mapper.xml
在核心配置文件中绑定注册 class 文件 BlogMapper(方式很多)
开启自动提交事务
public static SqlSession getSqlSession() { // autoCommit:自动开启事务 return sqlSessionFactory.openSession(true); }初始化数据库表数据
public class BlogMapperTest { @Test public void initBlog() { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) { BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); mapper.addBlog(generateNewBlog(IdUtils.generateId(), "Mybatis学习", "Hunter", new Date(), 0)); mapper.addBlog(generateNewBlog(IdUtils.generateId(), "Java学习", "Hunter", new Date(), 0)); mapper.addBlog(generateNewBlog(IdUtils.generateId(), "Spring学习", "Hunter", new Date(), 0)); mapper.addBlog(generateNewBlog(IdUtils.generateId(), "微服务学习", "Hunter", new Date(), 0)); } } private Blog generateNewBlog(String id, String title, String author, Date date, int views) { Blog blog = new Blog(); blog.setId(id); blog.setTitle(title); blog.setAuthor(author); blog.setCreateTime(date); blog.setViews(views); return blog; } }
if 语句
<select id="queryBlogIf" parameterType="map" resultType="blog">
SELECT *
FROM blog
<!-- where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。 -->
<where>
<if test="title != null">
AND title = #{title}
</if>
<if test="author != null">
AND author = #{author}
</if>
</where>
</select>
choose, when, otherwise 语句
<select id="queryBlogChoose" parameterType="map" resultType="blog">
SELECT *
FROM blog
<!-- where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。 -->
<where>
<choose>
<when test="title != null">
AND title = #{title}
</when>
<when test="author != null">
AND author = #{author}
</when>
<otherwise>
AND views = #{views}
</otherwise>
</choose>
</where>
</select>
set 语句
<update id="updateBlog" parameterType="map">
UPDATE blog
<!-- set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。-->
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author},
</if>
</set>
WHERE id = #{id}
</update>
sql 片段
使用 sql 标签 抽取出公共的部分,在使用的地方使用 include 标签引用即可。
- 最好 基于单表来定义 sql 片段
- 不要存在 where 标签
<sql id="if-title-author">
<if test="title != null">
AND title = #{title}
</if>
<if test="author != null">
AND author = #{author}
</if>
</sql>
<select id="queryBlogIf" parameterType="map" resultType="blog">
SELECT *
FROM blog
<!-- where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。 -->
<where>
<include refid="if-title-author"></include>
</where>
</select>
foreach
<!-- SELECT * FROM blog WHERE (id = 1 OR id = 2 OR id = 3) -->
<select id="queryBlogForEach" parameterType="map" resultType="blog">
SELECT *
FROM blog
<where>
<foreach collection="ids" item="id" open="(" separator="OR" close=")">
id = #{id}
</foreach>
</where>
</select>
缓存
缓存是存在内存中的临时数据,将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从而提高查询效率,一定程度上解决了高并发系统的性能问题。
经常查询并且不经常改变的数据适合缓存。
MyBatis 中定义了两级缓存:一级缓存 和 二级缓存。默认情况下,只开启了一级缓存(SqlSession 级别 的缓存,也称为本地缓存)。二级缓存是 基于 namespace 级别 的缓存。为了提高扩展性,MyBatis 定义了缓存接口 Cache,可以通过实现 Cache 接口来自定义二级缓存。
一级缓存
一级缓存,也叫本地缓存,是 SqlSession 级别 的缓存,它 就是一个 map,默认开启。
- 与数据库 同一次会话期间查询到的数据会放在本地缓存中。
- 之后如果需要获取相同的数据,直接从缓存中拿,没必要再次访问数据库。
缓存存在的例子:
@Test
public void test_cache() {
// 获得sqlSession对象,try语句在结束时会自动关闭资源,这称为try-with-resources语句
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
// 执行sql
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.getUserById(1);
System.out.println(user);
System.out.println("=============================================================");
user = userMapper.getUserById(1);
System.out.println(user);
}
}
从日志可以看出,只有一次访问数据库的记录。
缓存失效的情况:
对数据库进行增删改操作,由于可能会改变原来的数据,缓存会刷新。
手动清理
// 手动清理缓存 sqlSession.clearCache();
例子:
@Test
public void test_cache() {
// 获得sqlSession对象,try语句在结束时会自动关闭资源,这称为try-with-resources语句
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
// 执行sql
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.getUserById(1);
System.out.println(user);
System.out.println("=============================================================");
// 增加操作
user = new User(4, "王五", "123456");
userMapper.addUser(user);
System.out.println("=============================================================");
// 重新访问数据库(确保获取数据变动后的内容),然后刷新缓存
user = userMapper.getUserById(1);
System.out.println(user);
}
}

二级缓存
二级缓存,也叫 全局缓存,是 namespace 级别的缓存,一个命名空间,对应一个二级缓存。
- 工作机制:
- 一个会话查询一条数据,这个数据就会被放在当前会话的 一级 缓存中
- 如果当前会话关闭,一级缓存中的数据被保存到二级缓存中
- 新的会话查询信息,就从二级缓存中获取内容
- 不同的 mapper 查出的数据会放在自己对应的缓存(map)中
使用步骤
在 mybatis 的核心配置文件中,开启全局缓存
<!-- 显式地开启全局缓存(默认已开启) --> <setting name="cacheEnabled" value="true"/>在 要使用二级缓存的 Mapper 中 开启缓存
<!-- 使用二级缓存 --> <cache/> <!-- 也可自定义参数 --> <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>如果 readOnly 参数没有设置,需要将实体类序列化(
implements Serializable)。
获取数据顺序
- 先看二级缓存
- 再看一级缓存
- 查询数据库
自定义缓存 Ehcache
导包
<dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.2.3</version> </dependency>在 mapper 中指定自定义的缓存实现
<!-- 使用EhcacheCache --> <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>搭配相应的配置文件
ehcache.xml<?xml version="1.0" encoding="UTF-8" ?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false"> <diskStore path="./tmpdir/Tmp_EhCache"/> <defaultCache eternal="false" maxElementsInMemory="10000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="1800" timeToLiveSeconds="259200" memoryStoreEvictionPolicy="LRU"/> <cache name="cloud_user" eternal="false" maxElementsInMemory="5000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="1800" timeToLiveSeconds="1800" memoryStoreEvictionPolicy="LRU"/> </ehcache>
但是目前都用 Redis 来做缓存,本章了解一下。