Mybatis个人总结
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/b26873e7a352ea91e9b82e45ac407781.png)
mybatis官方文档
参考
CGB2109-Day09-Mybatis
CGB2109-Day10-mybatis
一. 项目创建
1. 创建工程
2. 导入jar包
<!--mybatis依赖包-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<!--jdbc依赖包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--添加lombok的包-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
3. YML配置文件
#1.配置端口号 注意缩进!!!!!
server:
port: 8090
#2.配置数据源
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/jt?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
username: root
#yml文件 0不解析 如果字母以0开头则引号包裹
#password: "0123456"
password: root
#3.配置Mybatis
mybatis:
type-aliases-package: com.jt.pojo
#将所有的映射文件全部加载
mapper-locations: classpath:/mappers/*.xml
#开启驼峰映射
configuration:
map-underscore-to-camel-case: true
#4.打印Sql com.jt.mapper下的Sql日志
logging:
level:
com.jt.mapper: debug
4. Mapper接口
通过注解Mapper描述将该类交给spring容器管理
package com.jt.mapper;
import com.jt.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper //将接口交给Spring容器管理 Map<userMapper,JDK代理对象>
public interface UserMapper {
//指定接口方法 查询demo_user的全部数据
List<User> findAll();
}
5.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="">
</mapper>
6. 编辑测试类
package com.jt;
import com.jt.mapper.UserMapper;
import com.jt.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class TestMybatis {
@Autowired
private UserMapper userMapper;
@Test
public void testDemo1(){
List<User> userList = userMapper.findAll();
System.out.println(userList);
}
}
二. 常用语法
1. 动态取值
1.1 参数传递: 使用#{key} 获取的参数的值
1.1.1 Mapper接口
@Mapper //将接口交给Spring容器管理 Map<userMapper,JDK代理对象>
public interface UserMapper {
//根据ID查询数据库
User findUserById(int id);
}
1.1.2 xml映射文件
<!-- 根据ID查询数据库
动态取值: #{key}
-->
<select id="findUserById" resultType="com.jt.pojo.User">
select * from demo_user where id = #{id}
</select>
1.1.3 解析
xml的sql语句标签参数
id : 匹配mapper接口的方法名
resultType : 封装查询的数据,并传回到mapper接口。可以是自定义的对象,也可以是一般数据类型。
#{?} : 接受mapper接口传递过来的参数,参数名要保持一致
1.2 对象传递: 使用#{属性} 获取的是属性值
1.2.1 Mapper接口
@Mapper //将接口交给Spring容器管理 Map<userMapper,JDK代理对象>
public interface UserMapper {
List<User> findUserByNA(User user);
}
1.2.2 xml映射文件
<!-- 根据name和age查询数据
知识点: 别名包
在配置文件中指定包路径: 可以自动的实现包路径的拼接
resultType规则:
1. 首先根据别名包匹配.设定..
2. 如果匹配不成功,则按照路径匹配.
参数传递的规则:
1. 如果是单个参数,则使用#{key} 获取的参数的值
2. 如果是对象参数,则使用#{属性} 获取的是属性值
-->
<select id="findUserByNA" resultType="User">
select * from demo_user where
name = #{name} and age = #{age}
</select>
1.3 集合传递: 传递的参数是Map, 则使用#{key}
1.3.1 Mapper接口
@Mapper //将接口交给Spring容器管理 Map<userMapper,JDK代理对象>
public interface UserMapper {
//习惯: 参数只有一个
List<User> findUserByAge(Map<String, Integer> map);
}
1.3.2 xml 映射文件
<!--
根据年龄查询数据
语法: 如果传递的参数是Map, 则使用#{key}
xml转译字符:
1. > >
2. < <
3. & &
-->
<select id="findUserByAge" resultType="User">
select * from demo_user where age > #{minAge}
and age < #{maxAge}
</select>
2. 转义标签
参考: 1.3的findUserByAge
<!--
根据年龄查询数据
语法: 如果传递的参数是Map, 则使用#{key}
xml转译字符:
1. > >
2. < <
3. & &
4. 万能转译字符
<![CDATA[ 转移内容 ]]>
-->
<select id="findUserByAge" resultType="User">
<![CDATA[
select * from demo_user
where age > #{minAge} and age < #{maxAge}
]]>
</select>
3. 注解@Param实现数据封装
作用:用注解来简化xml配置的时候(比如Mybatis的Mapper.xml中的sql参数引入),@Param注解的作用是给参数命名,参数命名后就能根据名字得到参数值,正确的将参数传入sql语句中(一般通过**#{}的方式,${}会有sql注入的问题)。
@Param是MyBatis所提供的(org.apache.ibatis.annotations.Param),作为Dao层的注解,作用是用于传递参数,从而可以与SQL中的的字段名相对应,一般在2=<参数数<=5时使用最佳。
当只有一个参数时,没什么好说的,传进去一个值也只有一个参数可以匹配。
当存在多个参数时,传进去的值就区分不开了,这时可以考虑用Map**,例如接口,缺点就在于可读性差,每次必须阅读他的键,才能明白其中的作用,并且不能限定其传递的数据类型,下面是使用@Param的情况
案例 1:@Param传递参数
mapper接口
@Repository
public interface UserMapper extends BaseMapper<User> {
List<User> findUserByAge(@Param("minAge") int minAge, @Param("maxAge") int maxAge);
}
xml文件
<select id="findUserByAge" resultType="com.hyang.mybatis.sys.entity.User">
select * from user where
age > #{minAge} and #{maxAge} > age
</select>
测试
@Test
public void findUserByAge() {
List<User> userList = userMapper.findUserByAge(22, 24);
userList.forEach(System.out::println);
}
运行
案例 2:@Param的重命名
mapper接口
@Repository
public interface UserMapper extends BaseMapper<User> {
List<User> findUserByAge1(@Param("mi") int minAge, @Param("ma") int maxAge);
}
xml文件
<select id="findUserByAge1" resultType="com.hyang.mybatis.sys.entity.User">
select * from user
<where>
<if test="mi!=null and mi !=''">and age > #{mi}</if>
<if test="ma!=null and ma !=''">and #{ma} > age</if>
</where>
</select>
测试
@Test
public void findUserByAge1() {
List<User> userList = userMapper.findUserByAge1(17, 24);
userList.forEach(System.out::println);
}
运行
4. 模糊查询
sql语法
select * from 表名 where 列名 like "%关键字%"
mybatis语法:动态拼接%
select * from 表名 where 列名 like "%"#{关键字}"%"
4.1 mapper接口
@Repository
public interface UserMapper extends BaseMapper<User> {
/**
* 根据名字里面的关键字查询用户信息
* @param userName
* @return
*/
List<User> findUserByLike(String userName);
}
4.2 xml文件
<!--模糊查询 特别注意表名的大小写问题!!!!!
windows系统中: 不区分大小写
Linux系统中: 区分大小写问题.
语法: "%" 这样的方式
-->
<select id="findUserByLike" resultType="com.hyang.mybatis.sys.entity.User">
select * from user where
name like "%"#{usernamr}"%"
</select>
4.3 测试
@Test
public void findUserByLike(){
List<User> userList=userMapper.findUserByLike("与海");
userList.forEach(System.out::println);
}
4.4 运行
5. sql标签用法
说明: Sql语句中经常出现重复的sql语句.如果每次重复的内容都自己手写.则开发的效率低.
优化: 将公共的Sql进行抽取
优势: Sql标签可以节省代码
缺点: 可读性变差了, 如果是关联操作 则根据情况而定.
Sql标签用法
mybatis语法
定义公共sql语句
<sql id="sql名">sql语句</sql>
调用
<include refid="sql名"/>
<!--模糊查询 特别注意表名的大小写问题!!!!!
windows系统中: 不区分大小写
Linux系统中: 区分大小写问题.
语法: "%" 这样的方式
-->
<select id="findUserByLike" resultType="User">
SELECT <include refid="tableColumn"/> FROM demo_user WHERE NAME LIKE "%"#{name}"%"
</select>
<!--Sql标签: 抽取公共的Sql语句 -->
<sql id="tableColumn">
id,name,age,sex
</sql>
6. mybatis中的集合参数写法 : foreach
关于Mybatis的遍历的写法
foreach:
1. collection 需要遍历的集合
1.1 数组 关键字: array/list
1.2 list集合 关键字: list/array
1.3 Map<key,array/list> 关键字:key
2. open/close 循环体的开始和结束 可以写到循环之外简化标签
3. item 当前遍历数据的变量名
4. separator 分割符
mapper接口
@Repository
public interface UserMapper extends BaseMapper<User> {
List<User> findListByIn(int[] arrays);
}
xml文件
<!--
关于Mybatis的遍历的写法
foreach:
1. collection 需要遍历的集合
1.1 数组 关键字: array/list
1.2 list集合 关键字: list/array
1.3 Map<key,array/list> 关键字:key
2. open/close 循环体的开始和结束 可以写到循环之外简化标签
3. item 当前遍历数据的变量名
4. separator 分割符
-->
<select id="findListByIn" resultType="User">
select * from user where id in (
<foreach
collection="array"
item="id"
separator=","
>
#{id}
</foreach>
)
</select>
测试
@Test
public void testFindListByIn(){
int[] array = {1,2,3,5,7};
List<User> userList = userMapper.findListByIn(array);
userList.forEach(System.out::println);
}
运行
7. 插入数据insert
略
三. 动态Sql
1. 动态Sql-where条件
1.1 mybatis语法
<where>
<if test="关键字 != null and 关键字 != '' "> ... </if>
...
</where>
1.2 mapper接口
@Repository
public interface UserMapper extends BaseMapper<User> {
List<User> findUserList(User user);
}
1.3 xml文件
<select id="findUserList" resultType="com.hyang.mybatis.sys.entity.User">
select * from user
<where>
<if test="id !=null and id!=''">and id = #{id}</if>
<if test="name !=null and name!=''">and name like "%"#{name}"%"</if>
<if test="age !=null and age!=''">and age = #{age}</if>
<if test="email !=null and email!=''">and email = #{email}</if>
</where>
</select>
1.4 测试
/**
* 案例1: 测试动态sql
* 查询18岁中姓'与'的人员信息
*/
@Test
public void testDemo1(){
User user = new User();
user.setAge(18).setName("与"); //动态变化的数据
List<User> userList = userMapper.findUserList(user);
userList.forEach(System.out::println);
}
1.5 运行
2. 动态Sql-set条件
<!--根据对象中不为null的元素,充当set条件.-->
<update id="updateUser">
update demo_user
<set>
<if test="name !=null">name = #{name},</if>
<if test="age !=null">age = #{age},</if>
<if test="sex !=null">sex = #{sex}</if>
</set>
where id = #{id}
</update>
3. 动态Sql-choose、when、otherwise
说明: 如果不想使用所有的条件可以使用choose 类似于java中的switch 语法:
<select id="findUserByNS" resultType="User">
select * from demo_user
<where>
<choose>
<when test="name !=null">
name = #{name}
</when>
<when test="age !=null">
age = #{age}
</when>
<otherwise>
sex = #{sex}
</otherwise>
</choose>
</where>
</select>
解读:
当name不等于空时:查询name。当name等于空而age不为空时,查询age。当都不满足时,查询sex。
4. trim标签的使用
四. mybatis封装返回值 结果集映射
1. resultType
2. resultMap
当表中的字段与POJO中的属性名称不一致时,需要使用resultMap的方式进行映射.
mybatis语法
<?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.jt.mapper.DogMapper">
<select id="findAll" resultMap="dogRM">
select * from dog
</select>
<resultMap id="dogRM" type="Dog">
<!--ID:代表主键-->
<id column="dog_id" property="dogId"/>
<!--结果集-->
<result column="dog_name" property="dogName"/>
<!--<result column="age" property="age"/>-->
</resultMap>
</mapper>
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class Dog implements Serializable {
private Integer dogId;
private String dogName;
private Integer age;
}
3. 开启驼峰规则映射
说明: 在业务中经常出现该现象. 字段 user_id 属性:userId 属性和字段有驼峰映射规则.但是采用resultType的方式进行映射.则不能正常赋值.
解决方案:
1.resultMap 繁琐.
2.开启驼峰映射规则
yml文件
mybatis:
#定义别名包
type-aliases-package: com.jt.pojo
#将所有的映射文件全部加载
mapper-locations: classpath:/mappers/*.xml
#开启驼峰映射
configuration:
map-underscore-to-camel-case: true
五. Mybatis 关联查询
1. 业务概述
1.1 常见关联关系
- 一对一 一个员工对应一个部门
- 一对多 一个部门对应多个员工
- 多对多
双向的一对多
1.一个老师对应多个学生
2.一个学生对应多个老师
1.2 创建数据表
说明: 一个员工emp对应一个部门dept
表设计如下:
1.emp表:
2. 部门表dept:
1.3 封装数据表
1.3.1 封装Emp对象
package com.jt.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class Emp implements Serializable {
private Integer id;
private String name;
private Integer age;
//关联关系: 一个员工对应一个部门
private Dept dept;
//private Integer deptId;
}
1.3.2 封装Dept对象
package com.jt.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.List;
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class Dept implements Serializable {
private Integer deptId;
private String deptName;
//关联 一个部门下有多个员工
private List<Emp> emps;
}
2. 关联查询Sql
2.1 笛卡尔积的方式 交集
特点: 只获取2张表的 交集
SELECT * FROM emp,dept
WHERE emp.dept_id = dept.dept_id
2.2 连接查询
分类: 1.左连接(LEFT JOIN)
2.内连接
3.右连接
/*左连接 emp表当作主表 */
SELECT * FROM
emp
LEFT JOIN
dept
ON
emp.dept_id = dept.dept_id
2.3 一对一封装 掌握
mapper接口
@Mapper
public interface EmpMapper {
/**
* 查询所有员工信息
*/
List<Emp> findAll();
}
xml文件
<!--
规则:
1.如果操作单表 一般使用resultType
2.如果进行关联操作,使用resultMap
3.如果是多表关联操作,则不允许出现重名字段.否则映射失败.
-->
<select id="findAll" resultMap="empRM">
SELECT emp.id,emp.name,emp.age,dept.dept_id,
dept.dept_name
FROM emp,dept
WHERE emp.dept_id = dept.dept_id
</select>
<!--
关于自动映射规则:
1.没有关联映射时: 如果属性和字段同名,则可以省略不写.
2.如果有关联映射: 则需要添加自动映射的开关autoMapping="true"
该注解只对当前对象有效 如果有多个对象,则需要添加多次
-->
<resultMap id="empRM" type="Emp" autoMapping="true">
<!--主键是必填项-->
<id column="id" property="id"/>
<!--
知识点:
1.一对一关联封装 association标签
2.必须指定属性的类型 javaType属性
3.autoMapping="true" 自动映射
-->
<association property="dept" javaType="Dept" autoMapping="true">
<!--主键必填项-->
<id column="dept_id" property="deptId"/>
<!--由于一起开启驼峰规则映射.所以下列的操作可以省略-->
<!--<result column="dept_name" property="deptName"/>-->
</association>
</resultMap>
2.4 一对多封装
2.4.1 关联说明
一个部门下,对应多个员工, 主对象是Dept
2.4.2 mapper接口
@Mapper
public interface DeptMapper {
/**
* 查询所有部门,及部门下的成员信息
*/
List<Dept> findAll();
}
2.4.3 xml映射文件
<select id="findAll" resultMap="deptRM">
SELECT dept.dept_name,emp.*
FROM dept,emp
WHERE emp.dept_id = dept.dept_id
</select>
<resultMap id="deptRM" type="Dept" autoMapping="true">
<!--主键必须标识-->
<id column="dept_id" property="deptId"/>
<!-- 一对多封装 固定搭配 ofType="" -->
<collection property="emps" ofType="Emp" autoMapping="true">
<id column="id" property="id"></id>
</collection>
</resultMap>
3. 总结
思想: 面向对象方式!!!
1.动态Sql: 不清楚用户到底传递了什么数据!!!
1.动态查询 2.动态更新 3.分支结构
关联关系
一对一/一对多/多对多(双向一对多)
resultMap 万能的数据封装的结构体系
一对一: association property=“dept” javaType=“Dept”
一对多: collection property=“emps” ofType=“Emp”
六. Mybatis 缓存讲解
1. 缓存说明
说明: 如果相同的数据需要多次查询,则可以使用缓存的方式处理,提高用户的响应速度.
2. mybatis缓存说明
mybatis中提供了2种缓存的机制.
一级缓存: SqlSession级别 在同一个sqlSession内(即同一个mapper接口方法)实现数据的共享 默认开启
二级缓存: SqlSessionFactory级别 由同一个sqlSessionFactory,生产的SqlSession 数据共享. 默认开启 + 配置
易错项:
3. 一级缓存测试
3.1 mapper接口
@Repository
public interface UserMapper extends BaseMapper<User> {
/**
* 测试mybatis缓存
* @return
*/
List<User> findCache1(String name);
}
3.2 xml映射
<select id="findCache1" resultType="User">
select * from user where name = "%"#{name}"%"
</select>
3.3 一级缓存测试
3.3.1 无控制事务
测试
@Test
//@Transactional //控制事务
public void findUserByAgeCache() {
List<User> userList1 = userMapper.findCache1("与");
List<User> userList2 = userMapper.findCache1("王");
List<User> userList3 = userMapper.findCache1("岳");
}
运行
结论
测试方法没有添加控制事务(@Transactional),底层mybatis创建了三次SqlSession,没有用mybatis缓存
3.3.2 事务控制,开启mybatis一级缓存
测试
@Test
@Transactional
public void findUserByAgeCache() {
List<User> userList1 = userMapper.findCache1("与");
List<User> userList2 = userMapper.findCache1("王");
List<User> userList3 = userMapper.findCache1("岳");
}
运行
结论
SqlSession公用同一个对象org.apache.ibatis.session.defaults.DefaultSqlSession@2d272b0d,开启mybatis一级缓存
测试mybatis的一级缓存
现象: 如果采用springBoot的方式进行测试时发现,sql执行多次. 一级缓存无效.
原因: springBoot整合mybatis之后,使用Mapper.find查询时.springBoot默认会开启多个sqlSession
解决方案: 添加事务注解@Transactional
知识讲解: springBoot中如果添加了事务注解,则默认采用同一个SqlSession
4. 二级缓存测试
二级缓存是建立了2个session以后执行相同的select语句后依然也不会再数据库执行select语句,而内容依然从缓存中读取出来。
4.1 测试策略
说明: 为了构建多个mapper对象.需要准备多个线程进行测试. 可以通过浏览器让用户发起多次请求.之后测试二级缓存是否有效.
层级代码结构:
- Controller 层 SpringMVC 面向接口编程 2:2:1
- Service 层 Spring
- Mapper/Dao层 Mybatis
4.2 测试准备
4.2.1 entity对象实现序列化
@SuppressWarnings("serial")
@TableName("user")
@Data
@Accessors(chain = true)
public class User implements Serializable {
...
4.2.2 xml映射文件添加cache标签
<?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.hyang.mybatis.sys.mapper.UserMapper">
<!--UserMapper:开启mybatis二级缓存-->
<cache/>
4.3 测试
4.3.1 xml映射方法
<select id="findCache1" resultType="User">
select * from user where name like "%"#{name}"%"
</select>
4.3.2 测试
@Autowired
private UserMapper userMapper;
@Autowired
private SqlSessionFactory sqlSessionFactory;
@Test
public void secondCache() {
List<User> userList = userMapper.findCache1("与");
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
List<User> userList1 = userMapper1.findCache1("与");
// 注意必须先关闭sqlsession,才会将session数据存入二级缓存中,然后新打开一个sqlsession才会命中缓存
sqlSession1.close();
List<User> userList2 = userMapper2.findCache1("与");
}
4.3.3 运行
Cache Hit Ratio : 缓存命中率
测试成功