MyBatis学习笔记
1 MyBatis 简介
1.1 什么是 MyBatis
- MyBatis本是 apache 的一个开源项目 iBatis
- 2010年这个项目由 apache software foundation 迁移到了 google code,并且改名为 MyBatis
- 2013年11月迁移到 Github
- MyBatis 是一款优秀的持久层框架
- 它支持自定义 SQL、存储过程以及高级映射
- MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作
- MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO 为数据库中的记录
1.2 怎么获得 MyBatis
1.3 什么是持久层
-
数据持久化:将程序的数据在持久状态和瞬时状态之间转换的过程
-
持久化原因:内存有着断电即失的特性,但是有一些对象不能让他丢失,所以需要持久化
-
持久层:完成持久化工作的代码块,层的界限十分明显
1.4 为什么需要 MyBatis
- 更加方便地帮助程序员将数据存入到数据库中
- 传统的 JDBC 代码太过复杂,MyBatis 大大简化了过程
- 使用的人很多
- 优点
- 简单易学、灵活
- 解除 sql 与程序代码的耦合
- sql 和代码的分离,提高了可维护性
- 提供映射标签,支持对象与数据库的 orm 字段关系映射
- 提供对象关系映射标签,支持对象关系组建维护
- 提供 xml 标签,支持编写动态 sql
2 第一个 MyBatis 程序
2.1 搭建数据库
CREATE DATABASE `mybatis`;
USE `mybatis`;
CREATE TABLE `user`(
`id` INT(20) NOT NULL PRIMARY KEY,
`name` VARCHAR(30) DEFAULT NULL,
`age` INT(10) DEFAULT NULL,
`sex` CHAR(10) DEFAULT NULL,
`pwd` VARCHAR(30) DEFAULT NULL
)ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `user`(`id`, `name`, `age`, `sex`, `pwd`) VALUES
(1, '石昊', 25, '男', '12345'),
(2, '叶凡', 18, '男', '25415'),
(3, '林枫', 30, '男', '45612'),
(4, '王腾', 45, '男', '78445'),
(5, '梦情', 26, '女', '12254');
2.2 创建项目
新建一个普通的 Maven 项目即可
删除项目的 src
包,我们将该项目作为一个父工程,只用 pom
文件来进行依赖管理,新建子工程即可
2.3 依赖导入
<dependencies>
<!--MySql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<!--MyBatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
<!--Junit依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
2.4 创建子模块
仍旧是创建一个普通的 Maven 项目作为子模块
2.5 MyBatis 配置文件
<?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=true&useUnicode=true&characterEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="0531"/>
</dataSource>
</environment>
</environments>
</configuration>
2.6 MyBatis 工具类
package com.jiuxiao.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;
/**
* MyBatis工具类
*
* @author WuDaoJiuXiao
* @Date 2022/4/25 11:12
* @since 1.0.0
*/
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
//使用 MyBatis 第一步:获取 SqlSessionFactory 对象
String resource = "mybatis-config.xml";
try {
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取 SqlSession 对象
*
* @return
*/
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
2.7 编写代码
- 创建数据库表的实体类
package com.jiuxiao.pojo;
/**
* User实体类
*
* @author WuDaoJiuXiao
* @Date 2022/4/25 11:24
* @since 1.0.0
*/
public class User {
private int id;
private String name;
private int age;
private String sex;
private String pwd;
public User() {
}
public User(int id, String name, int age, String sex, String pwd) {
this.id = id;
this.name = name;
this.age = age;
this.sex = sex;
this.pwd = pwd;
}
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 int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", pwd='" + pwd + '\'' +
'}';
}
}
- Mapper 层接口
package com.jiuxiao.mapper;
import com.jiuxiao.pojo.User;
import java.util.List;
/**
* User的Mapper接口
*
* @author WuDaoJiuXiao
* @Date 2022/4/25 14:23
* @since 1.0.0
*/
public interface UserMapper {
List<User> getUserList();
}
- Mapper 层接口实现类,由原来的
impl
继承接口,变成了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">
<!--namespace 绑定一个对应的 Mapper 接口-->
<mapper namespace="com.jiuxiao.mapper.UserMapper">
<!--Select 查询语句-->
<select id="getUserList" resultType="com.jiuxiao.pojo.User">
select * from mybatis.user;
</select>
</mapper>
2.8 测试
public class UserMapperTest {
@Test
public void test(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
}
}
执行程序,报错内容如下
出错信息为
UssrMapper
没有注册,这是很容易让人疏忽的点,实现了 Mapper 接口之后,一定要去mybatis-config.xml
中进行注册
<!--注册 Mapper-->
<mappers>
<mapper resource="com/jiuxiao/mapper/UserMapper.xml"/>
</mappers>
注册好之后,测试程序,再次出错,错误为找不到 UserMapper.xml
文件,这是为什么?命名我们写了啊?
分析一下错误,打开 target
包,依次寻找,并没有在 com.jiuxiao.mapper
包下找到所希望的 UserMapper.xml
,为什么?
这是因为,我们的项目为一个 Maven 项目,而 Maven 是约定大于配置,Maven 会自动排除 java
路径下的所有资源配置,因此我们要去pom.xml
中配置资源文件导出路径(为了保险起见,父子工程的配置文件中均添加上配置)
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
再次测试,发现已经成功读取数据库内容
若出现字符编码相关的错误,只需要在父工程的配置文件中加入编码配置
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
3 简单 CRUD
3.1 select
根据 ID 查询用户
/**
* 根据 ID 获取用户
*
* @param id
* @return
*/
User getUserById(int id);
<select id="getUserById" resultType="com.jiuxiao.pojo.User" parameterType="int">
select * from user where id = #{id};
</select>
@Test
public void getUserById(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1);
System.out.println(user);
sqlSession.close();
}
3.2 insert
插入新用户
/**
* 添加用户
*
* @param user
* @return
*/
int addUser(User user);
<insert id="addUser" parameterType="com.jiuxiao.pojo.User">
insert into mybatis.user(id, name, age, sex, pwd)
value (#{id}, #{name}, #{age}, #{sex}, #{pwd});
</insert>
@Test
public void addUser(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.addUser(new User(6, "伊人泪", 19, "女", "12455"));
sqlSession.commit(); //不要忘记提交
sqlSession.close();
}
成功插入数据库
3.3 update
更新用户信息
/**
* 更新用户信息
*
* @param user
* @return
*/
int updateUser(User user);
<update id="updateUser" parameterType="com.jiuxiao.pojo.User">
update mybatis.user
set name = #{name}',
age = #{age},
sex = #{sex},
pwd = #{pwd}
where id = #{id};
</update>
@Test
public void updateUser(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.updateUser(new User(4, "玉如梦", 21, "女", "48848"));
sqlSession.commit();
sqlSession.close();
}
执行代码后,发现 id == 4
的用户已信息经被更新
3.4 delete
删除用户
/**
* 根据名字删除用户
*
* @param id
* @return
*/
int deleteUserByName(String name);
<delete id="deleteUserByName" parameterType="com.jiuxiao.pojo.User">
delete
from mybatis.user
where name = #{name};
</delete>
@Test
public void deleteUserByName(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.deleteUserByName("叶凡");
sqlSession.commit();
sqlSession.close();
}
执行代码,name == 叶凡
的用户已经被删除
3.5 模糊查询
模糊查询时,参数传递的两种方式
1 在 Java 代码执行时,直接使用通配符 %
该方式较为安全,类似本例子中的查询:mapper.getUserLike("%梦%");
,可以直接得到正确的查询结果
2 在 sql 拼接时使用通配符 %
该方式不太安全,可能会出现 sql 注入的隐患
举个例子,我们想要根据 id 查出用户信息,查询语句为
select * from mybatis.user where id = ?
正常情况下,用户传入的
value
为合法数字,查询语句为select * from mybatis.user where id = 2
,不会出错但是,如果用户传入的
value
为num or 1 = 1
时,查询语句变为select * from mybatis.user where id = num or 1 = 1
,此时,如果 num 的值很大,超过了数据库的总人数,那么语句作用就等价于select * from mybatis.user where true
,查询的结果就是整个数据库的用户信息,出现安全隐患
/**
* 模糊查询
*
* @param value
* @return
*/
List<User> getUserLike(String value);
<select id="getUserLike" resultType="com.jiuxiao.pojo.User" parameterType="string">
<!--方式一:Java 传值,较为安全-->
select *
from mybatis.user
where name like #{value};
<!--方式二:sql拼接,有 sql 注入的风险-->
select *
from mybatis.user
where name like "%"#{value}"%";
</select>
@Test
public void getUserLike(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userLike = mapper.getUserLike("%梦%"); //查询名字中带有 "梦" 字的用户
for (User user : userLike) {
System.out.println(user);
}
sqlSession.close();
}
4 配置解析
4.1 核心配置文件
MyBatis 的配置文件(mybatis-config.xml)包含了会深深影响 MyBatis 行为的设置和属性信息
+ configuration(配置)
+ properties(属性)
+ settings(设置)
+ typeAliases(类型别名)
+ typeHandlers(类型处理器)
+ objectFactory(对象工厂)
+ plugins(插件)
+ environments(环境配置)
+ environment(环境变量)
+ transactionManager(事务管理器)
+ dataSource(数据源)
+ databaseIdProvider(数据库厂商标识)
+ mappers(映射器)
在 xml 配置文件中,所有的标签都是有一定顺序的,不能乱序使用(顺序就是上方从上至下的顺序)
4.2 属性 properties
-
这些属性可以在外部进行配置,并可以进行动态替换
-
既可以在典型的 Java 属性文件(*.properties)中配置这些属性,也可以在 properties 元素的子元素中设置
在原来,我们把数据库的配置文件,需要直接写在 mybatis-config.xml
中,不是动态的,使用 Java 的配置文件 *.properties
可以进行动态配置
首先写一个配置文件 application.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf-8
username=root
password=0531
然后核心配置文件 mybatis-config.xml
中动态导入配置即可
<configuration>
<!--引入配置文件-->
<properties resource="application.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>
<mappers>
<mapper resource="com/jiuxiao/mapper/UserMapper.xml"/>
</mappers>
</configuration>
查询全部用户列表,测试成功
注意
可以直接引入外部配置文件
可以再其中增加一些属性配置
如果两个文件中有同一个字段,则优先使用外部配置文件的字段
4.3 类型别名 typeAliases
- 类型别名可为 Java 类型设置一个缩写名字
- 仅用于 XML 配置,意在降低冗余的全限定类名书写
1 指定具体的类
<select id="getUserList" resultType="User">
select *
from mybatis.user;
</select>
<!--指定具体的类-->
<typeAliases>
<typeAlias type="com.jiuxiao.pojo.User" alias="User"/>
</typeAliases>
2 只指定包名
<!--只指定包名-->
<typeAliases>
<package name="com.jiuxiao.pojo"/>
</typeAliases>
4.4 设置 setting
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为
最基本的有以下几种需要记住
4.5环境配置 environments
MyBatis 可以配置成适应多种环境(通过改变 id 来改变环境),但是每个 SqlSessionFactory 实例只能选择一种环境
MyBatis 默认的事务管理器就是 JDBC ,一共两种 [JDBC / MANAGED]
<transactionManager type="JDBC"/>
MtBatis 默认的连接池处于开启状态,一共三种 [UNPOOLED / POOLED / JNDI]
<dataSource type="POOLED">
MtBatis 默认使用的环境 ID 为 development
<environments default="development">
4.6 映射器 mappers
MapperRegistry:注册绑定到我们需要的 Mapper 文件
方式一:使用 resource 绑定 xml 文件【推荐使用】
xml 文件随便放在什么地方都可以,只要 xml 文件的路径写正确
<mappers>
<mapper resource="com/jiuxiao/mapper/UserMapper.xml"/>
</mappers>
方式二:使用 class 绑定接口文件
注意点:
接口和它的 Mapper 配置文件必须同名
接口和它的 Mapper 配置文件必须在同一个包下
<mappers>
<mapper class="com.jiuxiao.mapper.UserMapper"/>
</mappers>
方式三:直接使用 package 注册 Mapper 所在的包
注意点:
接口和它的 Mapper 配置文件必须同名
接口和它的 Mapper 配置文件必须在同一个包下
<mappers>
<package name="com.jiuxiao.mapper"/>
</mappers>
4.7 作用域和生命周期
SqlSessionFactoryBuilder
-
一旦创建了 SqlSessionFactory,就不再需要它了
-
它的最佳作用域是作为局部变量来使用
SqlSessionFactory
- 其本质其实是数据库连接池
-
一旦被创建就在程序运行期间一直存在
-
它的最佳作用域是应用作用域,最简单的就是使用单例模式或者静态单例模式
SqlSession
-
可以理解为一个连接到连接池的一个请求
-
它的实例不是线程安全的,不能被共享,所以它的最佳作用域是请求或方法作用域
-
用完之后要赶紧关闭,否则会造成资源占用
5 ResultMap 结果集映射(重点)
我们目前使用的项目中, pojo
下的 User
实体类的属性,它的属性名和数据库字段名完全一致
那么,我们将 User
中的属性名修改之后,与数据库中的字段完全不一致,此时使用用户 id 查询用户,发现返回值为 null
,根本查不到了
这种情况下,我们怎么读取数据库信息?
解决方式一:起别名
我们在写查询语句的时候,直接手动将数据库字段映射到实体类的属性名上,这无疑是最暴力、最简单的方法
<select id="getUserById" resultType="User" parameterType="int">
select id userId, name userName, age userAge, sex gender, pwd password
from mybatis.user
where id = #{id};
</select>
解决方式二:使用结果集映射 resultMap
<mapper namespace="com.jiuxiao.mapper.UserMapper">
<!--结果集映射-->
<resultMap id="UserMap" type="User">
<!--column : 数据库字段名 property : 实体类属性名-->
<result column="id" property="userId"/>
<result column="name" property="userName"/>
<result column="age" property="userAge"/>
<result column="sex" property="gender"/>
<result column="pwd" property="password"/>
</resultMap>
<select id="getUserById" resultMap="UserMap" parameterType="int">
select *
from mybatis.user
where id = #{id};
</select>
</mapper>
6 日志
6.1 日志工厂
如果一个数据库操作出现了异常,那么就需要排错,这个时候,日志就是最好的助手
MyBatis 为我们提供了很多的日志种类,其中重点是 LOG4J
和 STDOUT_LOGGING
两种
在 mybatis 的核心配置文件中,配置我们的日志
<!--日志类型-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
6.2 log4j
6.2.1 什么是 log4j
-
Log4j 是 Apache 的一个开源项目,通过使用 Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI 组件等
-
可以控制每一条日志的输出格式
-
可以定义每条日志的级别
-
通过配置文件来灵活配置,不需要修改代码
6.2.2 怎么使用 log4j
- 首先需要导入 log4j 的依赖
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
- 然后配置 log4j.properties
#日志输出目的地
log4j.rootLogger=DEBUG,console,file
#console 输出的相关设置
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
#file 输出的相关设置
log4j.appender.FILE.append=true #日志类型为追加
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/jiuxiao.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
#log 输出级别
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,与上面的
STDOUT_LOGGING
相比更加详细
6.3 log4j 简单使用
-
在要使用 log4j 的类中导入包
import org.apache.log4j.Logger
-
日志对象的参数为当前类的 class
-
常用日志级别
logger.info("info : 进入了 log4j");
logger.debug("debug : 进入了 log4j");
logger.error("error : 进入了 log4j");
- 在
log4j.properties
中我们配置了日志输出文件,可以看到文件里也有很详细的日志信息
7 分页
7.1 为什么要使用分页
如果数据库有成千上万个数据,如果不用分页,而是一次性全部查询出来并且展示,那么无疑速度很慢
所以,我们就需要使用分页功能,每个页面只展示一点,那么每次只需要去数据库查询一点,这样就可以提高查询效率
7.2 limit 语法
select * from tableName limit startIndex, pageSize;
select * from User limit 1, 5; -- 从 User 表的下标 1 处开始,查询五个数据
select * from User limit 5; -- 从 User 表的第一个数据开始,查询到第 5 个数据
7.3 limit 分页
/**
* 使用 limit 实现分页
*
* @param map
* @return
*/
List<User> getUserByLimit(Map<String, Object> map);
<!--结果集映射-->
<resultMap id="UserMap" type="User">
<result column="id" property="userId"/>
<result column="name" property="userName"/>
<result column="age" property="userAge"/>
<result column="sex" property="gender"/>
<result column="pwd" property="password"/>
</resultMap>
<!--分页-->
<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
select * from mybatis.user limit #{startIndex}, #{pageSize}
</select>
@Test
public void getUserByLimit(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("startIndex", 1);
map.put("pageSize", 2);
List<User> userList = mapper.getUserByLimit(map);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
7.4 RowBounds 分页(过时)
/**
* 使用 RowBounds 实现分页
*
* @return
*/
List<User> getUserByRowBounds();
<!--分页2-->
<select id="getUserByRowBounds" resultMap="UserMap">
select *
from mybatis.user
</select>
@Test
public void getUserByRowBounds(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
RowBounds rowBounds = new RowBounds(1, 2);
List<User> userList = sqlSession.selectList("com.jiuxiao.mapper.UserMapper.getUserByRowBounds", null, rowBounds);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
8 使用注解开发
8.1 简单查询
/**
* User的Mapper接口
*
* @author WuDaoJiuXiao
* @Date 2022/4/25 14:23
* @since 1.0.0
*/
public interface UserMapper {
@Select("select * from mybatis.user")
List<User> getUserList();
}
<!--绑定接口-->
<mappers>
<mapper class="com.jiuxiao.mapper.UserMapper"/>
</mappers>
/**
* UserDao测试
*
* @author WuDaoJiuXiao
* @Date 2022/4/25 14:31
* @since 1.0.0
*/
public class UserMapperTest {
@Test
public void getUserList(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
}
虽然查询结果有数据,但是有的结果为 null,这是为什么?
这就是使用注解的不足之处了,对于数据库字段与实体类属性名不一致的情况,它无法解决
使用注解来映射简单语句会让开发流程简化,但是对于一次稍微复杂一点的语句,Java 的注解此时就有点力不从心了
因此,如果需要完成复杂的事情,最好使用 XML 文件来完成映射
思考:为什么就仅仅在接口上写了一个注解,他就能从数据库查询出信息?原理是什么?
8.2 注解实现 CRUD
查询数据功能
//方法存在多个参数时,所有的参数必须加上 @Param 注解
//其中,@Select 注解中的 #{xxx} 参数名,对应于 @Param 注解中的值
@Select("select * from mybatis.user where id = #{id} and name = #{name}")
User getUserByIdAndName(@Param("id") int userId, @Param("name") String userName);
@Test
public void getUserByIdAndName(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserByIdAndName(5, "梦情");
System.out.println(user);
sqlSession.close();
}
增加数据功能
在创建 sqlSession 对象时,可以传一个布尔值,来控制事务是否自动提交
/** * 获取 SqlSession 对象 * * @return */ public static SqlSession getSqlSession() { return sqlSessionFactory.openSession(true); //设置事务自动提交 }
@Insert("insert into mybatis.user(id, name, age, sex, pwd) value (#{id}, #{name}, #{age}, #{sex}, #{pwd})")
int addUser(User user);
@Test
public void addUser(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.addUser(new User(8, "霍诗韵", 23, "女", "45822"));
sqlSession.close();
}
修改数据功能
@Update("update mybatis.user set pwd = #{pwd} where name = #{name}")
int updateUser(@Param("pwd") String password, @Param("name") String name);
@Test
public void updateUser(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.updateUser("00000", "梦情");
sqlSession.close();
}
删除数据功能
@Delete("delete from mybatis.user where id = #{id}")
void deleteUserById(@Param("id") int userId);
@Test
public void deleteUserById(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.deleteUserById(7);
sqlSession.close();
}
关于
@Param
注解
基本类型的参数或者 String 类型,都需要加上
@Param
注解引用类型的参数不需要加该注解
如果只有一个基本类型的话,可以不加该注解,但是规范写法应该加上
在 SQL 中引用的就是这里
@Param
注解中设定的属性名
#{}
和${}
区别?
#{}
:可以防止 sql 注入,安全性较高,推荐使用
${}
:早些年这样写,不能防止 sql 注入,不推荐,现在一般都用#{}
9 MyBatis 执行原理
10 Lombok
10.1 什么是 Lombok
-
Lombok 项目是一个 Java 库,它会自动插入编辑器和构建工具中
-
Lombok 提供了一组有用的注释,用来消除 Java 类中的大量样板代码
-
仅五个字符就可以替换数百行代码从而产生干净,简洁且易于维护的 Java 类
10.2 安装和导入 Lombok
直接在 IDEA 的插件市场搜索安装
导入 Lombok 依赖
<!--Lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
10.3 Lombok 的使用
@Data 注解: 自动生成无参构造、get、set、toString、hashCode、equals、canEquals 方法
@NoArgsContructor 注解: 自动生成无参构造
@AllArgsContructor 注解: 自动生成有参构造
@ToString 注解: 自动生成 toString 方法
@EqualsAndHashCode 注解: 自动生成 hashCode、equals、canEquals 方法
注解加到类名上,就是针对所有属性;加到属性上,只针对该属性
11 多对一 && 一对多
以学生和老师的关系为例
对于学生而言,多个学生关联同一个老师(多对一)
对于老师而言,所拥有的一个集合中有多个学生(一对多)
11.1 创建测试环境
就以学生和老师的关系为例,创建如下数据库
CREATE TABLE `teacher`(
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
CREATE TABLE `student`(
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `ftkid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO teacher(`id`, `name`) VALUE (1, '林枫');
INSERT INTO `student`(`id`, `name`, `tid`) VALUES
(1, '梦情', 1),
(2, '柳菲', 1),
(3, '段欣叶', 1),
(4, '秋月心', 1),
(5, '伊人泪', 1);
然后分别创建两个类对应的实体类
package com.jiuxiao.pojo;
import lombok.Data;
/**
* 老师实体类
*
* @author WuDaoJiuXiao
* @Date 2022/4/28 14:49
* @since 1.0.0
*/
@Data
public class Teacher {
private int id;
private String name;
}
package com.jiuxiao.pojo;
import lombok.Data;
/**
* 学生实体类
*
* @author WuDaoJiuXiao
* @Date 2022/4/28 14:48
* @since 1.0.0
*/
@Data
public class Student {
private int id;
private String name;
//学生需要去关联老师
private Teacher teacher;
}
绑定接口
<mappers>
<mapper class="com.jiuxiao.mapper.TeacherMapper"/>
<mapper class="com.jiuxiao.mapper.StudentMapper"/>
</mappers>
测试环境是否创建成功
public interface TeacherMapper {
@Select("select * from mybatis.teacher where id = #{tid}")
Teacher getTeacher(@Param("tid") int id);
}
public class Test {
public static void main(String[] args) {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacher(1);
System.out.println(teacher);
}
}
11.2 多对一处理
现在要实现以下需求:查询出所有学生的信息,并且学生的信息中还要有该学生对应老师的名字,即实现以下 SQL 查询
select s.id, s.name, t.name from student s, teacher t where s.tid = t.id;
方式一:按照查询嵌套处理
该方式类似于查询中的嵌套子查询,即类似于下方的 sql 语句
select s.id, s.name, t.name
from student s, teacher t
where s.tid = (
select t.id
from teacher
);
/**
* 获取所有学生的信息以及老师的名称 - 按照查询嵌套处理
*
* @return
*/
List<Student> getStudentInfo();
<!--按照查询嵌套处理-->
<select id="getStudentInfoBySearch" resultMap="StudentAndTeacher">
select *
from student
</select>
<select id="getTeacher" resultType="Teacher">
<!-- 按照平时查询时的嵌套语句进行嵌套 -->
select *
from teacher
where id = #{id}
</select>
<resultMap id="StudentAndTeacher" type="Student">
<result column="id" property="id"/>
<result column="name" property="name"/>
<!-- association : 对象 / collection : 集合-->
<association column="tid" property="teacher" javaType="Teacher" select="getTeacher"/>
</resultMap>
@Test
public void getStudentInfo(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> studentList = mapper.getStudentInfo();
for (Student student : studentList) {
System.out.println(student);
}
}
方式二:按照结果嵌套处理
该方式类似于查询中的连表查询,即类似于下方的 sql 语句
select s.id s_id, s.name s_name, t.name t_name
from student s, teacher t
where s.tid = t.id
/**
* 获取所有学生的信息以及老师的名称 - 按照结果嵌套处理
*
* @return
*/
List<Student> getStudentInfoByResult();
<!--按照结果嵌套处理-->
<select id="getStudentInfoByResult" resultMap="StudentAndTeacherTwo">
<!-- 先按照数据库平时查询的方式写出查询语句,然后再去 resultMap 中对应各个表字段之间的关系 -->
select s.id s_id, s.name s_name, t.name t_name
from student s,
teacher t
where s.tid = t.id
</select>
<resultMap id="StudentAndTeacherTwo" type="Student">
<result property="id" column="s_id"/>
<result property="name" column="s_name"/>
<association property="teacher" javaType="Teacher">
<result property="name" column="t_name"/>
</association>
</resultMap>
@Test
public void getStudentInfoByResult(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> studentList = mapper.getStudentInfoBySearch();
for (Student student : studentList) {
System.out.println(student);
}
}
11.3 一对多处理
还是以老师学生为例,要实现一个需求:根据老师的 id 查询出该老师的信息,以及该老师对应的学生的信息
首先创建老师、学生实体类
/**
* 老师实体类
*
* @author WuDaoJiuXiao
* @Date 2022/4/28 14:49
* @since 1.0.0
*/
@Data
public class Teacher {
private int id;
private String name;
//一个老师拥有多个学生
private List<Student> students;
}
/**
* 学生实体类
*
* @author WuDaoJiuXiao
* @Date 2022/4/28 14:48
* @since 1.0.0
*/
@Data
public class Student {
private int id;
private String name;
private int tid;
}
方式一:按照结果嵌套处理(适合我自己理解)
该方式类似于查询中的连表查询,即类似于下方的 sql 语句
select t.id t_id, t.name t_name, s.id s_id, s.name s_name
from teacher t, student s
where t.id = s.tid and t.id = 要查询的id;
/**
* 获取指定老师下的所有学生及老师信息
*
* @return
*/
List<Teacher> getAllStudentInfo(@Param("tid") int id);
<mapper namespace="com.jiuxiao.mapper.TeacherMapper">
<select id="getTeacherList" resultType="Teacher">
select *
from teacher
</select>
<select id="getAllStudentInfo" resultMap="StudentByTeacher">
select t.id t_id, t.name t_name, s.id s_id, s.name s_name
from teacher t, student s
where t.id = s.tid and t.id = #{tid}
</select>
<resultMap id="StudentByTeacher" type="Teacher">
<result property="id" column="t_id"/>
<result property="name" column="t_name"/>
<collection property="students" ofType="Student">
<result property="id" column="s_id"/>
<result property="name" column="s_name"/>
</collection>
</resultMap>
</mapper>
@Test
public void getAllStudentInfo(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
List<Teacher> teacherList = mapper.getAllStudentInfo(1);
for (Teacher teacher : teacherList) {
System.out.println("teacherId = " + teacher.getId());
System.out.println("teacherName = " + teacher.getName());
List<Student> students = teacher.getStudents();
for (Student student : students) {
System.out.println(student);
}
}
}
方式二:按照嵌套子查询处理(不怎么适合我自己)
该方式类似于查询中的嵌套子查询,即类似于下方的 sql 语句
select t.id t_id, t.name t_name, s.id s_id, s.name s_name
from teacher t, student s
where t.id = 要查询的id and t.id = any (
select s.tid
from student
);
/**
* 获取指定老师下的所有学生及老师信息 - 嵌套子查询方式
* @param id
* @return
*/
List<Teacher> getAllStudentInfoBySearch(@Param("tid") int id);
<!--获取指定老师下的所有学生及老师信息 - 嵌套子查询方式-->
<select id="getAllStudentInfoBySearch" resultMap="StudentByTeacherTwo">
select * from teacher where id = #{tid}
</select>
<resultMap id="StudentByTeacherTwo" type="Teacher">
<collection property="students" column="id" javaType="ArrayList" ofType="Student" select="tempSearch"/>
</resultMap>
<select id="tempSearch" resultType="Student">
select * from student where tid = #{tid}
</select>
@Test
public void getAllStudentInfoBySearch(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
List<Teacher> teacherList = mapper.getAllStudentInfoBySearch(1);
for (Teacher teacher : teacherList) {
System.out.println("teacherId = " + teacher.getId());
System.out.println("teacherName = " + teacher.getName());
List<Student> students = teacher.getStudents();
for (Student student : students) {
System.out.println(student);
}
}
}
小结
关联:
association
,表示多对一集合:
collection
,表示一对多
javaType
:用来指定实体类中属性的类型
ofType
:用来指定映射到List
或者集合中的泛型的约束类型(例如为List<User>
,那么ofType = User
)应当尽量保证 SQL 的可读性,通俗易懂
针对一对多、多对一时,注意实体类属性名与数据库字段命名问题
12 动态 SQL
12.1 什么是动态 SQL
-
动态 SQL 是 MyBatis 的强大特性之一
-
利用动态 SQL,可以彻底摆脱 SQL 语句拼接的痛苦
-
其本质就是在拼接 SQL 语句
12.2 环境搭建
首先创建一个数据库表 blog
用来测试
CREATE TABLE `blog`(
`id` VARCHAR(50) NOT NULL COMMENT '博客id',
`title` VARCHAR(100) NOT NULL COMMENT '博客标题',
`author` VARCHAR(30) NOT NULL COMMENT '博客作者',
`create_time` TIMESTAMP NOT NULL COMMENT '创建时间',
`views` INT(30) NOT NULL COMMENT '浏览量'
)ENGINE=INNODB DEFAULT CHARSET=utf8;
然后就是实体类、实体类的 Mapper 文件、注册 Mapper 文件
/**
* Blog 实体类
*
* @author: WuDaoJiuXiao
* @Date: 2022/04/29 14:25
* @since: 1.0.0
*/
@Data
@NoArgsConstructor
public class Blog {
private String id;
private String title;
private String author;
private Date createTime;
private int views;
}
<!--开启驼峰命名转换-->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<mappers>
<mapper class="com.jiuxiao.mapper.BlogMapper"/>
</mappers>
在企业开发中,一般会将用户的 id 使用 UUID 生成,新建一个工具类,专门用来生成 UUID
/**
* ID 工具类
*
* @author: WuDaoJiuXiao
* @Date: 2022/04/29 14:40
* @since: 1.0.0
*/
public class IdUtils {
public static String getUUId(){
return UUID.randomUUID()
.toString()
.replaceAll("-", "");
}
}
插入测试用的数据
12.3 关键字标签
实现如下需求:传入的值为 author 则根据 author 查,传入 title 则根据 title 查...... 什么都不传,则查询全部
IF 标签
/**
* 查询博客
*
* @param map
* @return
*/
public List<Blog> getBlogListByIf(Map map);
<select id="getBlogListByIf" resultType="blog">
select * from mybatis.blog where 1 = 1
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
<if test="views != null">
and views > #{views}
</if>
</select>
首先,不传入任何数据,按照预期,返回结果应该为数据库表的所有信息
@Test
public void getBlogListByIf(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
List<Blog> blogList = mapper.getBlogListByIf(map);
for (Blog blog : blogList) {
System.out.println(blog);
}
}
然后分别传入数据进行测试
map.put("author", "荒天帝");
map.put("title", "C++入门");
map.put("views", 1564);
CHOOSE 标签
<select id="getBlogListByChoose" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<choose>
<when test="title != null">
and title like #{title}
</when>
<when test="author != null">
author = #{author}
</when>
<otherwise>
and views > #{views}
</otherwise>
</choose>
</where>
</select>
该标签中,只会选择一个条件去完成,类似 Java 的 switch...case
我们不传入 title,只传入 author 和 views,结果是走到第二个 when
时符合条件,直接进行了返回
WHERE 标签
<select id="getBlogListByChoose" parameterType="map" resultType="blog">
select * from mybatis.blog
<!-- where 标签:省去我们写 and 的困扰-->
<where>
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
author = #{author}
</if>
</where>
</select>
SET 标签
该标签会帮助我们去掉 SQL 语句中多余的标签
/**
* 更新博客
*
* @param map
*/
void updateBlog(Map map);
<update id="updateBlog" parameterType="map">
update mybatis.blog
<set>
<if test="author != null">
author = #{author},
</if>
<if test="title != null">
title = #{title}
</if>
</set>
where views > #{views}
</update>
@Test
public void updateBlog(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
map.put("title", "简单易懂 MyBatis");
map.put("views", 5000);
mapper.updateBlog(map);
}
我们在传值时,只传了 title
而没有传入 author
,那么,在第一个 if 判断中 author
就为空,最终的 sql 语句就是
update mybatis.blog set author = , title = '简单易懂 MyBatis' where views > 5000;
很明显这样的 SQL 语句是不合法的,一定会报错,但是并没有
所以 SET 标签就是帮我们去掉了多余的逗号,最终的 SQL 语句是
update mybatis.blog set title = '简单易懂 MyBatis' where views > 5000;
FOREACH 标签
动态 SQL 的另一个常见使用场景是对集合进行遍历,比如:
<foreach item="id" index="index" collection="id-list"
open="(" separator="," close=")">
#{id}
</foreach>
-- 以 Java 的循环来进行类比 for (int id : id-list)
-- id : 表示遍历的每个元素的代称
-- id-list : 要进行遍历的集合/数组名称
-- index : 下标
-- open : 数组的开始符号
-- close : 数组的结束符号
-- separator : 分隔符
需求:实现该 sql 语句:
select * from blog where 1 = 1 and (id = 1 or id = 3 or id = 4)
/**
* 查询第 1、3、4 号博客
* @param map
* @return
*/
List<Blog> searchBlogsForeach(Map map);
<select id="searchBlogsForeach" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id = #{id}
</foreach>
</where>
</select>
@Test
public void searchBlogsForeach(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
ArrayList<Integer> ids = new ArrayList<Integer>();
ids.add(1);
ids.add(3);
ids.add(4);
map.put("ids", ids);
List<Blog> blogList = mapper.searchBlogsForeach(map);
for (Blog blog : blogList) {
System.out.println(blog);
}
}
12.4 SQL 片段
SQL 片段就是将一些使用频率较多的 SQL 语句单独取出来成为一个片段,以后要实现相似功能,直接进行引用即可
<!-- 使用 SQL 片段之前 -->
<select id="getBlogListByIf" resultType="blog">
select * from mybatis.blog where 1 = 1
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
<if test="views != null">
and views > #{views}
</if>
</select>
<!-- 使用 SQL 片段 -->
<sql id="if-test">
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
<if test="views != null">
and views > #{views}
</if>
</sql>
<select id="getBlogListByIf" parameterType="map" resultType="blog">
select * from mybatis.blog where 1 = 1
<include refid="if-test"/>
</select>
13 缓存
13.1 什么是缓存
-
缓存的本质为存放在内存中的临时数据
-
将用户经常访问的数据放在缓存中,再次去查询的时候就不用去数据库中费时费力查询结果,而是直接从缓存中读取
-
这样提高了查询效率,节省了数据库资源,解决了高并发系统的性能问题
-
对于经常查询的、不改变的数据,可以使用缓存
13.2 MyBatis 缓存
MyBatis 中默认定义了两级缓存:一级缓存和二级缓存
-
默认情况下,只开启了一级缓存(SqlSession 级别的缓存,也称为本地缓存)
-
二级缓存需要手动进行开启,它的配置是基于 namespace 级别的缓存
-
MyBatis 顶一个缓存接口 Cache,因此可以通过实现 Cache 接口来自定义二级缓存
13.3 一级缓存
-
一级缓存也叫做本地缓存,与数据库的同一次会话查询到的数据会放在本地缓存中
-
以后要查询相同的数据,直接从缓存中拿,没必要去查询数据库
-
一级缓存从 SqlSession 创建开始,直到 SqlSession 关闭结束
在一个 SqlSession 中连续查询两次数据库的同一记录,可以发现,数据库只进行了一次查询,第二次是在本地缓存中直接取的``
@Test
public void getUserById(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper2 mapper = sqlSession.getMapper(UserMapper2.class);
User2 u1 = mapper.queryUserById(2);
System.out.println(u1);
System.out.println("-----------------");
User2 u2 = mapper.queryUserById(2);
System.out.println(u2);
System.out.println("u1 == u2 -----> " + (u1 == u2));
}
缓存失效的情况
-
查询不同的记录,毫无疑问肯定会刷新缓存
-
增、删、改操作会修改数据库的数据,因此在每一次增删改操作之后,都会去刷新一次缓存
@Test
public void getUserById(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper2 mapper = sqlSession.getMapper(UserMapper2.class);
User2 u1 = mapper.queryUserById(1);
System.out.println(u1);
mapper.updateUserById(new User2(2, "段无涯", 32, "男", "45654"));
User2 u2 = mapper.queryUserById(1);
System.out.println(u2);
}
-
查询不同的 Mapper
-
手动清理缓存
@Test
public void getUserById(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper2 mapper = sqlSession.getMapper(UserMapper2.class);
User2 u1 = mapper.queryUserById(1);
System.out.println(u1);
sqlSession.clearCache();
User2 u2 = mapper.queryUserById(1);
System.out.println(u2);
}
13.4 二级缓存
-
二级缓存也成为全局缓存,一级缓存作用域太低了,所以就有了二级缓存
-
二级缓存是基于 namespace 级别的缓存,一个名称空间,对应一个二级缓存
-
工作机制
-
一个会话查询一条数据,该数据就会被放在当前会话的一级缓存中
-
如果当前会话关闭了,那么该会话对应的一级缓存也就没有了
-
在二级缓存中,一级缓存虽然关闭了,但是一级缓存中的数据会被保存在二级缓存当中
-
新的会话如果要查询该信息,就可以直接从二级缓存中获取内容
-
不同的接口对应的 Mapper 查出的数据,会被放在自己对应的 Map 中
-
要使用二级缓存,首先要在 mybatis-fonfig.xml 中显式开启全局缓存
<settings>
<!--显式开启全局缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
然后在要使用二级缓存的 Mapper 中开启
<mapper namespace="com.jiuxiao.mapper.UserMapper2">
<!--直接开启,不配置参数-->
<cache/>
<!--配置参数-->
<cache eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
</mapper>
@Test
public void CacheInfo(){
SqlSession sqlSessionOne = MyBatisUtils.getSqlSession();
UserMapper2 mapperOne = sqlSessionOne.getMapper(UserMapper2.class);
User2 userOne = mapperOne.queryUserById(1);
System.out.println(userOne);
sqlSessionOne.close(); //一级缓存关闭,它所具有的信息会被保存到二级缓存中
SqlSession sqlSessionTwo = MyBatisUtils.getSqlSession();
UserMapper2 mapperTwo = sqlSessionTwo.getMapper(UserMapper2.class);
User2 userTwo = mapperTwo.queryUserById(1);
System.out.println(userTwo);
sqlSessionTwo.close();
}
13.5 缓存原理
缓存顺序
-
先访问二级缓存,查看有没有查询的数据
-
再访问一级缓存,查看有没有查询的数据
-
最后才访问数据库
13.6 自定义缓存 Ehcache
Ehcache 是一种广泛使用的开源 Java 分布式缓存,主要用于通用缓存,早期使用的它,但现在慢慢的已经被 Redius 所代替
Ehcache 依赖导入
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.1.0</version>
</dependency>
在 Mapper 中设置缓存框架为 Ehcache
<mapper namespace="com.jiuxiao.mapper.UserMapper2">
<!--使用 Ehcache-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
</mapper>
新建 Ehcache 的配置文件 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 : 缓存目录,Ehcache 分为内存和磁盘两级,此属性定义磁盘的缓存位置
user.home : 用户主目录
user.dir : 用户当前工作目录
java.io.tmpdir : 默认临时文件路径
-->
<diskStore path="./tmpdir/Tmp_Ehcache"/>
<!-- defaultCache : 默认的缓存策略,当 ehcache 找不到时,则会使用这个缓存策略,只能定义一个默认缓存策略 -->
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="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"/>
<!--
cache 参数解析:
name : 缓存名称
maxElementsInMemory : 缓存的最大数目
maxElementsOnDisk : 硬盘最大缓存数目
eternal : 对象是否永久有效,一旦设置为 true,timeout 将不再起作用
overflowToDisk : 当系统宕机时,是否保存到磁盘中
timeToIdleSeconds : 对象在失效之前允许的闲置时间(单位:秒),仅当 eternal=false 时才使用
timeToLiveSeconds : 对象在失效之前允许的存活时间(单位:秒),仅当 eternal=false 时才使用
diskPersistent : 是否缓存虚拟机重启期间的数据
diskSpoolBufferSizeMB : 设置磁盘缓存区的大小,默认为 30MB,每个 Cache 都要有自己的大小
diskExpiryThreadIntervalSeconds : 磁盘失效线程运行时间间隔,默认 120 秒
memoryStoreEvictionPolicy : 当缓存达到 maxElementsInMemory 设置的最大值时, Ehcache 会根据指定的策略去情路缓存,默认为 LRU 算法
可选策略有 : FIFO (先进先出算法)、LRU (近期最少使用算法)、LFU (一直最少使用算法)
clearOnFlush : 内存数量最大时是否进行清理
-->
</ehcache>