1. 整合MyBatis-Plus
1)、什么是MyBatis-Plus
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
建议安装 MybatisX 插件
特点:
润物无声
只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑。
效率至上
只需简单配置,即可快速进行 CRUD 操作,从而节省大量时间。
丰富功能
热加载、代码生成、分页、性能分析等功能一应俱全。
2)、整合MyBatis-Plus
第一步:引入依赖
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
注意:引入 MyBatis-Plus 之后请不要再次引入 MyBatis,以避免因版本差异导致的问题。mysql依赖需要自己引入。
第二步:了解自动配置情况
- MybatisPlusAutoConfiguration 配置类,MybatisPlusProperties 配置项绑定。mybatis-plus:xxx 就是对mybatis-plus的定制
- SqlSessionFactory 自动配置好。底层是容器中默认的数据源
- mapperLocations 自动配置好的。有默认值。classpath*:/mapper/**/*.xml;任意包的类路径下的所有mapper文件夹下任意路径下的所有xml都是sql映射文件。 建议以后sql映射文件,放在 mapper下。
- 容器中也自动配置好了SqlSessionTemplate会话对象
- @Mapper 标注的接口也会被自动扫描;或者在主配置类上直接 添加@MapperScan(“com.igeek.springboot.mapper”) 批量扫描。
第三步:配置文件
在 application.properties或者yaml 配置文件中添加 MySQL 数据库的相关配置:
application.properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?unicode=true&characterEncode=utf8&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
application.yml
spring:
# 配置数据源的基础信息
datasource:
#JDBC属性配置
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?unicode=true&characterEncode=utf8&serverTimezone=GMT%2B8
username: root
password: root
注意:
1、这里的 url 使用了 ?serverTimezone=GMT%2B8 后缀,因为8.0版本的jdbc驱动需要添加这个后缀,否则运行测试用例报告如下错误:
java.sql.SQLException: The server time zone value ‘Öйú±ê׼ʱ¼ä’ is unrecognized or represents more
2、这里的 driver-class-name 使用了 com.mysql.cj.jdbc.Driver ,在 jdbc 8 中 建议使用这个驱动,否则运行测试用例的时候会有 WARN 信息
第四步:启动类
在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹
@SpringBootApplication
@MapperScan("com.igeek.springboot.mapper")
public class DemomptestApplication {
public static void main(String[] args) {
SpringApplication.run(DemomptestApplication.class, args);
}
}
第五步:实体类
创建包 entity 编写实体类 User.java(此处使用了 Lombok 简化代码)
表数据在官网上
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
第六步:mapper映射
创建包 mapper 编写Mapper 接口
UserMapper.java
@Repository
public interface UserMapper extends BaseMapper<User> {
}
第七步:service业务
创建包 service编写IService 接口UserService.java
public interface UserService extends IService<User> {
}
创建包 service编写UserServiceImpl实现类
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {
}
第八步:查看sql输出日志
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
第九步:测试
@SpringBootTest
class SpringbootCh05MybatisplusApplicationTests {
@Autowired
private UserService userService;
//查询列表
@Test
public void testSelectAll(){
//SELECT id,name,age,email FROM user
List<User> list = userService.list(null);
System.out.println(list);
}
}
3)、CRUD - 查询
1.查询
1.1 通过多个id批量查询
完成了动态sql的foreach的功能
//通过多个id批量查询
@Test
public void testSelectByIds(){
//SELECT id,name,age,email FROM user WHERE id IN ( ? , ? , ? )
List<User> list = userService.listByIds(Arrays.asList(1,2,3));
System.out.println(list);
}
1.2 简单的条件查询
通过map封装查询条件
注意:map中的key对应数据库中的列名。如:数据库user_id,实体类是userId,这时map的key需要填写user_id
//简单的条件查询
@Test
public void testSelectByMap(){
//Map集合的key一定要与表中字段一致
Map<String,Object> map = new HashMap();
map.put("name","Jone");
map.put("age",18);
//SELECT id,name,age,email FROM user WHERE name = ? AND age = ?
List<User> list = userService.listByMap(map);
System.out.println(list);
}
2.分页
2.1 分页插件
MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能
2.1.1 添加分页插件
配置类中添加@Bean配置
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConfig {
// 将分页插件加入至IOC容器中
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加内部拦截器
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInnerInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInnerInterceptor.setMaxLimit(-1L);
interceptor.addInnerInterceptor(paginationInnerInterceptor);
return interceptor;
}
}
2.1.2 测试selectPage分页
测试:最终通过page对象获取相关数据
//分页查询
@Test
public void testSelectPage() {
Page<User> page = new Page(1,3);
//返回对象得到分页所有数据
Page<User> userPage = userMapper.selectPage(page, null);
//总页数
long pages = userPage.getPages();
//当前页
long current = userPage.getCurrent();
//查询数据集合
List<User> records = userPage.getRecords();
//总记录数
long total = userPage.getTotal();
//下一页
boolean hasNext = userPage.hasNext();
//上一页
boolean hasPrevious = userPage.hasPrevious();
System.out.println(pages);
System.out.println(current);
System.out.println(records);
System.out.println(total);
System.out.println(hasNext);
System.out.println(hasPrevious);
}
2.1.3 测试selectMapsPage分页
当指定了特定的查询列时,希望分页结果列表只返回被查询的列,而不是很多null值
测试selectMapsPage分页:结果集是Map
@Test
public void testSelectMapsPage() {
//Page不需要泛型
Page<Map<String, Object>> page = new Page<>(1, 5);
Page<Map<String, Object>> pageParam = userMapper.selectMapsPage(page, null);
List<Map<String, Object>> records = pageParam.getRecords();
records.forEach(System.out::println);
System.out.println(pageParam.getCurrent());
System.out.println(pageParam.getPages());
System.out.println(pageParam.getSize());
System.out.println(pageParam.getTotal());
System.out.println(pageParam.hasNext());
System.out.println(pageParam.hasPrevious());
}
2.2 Controller控制层
//分页查询
@GetMapping("/dynamic_table")
public String findAllByPage(
@RequestParam(value="now",defaultValue="1") Integer pageNow,
Model model
){
//封装Page对象 第一个参数:当前页码 第二个参数:每页展示多少条记录
IPage page = new Page(pageNow,2);
//Service提供page来进行分页
IPage userPage = userService.page(page);
/**
* 将数据放至请求域
* records 记录列表
* total 总记录数
* size 每页显示条数
* current 当前页
* pages 总页数
*/
model.addAttribute("page",userPage);
return "table/dynamic_table";
}
2.3 页面
<tbody>
<tr class="gradeX" th:each="user : ${page.records}">
<td>[[${user.id}]]</td>
<td th:text="${user.name}">Internet</td>
<td th:text="${user.age}">Win 95+</td>
<td th:text="${user.email}">4</td>
<td>
<input type="button" class="btn btn-danger" value="删除" /> |
<input type="button" class="btn btn-warning" value="修改" />
</td>
</tr>
</tbody>
<div class="span6">
<div class="dataTables_info" id="dynamic-table_info">
总[[${page.pages}]]页数,总共[[${page.total}]]记录数,
当前第[[${page.current}]]页,每页显示[[${page.size}]]记录
</div>
</div>
<div class="dataTables_paginate paging_bootstrap pagination">
<ul>
<li class="prev disabled" th:class="${page.current eq 1}?'prev disabled':'prev'">
<a th:href="@{/dynamic_table(now=${page.current}-1)}">← Previous</a>
</li>
<li class="active" th:class="${now eq page.current}?'active':''"
th:each="now : ${#numbers.sequence(1,page.pages)}">
<a th:href="@{/dynamic_table(now=${now})}">[[${now}]]</a>
</li>
<li class="next" th:class="${page.current eq page.pages}?'next disabled':'next'">
<a th:href="@{/dynamic_table(now=${page.current}+1)}">Next → </a>
</li>
</ul>
</div>
4)、CRUD功能 - 添加
1.MP的主键策略
ASSIGN_ID 策略
MyBatis-Plus默认的主键策略是:ASSIGN_ID (使用了雪花算法)
@TableId(type = IdType.ASSIGN_ID)
private String id;
雪花算法:分布式ID生成器
雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性。
核心思想:
长度共64bit(一个long型)。
首先是一个符号位,1bit标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0。
41bit时间截(毫秒级),存储的是时间截的差值(当前时间截 - 开始时间截),结果约等于69.73年。
10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID,可以部署在1024个节点)。
12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID)。
优点:整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞,并且效率较高。
AUTO 自增策略
需要在创建数据表的时候设置主键自增
实体字段中配置 @TableId(type = IdType.AUTO)
@TableId(type = IdType.AUTO)
private Long id;
要想影响所有实体的配置,可以设置全局主键配置
#全局设置主键生成策略
mybatis-plus.global-config.db-config.id-type=auto
2.插入操作
//添加
@Test
public void testAdd() {
User user = new User();
user.setName("lucy");
user.setAge(20);
user.setEmail("1243@qq.com");
int insert = userMapper.insert(user);
System.out.println(insert);
}
5)、CRUD功能 - 删除
1.物理删除
1.1 根据id删除记录
@Test
public void testDeleteById(){
int result = userMapper.deleteById(5L);
system.out.println(result);
}
1.2 批量删除
@Test
public void testDeleteBatchIds() {
int result = userMapper.deleteBatchIds(Arrays.asList(8, 9, 10));
system.out.println(result);
}
1.3 简单条件删除
@Test
public void testDeleteByMap() {
HashMap<String, Object> map = new HashMap<>();
map.put("name", "Helen");
map.put("age", 18);
int result = userMapper.deleteByMap(map);
system.out.println(result);
}
Controller层
@Autowired
private UserService userService;
@GetMapping("/user/delete/{id}")
public String deleteUser(@PathVariable("id") Long id,
@RequestParam(value = "pn",defaultValue = "1")Integer pn,
RedirectAttributes ra){
userService.removeById(id);
ra.addAttribute("pn",pn);
return "redirect:/dynamic_table";
}
2.逻辑删除
2.1 物理删除和逻辑删除
**物理删除:**真实删除,将对应数据从数据库中删除,之后查询不到此条被删除数据
**逻辑删除:**假删除,将对应数据中代表是否被删除字段状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录
逻辑删除的使用场景:
可以进行数据恢复
有关联数据,不便删除
分析客户的行为
2.2 逻辑删除实现流程
2.2.1 数据库修改
添加 deleted字段
ALTER TABLE `user` ADD COLUMN `deleted` int DEFAULT 0;
2.2.2 实体类修改
添加deleted 字段,并加上 @TableLogic 注解
@TableLogic
private Integer deleted;
2.2.3 配置(可选)
application.properties 加入以下配置,此为默认值,如果你的默认值和mp默认的一样,该配置可无
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
2.2.4 测试
测试后发现,数据并没有被删除,deleted字段的值由0变成了1
测试后分析打印的sql语句,是一条update
注意:被删除前,数据的deleted 字段的值必须是 0,才能被选取出来执行逻辑删除的操作
@Test
public void testLogicDelete() {
int result = userMapper.deleteById(1L);
system.out.println(result);
}
2.2.5 测试逻辑删除后的查询
MyBatis Plus中查询操作也会自动添加逻辑删除字段的判断
@Test
public void testLogicDeleteSelect() {
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
6)、CRUD功能 - 修改(自动填充和乐观锁)
1.更新操作
注意:update时生成的sql自动是动态sql
UPDATE user SET age=? WHERE id=?
//修改
@Test
public void testUpdate() {
User user = new User();
user.setId(1340868235401764865L);
user.setName("lucymary");
int count = userMapper.updateById(user);
System.out.println(count);
}
2.自动填充
需求描述:
项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。
我们可以使用MyBatis Plus的自动填充功能,完成这些字段的赋值工作
1.1 数据库修改
在User表中添加datetime类型的新的字段 create_time、update_time
1.2 实体类修改
实体上增加字段并添加自动填充注解
//create_time
@TableField(fill = FieldFill.INSERT)
private Date createTime;
//update_time
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
1.3 实现元对象处理器接口
注意:不要忘记添加 @Component 注解
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
//mp执行添加操作,这个方法执行
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
//mp执行修改操作,这个方法执行
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
3、乐观锁
场景
主要适用场景:当要更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线程安全的数据更新
乐观锁实现方式:
1.取出记录时,获取当前version
2.更新时,带上这个version
3.执行更新时, set version = newVersion where version = oldVersion
4.如果version不对,就更新失败
4、乐观锁实现流程
4.1 修改实体类
添加 @Version 注解
@Version
private Integer version;
4.2 创建配置文件
创建包config,创建文件MybatisPlusConfig.java
此时可以删除主类中的 @MapperScan 扫描注解
@Configuration
public class MyConfig {
// 将插件加入至IOC容器中
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加乐观锁插件
OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor = new OptimisticLockerInnerInterceptor();
interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor);
return interceptor;
}
}
7)、条件构造器和常用接口
1.wrapper介绍
Wrapper : 条件构造抽象类,最顶端父类
AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
QueryWrapper : 查询条件封装
UpdateWrapper : Update 条件封装
AbstractLambdaWrapper : 使用Lambda 语法
LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
LambdaUpdateWrapper : Lambda 更新封装Wrapper
@SpringBootTest
public class QueryWrapperTests {
@Autowired
private UserMapper userMapper;
}
2.测试用例
2.1 ge、gt、le、lt、isNull、isNotNull
@Test
public void testQuery() {
QueryWrapper<User>queryWrapper = newQueryWrapper<>();
queryWrapper.isNull("name").ge("age", 12).isNotNull("email");
int result = userMapper.delete(queryWrapper);
System.out.println("delete return count = " + result);
}
2.2
注意:seletOne()返回的是一条实体记录,当出现多条时会报错
@Test
public void testSelectOne() {
QueryWrapper<User>queryWrapper = newQueryWrapper<>();
queryWrapper.eq("name", "Tom");
Useruser = userMapper.selectOne(queryWrapper);
//只能返回一条记录,多余一条则抛出异常
System.out.println(usr);
}
2.3 between、notBetween
包含大小边界
@Test
public void testSelectCount() {
QueryWrapper<User>queryWrapper = newQueryWrapper<>();
queryWrapper.between("age", 20, 30);
Integer count = userMapper.selectCount(queryWrapper);
//返回数据数量
System.out.println(count);
}
2.4 like、notLike、likeLeft、likeRight
selectMaps()返回Map集合列表,通常配合select()使用
@Test
public void testSelectMaps() {
QueryWrapper<User>queryWrapper = newQueryWrapper<>();
queryWrapper.select("name", "age").like("name", "e").likeRight("email", "5");
List<Map<String, Object>>maps = userMapper.selectMaps(queryWrapper);
//返回值是Map列表
maps.forEach(System.out::println);
}
2.5 orderBy、orderByDesc、orderByAsc
@Test
public void testSelectListOrderBy() {
QueryWrapper<User>queryWrapper = newQueryWrapper<>();
ueryWrapper.orderByDesc("age", "id");
List<User>users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
3.查询方式
查询方式 | 说明 |
---|---|
setSqlSelect | 设置 SELECT 查询字段 |
where | WHERE 语句,拼接 + WHERE 条件 |
and | AND 语句,拼接 + AND 字段=值 |
andNew | AND 语句,拼接 + AND (字段=值) |
or | OR 语句,拼接 + OR 字段=值 |
orNew | OR 语句,拼接 + OR (字段=值) |
eq | 等于= |
allEq | 基于 map 内容等于= |
ne | 不等于<> |
gt | 大于> |
ge | 大于等于>= |
lt | 小于< |
le | 小于等于<= |
like | 模糊查询 LIKE |
notLike | 模糊查询 NOT LIKE |
in | IN 查询 |
notIn | NOT IN 查询 |
isNull | NULL 值查询 |
isNotNull | IS NOT NULL |
groupBy | 分组 GROUP BY |
having | HAVING 关键词 |
orderBy | 排序 ORDER BY |
orderAsc | ASC 排序 ORDER BY |
orderDesc | DESC 排序 ORDER BY |
exists | EXISTS 条件语句 |
notExists | NOT EXISTS 条件语句 |
between | BETWEEN 条件语句 |
notBetween | NOT BETWEEN 条件语句 |
addFilter | 自由拼接 SQL |
last | 拼接在最后,例如:last(“LIMIT 1”) |