Mybatis
一、Mybatis 介绍
基本介绍
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
Mybatis官方文档
: https://mybatis.org/mybatis-3/zh_CN/index.html
Quick Start
// todo
查看官网案例: https://mybatis.org/mybatis-3/zh_CN/getting-started.html
二、🌈Mybatis 基本使用
基本使用
导入依赖
进行数据库配置
定义实体类
1
2
3
4
5
6
7
8
9
10
11
public class Emp implements Serializable {
private Integer id;
private String empName;
private Integer age;
private Double empSalary;
}定义接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public interface EmpMapper {
//查询单行记录
Emp getEmpById(Integer id);
//查询多行记录
List<Emp> getAll();
//更新
int updateEmp(Emp emp);
// 添加
int addEmp(Emp em);
// 删除
int deleteEmp(Integer id);
}在mapper.xml 中编写CRUD
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<mapper namespace="com.xiaoyu.mapper.EmpMapper">
<!-- 查询单行 -->
<select id="getEmpById" resultMap="BaseResultMap">
select * from t_emp where id = #{id}
</select>
<!-- 查询所有 -->
<select id="getAll" resultMap="BaseResultMap">
select * from t_emp
</select>
<!-- 更新语句 -->
<update id="updateEmp">
update
t_emp set emp_name = #{empName}, age = #{age},
emp_salary = #{empSalary}
where
id = #{id}
</update>
<!-- 插入语句 -->
<insert id="addEmp" useGeneratedKeys="true" keyProperty="id">
insert into
t_emp(emp_name, age, emp_salary)
values
(#{empName}, #{age}, #{empSalary})
</insert>
<!-- 删除语句 -->
<delete id="deleteEmp" parameterType="integer">
delete from t_emp where id = #{id}
</delete>
</mapper>测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MybatisApplicationTests {
EmpMapper empMapper;
void testGetEmp() {
System.out.println(empMapper.getEmpById(1));
}
void testAll() {
List<Emp> all = empMapper.getAll();
all.forEach(System.out::println);
}
}
三、参数传递
两种取值方式
#{}
:底层使用PreparedStatement
方式,SQL预编译后设置参数,无SQL注入攻击风险${}
:底层使用Statement
方式,SQL无预编译,直接拼接参数,有SQL注入攻击风险
使用场景
所有参数位置,都应该用
#{}
需要动态表名等,才用
${}
注意: 凡是使用了 ${}
的业务,一定要自己编写防SQL注入攻击代码
参数取值
传参形式 | 示例 | 取值方式 |
---|---|---|
单个参数 - 普通类型 | getEmploy(Long id) | #{变量名} |
单个参数 - List类型 | getEmploy(List |
#{变量名[0]} |
单个参数 - 对象类型 | addEmploy(Employ e) | #{对象中属性名} |
单个参数 - Map类型 | addEmploy(Map<String,Object> m) | #{map中属性名} |
案例
形参使用
@Param
指定参数名1
2
3
4
5
6
7
8
9
public interface EmpParamMapper {
//多个参数,用@Param指定参数名, #{参数名} 就可以取值。
Emp getEmployHaha( Long id,
Map<String,Object> m,
List<Long> ids,
; Emp e)
}取值使用
#{参数名}
来取值1
2
3
4
5
6
7
8
9
10
11
12
13
14<select id="getEmployHaha" resultType="com.atguigu.mybatis.bean.Emp">
select
*
from
t_emp
where
id = #{id}
and
emp_name = #{m.name}
and
age = #{ids[2]}
and
emp_salary = #{e.empSalary}
</select>实际参数
1
2
3
4
5
6
7
8
9
10
void testParam03(){
Map<String, Object> params = new HashMap<>();
params.put("name", "bbbb");
Emp emp = new Emp();
emp.setEmpSalary(1000.0D);
empParamMapper.getEmployHaha(1L, params,
Arrays.asList(19L,20L,21L,22L,34L), emp);
}
四、⭐ResultMap
ResultMap是Mybatis最强大的元素,它可以将查询到的复杂数据(比如查询到几个表中数据)映射到一个结果集当中。
简单结果映射
问题
若实体类中的属性和数据库表的字段不一致会出现 不对应的字段查询结果为空的情况
解决办法:
- JavaBean 和 数据库一样 【不推荐】
- 使用 列别名 【不推荐】
- 使用 驼峰命名自动映射
- 使用 ResultMap(自定义结果集)
使用resultMpa进行简单的结果集映射
1 |
|
resultMap
元素是 MyBatis 中最重要最强大的元素ResultMap
的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。ResultMap
的优秀之处在于 你完全可以不用显式地配置它们。如果这个世界总是这么简单就好了。(复杂映射!)
复杂映射
MyBatis 创建时的一个思想是:数据库不可能永远是你所想或所需的那个样子。 我们希望每个数据库都具备良好的第三范式或 BCNF 范式,可惜它们并不都是那样。 如果能有一种数据库映射模式,完美适配所有的应用程序,那就太好了,但可惜也没有。 而 ResultMap 就是 MyBatis 对这个问题的答案。
一对一查询案例
按照id查询订单 以及 下单的客户
实体类处理
Order类
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Order implements Serializable {
// 订单id
private Long id;
// 派送地址
private String address;
// 订单金额
private BigDecimal amount;
// 客户id
private Long customerId;
// 该订单的消费者
Customer customer;
}Customer 实体类
1
2
3
4
5
6
7
8
public class Customer implements Serializable {
private Long id;
// 客户姓名
private String customerName;
// 手机号
private String phone;
}
使用ResultMap 进行结果嵌套处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<mapper namespace="com.xiaoyu.mapper.OrderMapper">
<resultMap id="OrderAndCustomerResultMap" type="com.xiaoyu.pojo.Order">
<id property="id" column="id" jdbcType="BIGINT"/>
<result property="address" column="address" jdbcType="VARCHAR"/>
<result property="amount" column="amount" jdbcType="DECIMAL"/>
<!-- 一对一关联封装 -->
<association property="customer" javaType="com.xiaoyu.pojo.Customer">
<id property="id" column="id" jdbcType="BIGINT"/>
<result property="customerName" column="customer_name"
jdbcType="VARCHAR"/>
<result property="phone" column="phone" jdbcType="VARCHAR"/>
</association>
</resultMap>
<select id="queryOrderAndCustomer" resultMap="OrderAndCustomerResultMap">
select
c.id,c.customer_name, c.phone, o.id, o.address, o.amount
from
t_order o
join
t_customer c
on
o.customer_id = c.id
where
o.id = #{id}
</select>
</mapper>
一对多案例
按照id查询订单 以及 下单的客户
实体类处理
- Order实体类
1
2
3
4
5
6
7
8
9
10
11
public class Order implements Serializable {
// 订单id
private Long id;
// 派送地址
private String address;
// 订单金额
private BigDecimal amount;
// 客户id
private Long customerId;
}Customer 实体类
1
2
3
4
5
6
7
8
9
10
public class Customer implements Serializable {
private Long id;
// 客户姓名
private String customerName;
// 手机号
private String phone;
// 消费者的多个订单
private List<Order> list;
}
使用ResultMap 进行复杂结果映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<mapper namespace="com.xiaoyu.mapper.CustomerMapper">
<resultMap id="CustomerAndOrderResultMap" type="com.xiaoyu.pojo.Customer">
<id property="id" column="c_id" jdbcType="BIGINT"/>
<result property="customerName"
column="customer_name" jdbcType="VARCHAR"/>
<result property="phone" column="phone" jdbcType="VARCHAR"/>
<!-- collection:说明 一对N 的封装规则 ofType: 集合中元素的类型 -->
<collection property="list" ofType="com.xiaoyu.pojo.Order" >
<id property="id" column="o_id" jdbcType="BIGINT"/>
<result property="address" column="address" jdbcType="VARCHAR"/>
<result property="amount" column="amount" jdbcType="DECIMAL"/>
<result property="customerId"
column="customer_id" jdbcType="BIGINT"/>
</collection>
</resultMap>
<select id="queryCustomerAndOrderById" resultMap="CustomerAndOrderResultMap">
select
c.id c_id, customer_name, phone, o.id, address, amount, customer_id
from
t_customer c
left join
t_order o
on
c.id = o.customer_id
where
c.id = #{id};
</select>
</mapper>
Result的属性
属性 | 描述 |
---|---|
property |
映射到JavaBean的属性。 |
column |
数据库中的列名,或者是列的别名。 |
javaType |
一个 Java 类的全限定名,或一个类型别名。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。如果你映射到的是 HashMap需明确地指定 javaType |
jdbcType |
JDBC 类型, 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。 |
typeHandler |
使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的全限定名,或者是类型别名。 |
分布查询
五、⭐动态SQL
动态SQL就是根据不同的条件生成不同的SQL语句
动态SQL常用标签
IF标签
1 | <select id="queryBlog" parameterType="map" resultType="blog" > |
where标签
作用: 可以智能的添加或去掉 SQL语句中的 and | or
1 | <!-- where 标签的使用--> |
choose、when、otherwise标签
类似样式java的switch case语句
- choose —> switch
- when —> case
- otherwise —> default
set标签
set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号
1 | <update id="updateBlog" parameterType="map" > |
ForEach 标签
foreach 标签说明
- collection 需要遍历的集合
- item 集合中的每个元素
- open 开始拼接的字符
- close 结束时拼接的字符
- separator 分隔符
1 | <select id="queryForEach" parameterType="map" resultType="blog" > |
拼接完成的sql语句
select * from blog where 1 = 1 and ( id = 1 or id = 2 or id = 3 or id = 4 )
小结: 所谓的动态SQL, 本质还是SQL语句, 只是我们可以在SQL层面, 去执行一个逻辑代码
SQL片段
有的时候, 我们可能会将一些功能的部分抽取出来, 方便复用
使用sql标签抽取出公共的部分
1
2
3
4
5
6
7
8<sql id="if-title-author" >
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</sql>在需要使用的地方使用
include
标签即可1
2
3
4
5
6<select id="queryBlog2" parameterType="map" resultType="blog" >
select * from blog
<where>
<include refid="if-title-author" />
</where>
</select>
注意事项:
- 最好基于单表来定义SQL片段
- SQL片段中不要存在where 标签
六、Cache
简介
什么是缓存 [Cache]
- 存在内存中的临时数据
- 将用户经常查询的数据放在缓存(内存)中, 用户查询数据就不用从磁盘上(关系型数据库数据文件) 查询, 从缓存中查询, 从而提高查询效率, 解决了高并发系统的性能问题
为什么使用缓存
- 减少和数据库的交互次数, 减少系统开销, 提高系统效率
什么样的数据能使用缓存
- 经常查询并且不经常改变的数据
Mybatis中的缓存
Mybatis 包含一个非常强大的查询缓存特性, 他可以方便地定制和配置缓存. 缓存可以极大的提升查询效率
Mybatis系统中默认定义了两级缓存: 一级缓存和二级缓存
- 默认情况下, 只有一级缓存开启 ( SqlSession级别的缓存(事务级别的缓存), 也成为了本地缓存)
- 二级缓存需要手动开启和配置, 他是基于namespace 级别的缓存
- 为了提高拓展性, Mybatis定义了缓存接口Cache. 我们可以通过实现Cache 接口来自定义二级缓存
一级缓存
- 一级缓存也叫本地缓存: SqlSession 在此次十五中存在的缓存
- 与数据库同义词回话期间查询到的数据会放在本地缓存中
- 以后如果需要获取相同的数据, 直接从缓存中拿, 没有必要再去查询数据库
测试
- 开始日志
- 测试在一个Session中查询两次相同的记录
- 查看日志输出
1 |
|
缓存失效的情况
查询不同的记录
增删改操作, 可能会改变原来的数据, 所以必定会刷新缓存!
查询不同的Mapper.xml
手动清理缓存
1
sqlSession.clearCache(); //手动清理缓存
小结
一级缓存默认开启, 只在一次SqlSession中有效, 也就是拿到连接到关闭连接这个区间段
一级缓存相当于一个Map
二级缓存
- 二级缓存也叫全局缓存, 一级缓存作用域太低, 所以诞生了二级缓存
- 基于
namespace
级别的缓存, 一个名称空间, 对应一个二级缓存 - 工作机制
- 一个回话查询一条数据, 这个数据就会被放在当前回话的一级缓存中
- 如果当前会话关闭了, 这个回话对应的一级缓存也就没有了; 当回话关闭了, 一级缓存中的数据就会被保存到二级缓存中
- 新的会话查询信息, 就可以从二级缓存中获取内容
- 不同的mapper查出的数据会放在自己对应的缓存(map)中
二级缓存的使用
在要使用二级缓存的mapper.xml
文件中开启
1 | <cache/> |
也可以自定义一些参数
1 | <cache |
测试
问题: 如果在cache标签中没有定义参数, 则会有如下异常
Cause: java.io.NotSerializableException: com.xiaoyu.pojo.Stu
解决方法: 将实体类序列化, 实现Serializable
接口
1 |
|
小结
- 只要开启了二级缓存, 在同一个mapper.xml就下有效
- 所有的数据都会先放在一级缓存中
- 只有当回话提交, 或者关闭时, 才会提交到二级缓存中!