springBoot是帮助我们快速开发spring程序的
mybatisPlus是帮助我们快速开发mybatis程序的
MyBatisPlus简称MP
MyBatisPlus环境搭建的步骤?
先创建空白Project作为容器,mybatisplus所有工程都创建成其下面的子module
直接创建boot工程
②:选择当前模块需要使用的技术集(仅保留JDBC)
勾选功能:
pom.xml里
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.1version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.16version>
dependency>
注意事项1:由于MP(MybatisPlus)并未被收录到idea的系统内置配置,无法直接选择加入
注意事项2:如果使用Druid数据源,需要导入对应坐标
(类名与表名对应,属性名与字段名对应)
create database if not exists mybatisplus_db character set utf8;
use mybatisplus_db;
CREATE TABLE user (
id bigint(20) primary key auto_increment,
name varchar(32) not null,
password varchar(32) not null,
age int(3) not null ,
tel varchar(32) not null
);
insert into user values(null,'tom','123456',12,'12345678910');
insert into user values(null,'jack','123456',8,'12345678910');
insert into user values(null,'jerry','123456',15,'12345678910');
insert into user values(null,'tom','123456',9,'12345678910');
insert into user values(null,'snake','123456',28,'12345678910');
insert into user values(null,'张益达','123456',22,'12345678910');
insert into user values(null,'张大炮','123456',16,'12345678910');
pom.xml里导入lombok
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
resources下application.properties文件重命名为application.yml
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC
username: root
password: root
MP提供了BaseMapper类, 继承它,那么基本的crud方法直接就有了~
(就是之前Hibernate里面抽取的BaseDao)
package com.itheima.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.domain.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserDao extends BaseMapper<User> {
}
dao就开发完了,所有的基础crud功能都有了,抽取BaseDao的好处啊~
src/test/java/cn/whu/Mybatisplus01QuickstartApplicationTests.java 里写测试
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
UserDao userDao;
@Test
void testUserDao() {
List<User> users = userDao.selectList(null);
System.out.println(users);
}
}
运行结果:
2023-03-29 22:43:53.643 INFO 8492 --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
[User(id=1, name=tom, password=123456, age=12, tel=12345678910), User(id=2, name=jack, password=123456, age=8, tel=12345678910), User(id=3, name=jerry, password=123456, age=15, tel=12345678910), User(id=4, name=tom, password=123456, age=9, tel=12345678910), User(id=5, name=snake, password=123456, age=28, tel=12345678910), User(id=6, name=张益达, password=123456, age=22, tel=12345678910), User(id=7, name=张大炮, password=123456, age=16, tel=12345678910)]
通过入门案例制作,MyBatisPlus的优点有哪些?
MyBatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提高效率
官网:https/mybatis.plus/ 或者 https://mp.baomidou.com/
两个域名,第一个之前就被网友注册了,后来热心网友捐给他了,笑死~
网站竟然是中文的?没错,就是中国人开发的,振奋人心啊~ 也理解了baomidou,拼音域名了,哈哈哈
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
UserDao userDao;
@Test
void testGetById(){
Long id = 1l;
User user = userDao.selectById(id);
System.out.println(user);
// User(id=1, name=tomcat, password=123456, age=12, tel=12345678910)
// 果然能查到
}
@Test
void testUpdate(){
User user = new User();
user.setId(1l);
user.setName("tomcat");
userDao.updateById(user);
//查数据库 name真的变成 tomcat了
//有值修改 null值不变 //想想如何实现? 1、动态sql 2、先根据id查一下,将user的null属性补上原来的值
}
@Test
void testDel(){
Long id = 1641093882043678722l;
userDao.deleteById(id);
//查看数据库,确实删除了
}
@Test
void testSave(){
User user = new User(null,"张三","123",13,"13912345678");
userDao.insert(user);
//查数据库确实增加了 //id还是uuid,真不错~
}
@Test
void testUserDao() {//GetAll
List<User> users = userDao.selectList(null);
System.out.println(users);
}
}
有什么简单的办法可以自动生成实体类的GET、SET方法?
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.12version>
<scope>providedscope>
dependency>
import lombok.*;
/*
1 生成getter和setter方法:@Getter、@Setter
生成toString方法:@ToString
生成equals和hashcode方法:@EqualsAndHashCode
2 统一成以上所有:@Data
3 生成空参构造: @NoArgsConstructor
生成全参构造: @AllArgsConstructor
4 lombok还给我们提供了builder的方式创建对象,好处就是可以链式编程。 @Builder【扩展】
*/
@Data
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
思考一下Mybatis分页插件是如何用的?
①:设置分页拦截器作为Spring管理的bean
分页查询其实就是查询全部的基础上增强一点功能(最后加上limit),所以要么AOP要么拦截器,MP是通过拦截器实现的,所以得回归原始配置类,写MP的拦截器了,并将其放到IOC容器中给Spring管理
如果不写,分页查询方法就退化为查询所有方法了
新建类: cn.whu.config.MyBatisPlusConfig
package com.itheima.config;
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 ,只要此文件能被扫描到,就能在IOC容器中创建它
//否则就得启动类XXXApplications上写@Import(MybatisPlusConfig.class)也行
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//1.定义MP拦截器
MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
// 2.添加具体的拦截器 (这里加的就是MP内置的分页拦截器)
mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mpInterceptor;
}
}
忍不住创建为模板类
②:执行分页查询
cn.whu.Mybatisplus01QuickstartApplicationTests 测试类里面接着写
//分页查询
@Test
void testGetByPage(){
//1. 创建IPage分页对象,设置分页参数
//注意页码从1开始 (所有小于1的非法页码值都会被处理成1)
// 泛型不写也行写了方便点
IPage<User> page = new Page(2,2);//直接可以设置第几页 这页(每页)多少条
//2. 执行分页查询
userDao.selectPage(page, null);//查询参数先设置为null //其实返回值也是page
//3. 获取分页结果
System.out.println(page.getRecords());//直接就存储到page里面了
System.out.println("当前页码值: "+page.getCurrent());
System.out.println("每页显示数据条数: "+page.getSize());
System.out.println("一共多少页: "+page.getPages());
System.out.println("一共多少条数据: "+page.getTotal());
System.out.println("实际数据: "+page.getRecords());
}
再下面两步(3.3、3.4)非必要,调试时才可能需要
配置打印MybatisPlus的sql语句 (就是看它的日志)
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC
username: root
password: root
# 开启mp的日志(输出到控制台)
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
重新查询:查看日志
@Test
void testGetByPage2(){
IPage<User> page = new Page(2,3);//查询具有一般性的一页
userDao.selectPage(page, null);
System.out.println("实际数据: "+page.getRecords());
}
做法:在resources下新建一个logback.xml文件,名称固定,内容如下:
<configuration>
configuration>
关于logback参考播客:https://www.jianshu.com/p/75f9d11ae011
再执行只有MP日志了,没有Spring日志了
不过其实点具体的方法,效果差不多
resources下application.yml里
spring:
main:
banner-mode: off # 关闭SpringBoot启动图标(banner)
# mybatis-plus日志控制台输出
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
banner: off # 关闭mybatisplus启动图标
trick: 这两个配置都叫banner, yml里写banner,就会出现代码提示,选择即可
此时完整的yml配置:
server:
port: 80
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC
username: root
password: 1234
main:
banner-mode: off # 关闭SpringBoot启动图标(banner)
# 开启MP日志(控制台输出,其中有sql语句)
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
banner: off # 关闭mybatisplus启动图标
和前面一样,新建一个一样的boot程序,初始操作也完全一样
pom.xml添加依赖:
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.1version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.16version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
@Mapper // 肯定要IOC创建代理对象啊
public interface UserDao extends BaseMapper<User> {
}
User
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
application.yml
server:
port: 80
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC
username: root
password: 1234
main:
banner-mode: off # 关闭SpringBoot启动图标(banner)
# 开启MP日志(控制台输出,其中有sql语句)
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
banner: off # 关闭mybatisplus启动图标
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
</configuration>
Mybatisplus02DqlApplicationTests
就剩下一个测试方法了:
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
UserDao userDao;
@Test
void testGetAll() {
List<User> users = userDao.selectList(null);
System.out.println(users);
}
}
分页查询用不着,就不配那个拦截器了
此案例的初始环境,搭建完毕!
Wrapper是个接口,我们用其实现类完成按条件查询,DQL对应的实现类是QueryWrapper、LambdaQueryWrapper
//方式一:按条件查询
@Test
void testGetAll() {
//按条件查询
QueryWrapper qw = new QueryWrapper();
qw.lt("age",18);//where age<18
List<User> users = userDao.selectList(qw);
for (User user : users) {
System.out.println(user);
}
}
"age"容易写错,所以就出现了lambda格式
注意:只是还是QueryWrapper类,所以写法比较复杂
//方式二:lambda格式按条件查询
@Test
void testGetAll2() {
//方式一:按条件查询
//这种写法必须指定泛型了
QueryWrapper<User> qw = new QueryWrapper<User>();
qw.lambda().lt(User::getAge,10);//where age<10
List<User> users = userDao.selectList(qw);
for (User user : users) {
System.out.println(user);
}
}
注意:此时直接换用LambdaQueryWrapper类了,写法简单多了
//方式三:lambda格式按条件查询
@Test
void testGetAll2() {
//方式三:按条件查询 LambdaQueryWrapper实现类
//这种写法必须指定泛型了
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(User::getAge,10);//where age<10
List<User> users = userDao.selectList(lqw);
for (User user : users) {
System.out.println(user);
}
}
感想:还是Hibernate那一套,不写sql语句,只写java方法,毕竟java程序嘛
//并且关系
@Test
void testGetAll3() {
// 多条件查询方一:直接设置多个条件
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(User::getAge,30);//where age<30
lqw.gt(User::getAge,20);//where age>20
List<User> users = userDao.selectList(lqw);
for (User user : users) {
System.out.println(user);
}
}
条件多了会很难看,这个时候就该链式编程出马了
//并且关系
@Test
void testGetAll4() {
// 多条件查询方式2:链式编程
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(User::getAge,30).gt(User::getAge,20);//where age<30 and age>20
List<User> users = userDao.selectList(lqw);
for (User user : users) {
System.out.println(user);
}
}
//或者关系
@Test
void testGetAll5() {
// 多条件查询:or
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
// 中间链接一个or就行了
lqw.gt(User::getAge,30).or().lt(User::getAge,10);//where age>30 or age<10
List<User> users = userDao.selectList(lqw);
for (User user : users) {
System.out.println(user);
}
}
非链式这么写,肯定不推荐:
@Test
void testGetAll5_1() {
// 多条件查询:or
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.gt(User::getAge,30);
lqw.or().lt(User::getAge,10);//where age>30 or age<10
List<User> users = userDao.selectList(lqw);
for (User user : users) {
System.out.println(user);
}
}
先定义个辅助查询类,专门封装用户前端指定的查询条件参数
cn.whu.domain.query.UserQuery
/**
* 专门用于封装User对象查询条件的辅助类
*/
@Data
public class UserQuery extends User {//想用其属性 直接继承呗
private Integer age2;//作为查询条件的上限而存在
}
@Test
void testGetAll6() {
//模拟页面传递过来的查询数据
UserQuery uq = new UserQuery();
//uq.setAge(10);//模拟用户没有设置下限
uq.setAge2(20);//模拟用户设置上限的数据
// null判定
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
if(null!=uq.getAge()){
lqw.gt(User::getAge,uq.getAge());//age>xx
}
if(null!=uq.getAge2()){
lqw.lt(User::getAge,uq.getAge2());//age
}
List<User> users = userDao.selectList(lqw);
users.forEach(System.out::println);//就是每个对象输出一行
}
万一条件参数很多,代码中会出现大量的if判断,咋办?
@Test
void testGetAll7() {
//模拟页面传递过来的查询数据
UserQuery uq = new UserQuery();
//uq.setAge(10);//模拟用户没有设置下限
uq.setAge2(20);//模拟用户设置上限的数据
// null判定
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.gt(null!=uq.getAge(),User::getAge, uq.getAge());//第一个参数为true, 条件才会加上 age>xx
lqw.lt(null!=uq.getAge2(),User::getAge, uq.getAge2());//第一个参数为true, 条件才会加上 age
List<User> users = userDao.selectList(lqw);
users.forEach(System.out::println);//就是每个对象输出一行
}
链太长,其实就不建议这么写了
@Test
void testGetAll8() {
//模拟页面传递过来的查询数据
UserQuery uq = new UserQuery();
//uq.setAge(10);//模拟用户没有设置下限
uq.setAge2(20);//模拟用户设置上限的数据
// null判定
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//这条链有点长 不建议这么写
lqw.gt(null!=uq.getAge(),User::getAge, uq.getAge())
.lt(null!=uq.getAge2(),User::getAge, uq.getAge2());//第一个参数为true, 条件才会加上
List<User> users = userDao.selectList(lqw);
users.forEach(System.out::println);//就是每个对象输出一行
}
只查询部分字段,查出来的每行数据都有少了一些相同的列,就像投影一样
其实查询出来看到的结果都叫投影
@Test
void testGetAll9() {
//Lambda格式
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.select(User::getId,User::getName,User::getAge);//只看这三个字段
List<User> users = userDao.selectList(lqw);
users.forEach(System.out::println);
}
@Test
void testGetAll10() {
//普通格式 : 只能写字段名了
QueryWrapper<User> lqw = new QueryWrapper<User>();
qw.select("id","name","age");//只看这三个字段
List<User> users = userDao.selectList(qw);
users.forEach(System.out::println);
}
没查的字段全部为null了
这里只能用QueryWrapper不能用LambdaQueryWrapper了
因为LambdaQueryWrapper只能列出POJO中 有属性对应的字段
selectMaps
@Test
void testGetAll11() {
QueryWrapper<User> qw = new QueryWrapper<User>();
//查询结果包含模型类中未定义的属性
qw.select("count(*) as count, tel"); // 和sql语句一样写 也可以取别名 as也可以省略
List<Map<String, Object>> maps = userDao.selectMaps(qw);//换一个API 直接返回map,key是查询表达式,value是对应的值
System.out.println(maps);
}
运行结果
[{count=8, tel=12345678910}]
再加个分组玩玩
@Test
void testGetAll12() {
QueryWrapper<User> qw = new QueryWrapper<User>();
//查询结果包含模型类中未定义的属性
// 1)写逻辑字段
qw.select("count(*) as count, tel");
// 加分组条件
qw.groupBy("tel");
// 2)用selectMaps函数
List<Map<String, Object>> maps = userDao.selectMaps(qw);
System.out.println(maps);
}
运行结果
{count=7, tel=12345678910}
{count=1, tel=13912345678}
多条件查询有哪些组合?
查一条数据用selectOne
eg: where name = “张三” and password = “123”;
@Test
void testGetByIdAndPwd() {
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
// where name = "张三" and password = "123";
lqw.eq(User::getName,"张三").eq(User::getPassword,"123");
// 查一条数据用selectOne
User user = userDao.selectOne(lqw);
System.out.println(user);
//User(id=1641093548684476417, name=张三, password=123, age=13, tel=13912345678)
}
@Test
void testGetByBetween() {
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
// 范围查询:lt gt le ge eq between
lqw.between(User::getAge,10,20);//有between直接做简单范围查询
List<User> users = userDao.selectList(lqw);
users.forEach(System.out::println);
}
1、直接like, ‘%’左右都加
@Test
void testGetByLike() {
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
lqw.like(User::getName,"张");
List<User> users = userDao.selectList(lqw);
users.forEach(System.out::println);
}
==> Preparing: SELECT id,name,password,age,tel FROM user WHERE (name LIKE ?)
==> Parameters: %张%(String)
2、likeLeft, ‘%’加左边
@Test
void testGetByLikeLeft() {
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
lqw.likeLeft(User::getName,"张");//%张
List<User> users = userDao.selectList(lqw);
users.forEach(System.out::println);
}
==> Preparing: SELECT id,name,password,age,tel FROM user WHERE (name LIKE ?)
==> Parameters: %张(String)
3、likeRight, ‘%’加右边
@Test
void testGetByLikeRight() {
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
lqw.likeRight(User::getName,"张");//张%
List<User> users = userDao.selectList(lqw);
users.forEach(System.out::println);
}
==> Preparing: SELECT id,name,password,age,tel FROM user WHERE (name LIKE ?)
==> Parameters: 张%(String)
QueryWrapper<User> qw = new QueryWrapper<User>();
qw.select("gender","count(*) as nums");
qw.groupBy("gender");
List<Map<String, Object>> maps = userDao.selectMaps(qw);
System.out.println(maps);
题目:基于MyBatisPlus_Ex1模块,完成Top5功能的开发。
说明:
①:Top5指根据销售量排序(提示:对销售量进行降序排序)
②:Top5是仅获取前5条数据(提示:使用分页功能控制数据显示数量)
思考表的字段和实体类的属性不对应,查询会怎么样?
完全不写mybatis的mapper.xml配置文件了,直接POJO里写,个人感觉这么写好很多,那就这么写
之前之所以没有这些问题,是因为我们的表名、字段名和Model类的类名、属性名,完全一样的
接下来修改表结构:
alter table user rename to tbl_user;
alter table tbl_user change password pwd varchar(32) not null; -- 注意上面执行完表名就变了
再执行之前的查询代码就报错了
修改POJO后再执行代码,又OK了
@Data
@TableName("tbl_user")
public class User {
/*
id为Long类型,因为数据库中id为bigint类型,
并且mybatis有自己的一套id生成方案,生成出来的id必须是Long类型
*/
private Long id;
private String name;
@TableField(value = "pwd",select = false)
private String password;
private Integer age;
private String tel;
@TableField(exist = false) //表示online字段不参与CRUD操作
private Boolean online;
}
少一个注解都会报错,要么user表不存在,要么password字段不存在,要么online字段不存在
其实就相当于将上面的工程mybatisplus_02_dql复制一份为mybatisplus_03_dml,然后接着做
直接复制吧,除了项目名,pom.xml里也要修改名称,mybatisplus_02_dql.iml删了,然后刷新maven,执行任意代码就会重新生成的
domain下的query包删了,ApplicationTests里面的方法都删了,然后写3个分别增删改的方法
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
UserDao userDao;
@Test
void testSave() {
User user = new User();//有了online字段 全参数方法也不好用了,非得给他加上吗?
user.setName("whu");
user.setPassword("456");
user.setAge(130);
user.setTel("110");
userDao.insert(user);
}
@Test
void testDelById(){
Long id =1641451444677042177l;
userDao.deleteById(id);
}
@Test
void testUpdateById(){
User user = new User();
user.setId(1l);
user.setName("tom666");
userDao.updateById(user);//updateById不是update 否则全变成tom666了
}
}
主键生成的策略有哪几种方式?
不同的表应用不同的id生成策略
名称:@TableId
类型:属性注解
位置:模型类中用于表示主键的属性定义上方
作用:设置当前类中主键属性的生成策略
相关属性
type:设置主键属性的生成策略,值参照IdType枚举值
以后都用默认的,不指定就是默认,默认就是ASSIGN_ID,雪花算法,同时兼容整数和字符串类型
雪花算法,它至少有如下4个优点:
1.系统环境ID不重复
能满足高并发分布式系统环境ID不重复,比如大家熟知的分布式场景下的数据库表的ID生成。
2.生成效率极高
在高并发,以及分布式环境下,除了生成不重复 id,每秒可生成百万个不重复 id,生成效率极高。
3.保证基本有序递增
基于时间戳,可以保证基本有序递增,很多业务场景都有这个需求。
4.不依赖第三方库
不依赖第三方的库,或者中间件,算法简单,在内存中进行。
雪花算法,有一个比较大的缺点:
依赖服务器时间,服务器时钟回拨时可能会生成重复 id。
代码演示:
然后修改当前主键自增的值:
再执行ApplicationTest测试类里的save方法
果然就自增为8了
手动配置成雪花算法,就恢复默认了:
再执行2次save方法,奇怪的id又回来了:
问题:万一想换一个主键生成策略,每个实体类主键都要写一个重复的注解,好麻烦。
所有表明都是POJO类名加上一个相同的前缀 tbl_ 这样每个实体类都要配置一个@TableName注解,也好麻烦
解决: 全局配置
mybatis-plus:
global-config:
db-config:
id-type: assign_id
table-prefix: tbl_
上面yml中的配置
id-type: assign_id
, 相当于给每个pojo实体类的主键字段上都加上了@TableId(type = IdType.ASSIGN_ID)
这个注解,效果如下图:
上面yml中的配置
table-prefix: tbl_
, 相当于给每个pojo实体类上都加上了@TableName("tbl_user")
这个注解(user是自适应表名),效果如下图:
注释掉POJO类里面的注解:
改用全局配置:
再去执行ApplicationTest方法,仍然可以执行。
MyBatisPlus是否支持批量操作?
int deleteBatchIds(@Param(“coll”) Collection extends Serializable> idList);
int deleteBatchIds(Collection idList)
//删除指定多条数据
@Test
void testDelByIds(){
ArrayList<Long> list_id = new ArrayList<>();
list_id.add(1641456685967360001l);
list_id.add(1641456810840256513l);
list_id.add(1642013510106222593l);
list_id.add(1642015240697143298l);
userDao.deleteBatchIds(list_id);
}
当然也能按照多Id查询啦,了解一下
ListselectBatchIds(@Param(“coll”) Collection extends Serializable> idList);
ListselectBatchIds(Collection idList);
//查询指定多条数据
@Test
void testGetByIds(){
ArrayList<Long> list_id = new ArrayList<>();
list_id.add(1l);
list_id.add(2l);
list_id.add(3l);
list_id.add(4l);
List<User> users = userDao.selectBatchIds(list_id);
users.forEach(System.out::println);
}
在实际环境中,如果想删除一条数据,是否会真的从数据库中删除该条数据?
删除操作业务问题:业务数据从数据库中丢弃
逻辑删除:为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,数据保留在数据库中
-- 添加逻辑删除字段: 默认值0 表示未被删除
alter table tbl_user add deleted int(1) default 0;
//@TableName("tbl_user") // 改为全局配置了
public class User {
//@TableId(type = IdType.ASSIGN_ID)//其实默认就是雪花算法 不用配 // 改为全部配置了 (其实可以不配)
private Long id;
private String name;
@TableField(value = "pwd",select = false)
private String password;
private Integer age;
private String tel;
@TableField(exist = false)
private Integer online;
// 逻辑删除字段:1标记当前记录被删除 0标记未被删除
@TableLogic(value = "0",delval = "1")
private Integer deleted;
}
点进注解,就可以看到很清楚的中文注释
没有注释,点下上方提示的download source即可
执行删除操作
@Test
void testDelById(){
Long id =1l;
userDao.deleteById(id);
}
@Test
void testGetById(){
Long id =1l;
User user = userDao.selectById(id);
System.out.println(user);
}
现在还想查逻辑删除的记录咋办? 自己写sql就ok啦
@Mapper // 肯定要IOC创建代理对象啊
public interface UserDao extends BaseMapper<User> {
@Select("select * from tbl_user where id = #{id}") //这条sql语句肯定不会带and deleted = 1条件了
User getByIdUser(Integer id);
}
@Test
void testGetById3(){
User user = userDao.getByIdUser(1l);
System.out.println(user);
}
大量表需要逻辑删除,一个个写麻烦,万一某一个写错了,写反了,很难查错,不如yml里做个全局配置
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
# 逻辑删除字段名
logic-delete-field: deleted
# 逻辑删除字面值:未删除为0
logic-not-delete-value: 0
# 逻辑删除字面值:删除为1
logic-delete-value: 1
@Test
void testDelById(){
Long id =2l;
userDao.deleteById(id);
}
逻辑删除本质:逻辑删除的本质其实是修改操作。如果加了逻辑删除字段,查询数据时也会自动带上逻辑删除字段。
乐观锁主张的思想是什么?
只剩下1个的时候,不能秒杀卖出超过1个,也就不能卖出-1,-2,-10号商品,或者说,同一个商品不能被并发同时读到卖出多次,简单的资源OS同步互斥问题,生产者-消费者问题
-- 添加锁标记字段: 默认值设为1
alter table tbl_user add version int(11) default 1;
import com.baomidou.mybatisplus.annotation.Version;
public class User {
private Long id;
//... 其他字段省略
@Version
private Integer version;
}
实现原理: update set xxx = x, version = version+1 where version = 1
执行sql语句时,加上 version = version+1 where version = version+1 操作,然后自动锁表能保证数据库的修改操作是互斥的,多个请求秒杀一个商品记录时就只会有一个能秒杀成功了。
.
这些操作具体怎么加呢?答:拦截器
之前分页拦截那里,再加一行代码即可,也即再加一个乐观锁拦截器
不要小看拦截器,它才是真正干活儿的(我们没写具体怎么加,因为MP都帮我们写好了)
@Configuration //此类能被扫描到 此注解就会将其创建到IOC容器中
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mpInterceptor() {
// 1.定义MP拦截器
MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
// 2.添加具体的拦截器 (这里加的就是MP内置的分页拦截器)
//mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
// 3.添加乐观锁的拦截器 (加上之后MP就会自动帮我们添加sql操作,保证千量级的秒杀正常执行了)
mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mpInterceptor;
}
}
@Test
void testUpdateById2Version(){
User user = new User();
user.setId(3l);//注意挑一个没有被逻辑删除的记录
user.setName("jerry111");
user.setVersion(1);//注意必须指定version值
userDao.updateById(user);
}
每次都要setVersion,好麻烦,肯定有标准操作
标准操作:先查再改
@Test
void testUpdateById3Version(){
User user = userDao.selectById(3l);//先查 (注意要查没有被逻辑删除的记录)
user.setName("tom is a cat");//再直接修改要修改的字段值
userDao.updateById(user);//这个user里肯定有version了
}
如何实现加锁的呢
// 模拟秒杀时:A.B两个用户同时想修改同一条数据
@Test
void testUpdateById4Version(){
User userA = userDao.selectById(3l);//A用户进来 version = 3
User userB = userDao.selectById(3l);//B用户进来 version = 3 //也是3 秒杀导致的二者读到完全相同的数据
userB.setName("jerryB");
userDao.updateById(userB);// B用户先修改记录 version => 4
userA.setName("jerryA");
userDao.updateById(userA); // A用户也要修改记录 where version=3 条件不成立了 (A就没抢到)
}
框架还得保证多个update操作是互斥的才行,也就是2条update sql语句,必定是前一条执行完毕,下一条才能开始执行。
所幸:当一个语句对表执行update,delete的时候根据条件该表就会锁定,所以我们经常说锁表,只有这条语句执行完提交或者回滚的时候第二条语句才会执行。
mysql会自动锁表,所以不用担心啦,完全可行
如果只给一张表的字段信息,能够推演出Domain、Dao层的代码?
如果只给一张表的字段信息,能够推演出Domain、Dao层的代码?
答:MP提供好模板,我们开发者将空填好,自然就能自动生成代码了呀
pom.xml先添加2个MP必要依赖
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.1version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.16version>
dependency>
初始环境搭建完毕
第一步:添加代码生成器相关依赖
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-generatorartifactId>
<version>3.4.1version>
dependency>
<dependency>
<groupId>org.apache.velocitygroupId>
<artifactId>velocity-engine-coreartifactId>
<version>2.3version>
dependency>
注意添加完刷新一下maven
第二步:编写代码生成器类
cn.whu.Generator
public class Generator {
public static void main(String[] args) {
//1. 创建代码生成器对象,执行生成代码操作
AutoGenerator autoGenerator = new AutoGenerator();
//2. 数据源相关配置:读取数据库中的信息,根据数据库表结构生成代码
DataSourceConfig dataSource = new DataSourceConfig();
dataSource.setDriverName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("1234");
autoGenerator.setDataSource(dataSource);
//autoGenerator.setTemplate()//不设置就是使用内置模板
//autoGenerator.setTemplateEngine()//一般这两个API都用不上,因为自定义模板工作量太大
//3. 执行生成操作
autoGenerator.execute();
}
}
执行完毕,会弹出一个窗口目录,目录下有一个com子目录,下面就有自动生成的一系列代码
15:02:40.089 [main] DEBUG com.baomidou.mybatisplus.generator.engine.AbstractTemplateEngine - 模板:/templates/controller.java.vm;
文件:D://\com\baomidou\controller\Tbl_userController.java
15:02:40.093 [main] DEBUG com.baomidou.mybatisplus.generator.AutoGenerator - ==========================文件生成完成!!!==========================
package com.baomidou.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
/**
*
*
*
*
* @author ${author}
* @since 2023-04-01
*/
public class Tbl_user implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String name;
private String pwd;
private Integer age;
private String tel;
private Integer deleted;
private Integer version;
// get/set
// toString
}
数据库下几个表,就会生成几个实体类
很明显,代码生成的位置,以及命名,主键策略配置,都不对呀
下面就再加一些(全局)配置,来修改其生成位置以及命名
复制一份,命名为CodeGenerator,在这里面加全局配置
//设置全局配置
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setOutputDir(System.getProperty("user.dir")+"/mybatisplus_04_generator/src/main/java"); //设置代码生成位置
globalConfig.setOpen(false); //设置生成完毕后是否打开生成代码所在的目录
globalConfig.setAuthor("黑马程序员"); //设置作者
globalConfig.setFileOverride(true); //设置是否覆盖原始生成的文件
globalConfig.setMapperName("%sDao"); //设置数据层接口名,%s为占位符,指代模块名称
globalConfig.setIdType(IdType.ASSIGN_ID); //设置Id生成策略
autoGenerator.setGlobalConfig(globalConfig);
//设置包名相关配置
PackageConfig packageInfo = new PackageConfig();
packageInfo.setParent("com.aaa"); //设置生成的包名,与代码所在位置不冲突,二者叠加组成完整路径
packageInfo.setEntity("domain"); //设置实体类包名
packageInfo.setMapper("dao"); //设置数据层包名
autoGenerator.setPackageInfo(packageInfo);
//策略设置
StrategyConfig strategyConfig = new StrategyConfig();
strategyConfig.setInclude("tbl_user"); //设置当前参与生成的表名,参数为可变参数
strategyConfig.setTablePrefix("tbl_"); //设置数据库表的前缀名称,模块名 = 数据库表名 - 前缀名 例如: User = tbl_user - tbl_
strategyConfig.setRestControllerStyle(true); //设置是否启用Rest风格
strategyConfig.setVersionFieldName("version"); //设置乐观锁字段名
strategyConfig.setLogicDeleteFieldName("deleted"); //设置逻辑删除字段名
strategyConfig.setEntityLombokModel(true); //设置是否启用lombok
autoGenerator.setStrategy(strategyConfig);
说明:在资料中也提供了CodeGenerator代码生成器类,根据实际情况修改后可以直接使用。
最终完整 CodeGenerator.Java
反手就创建成模板文件,下次直接用
(此文件除了包依赖外,不依赖其他任何代码,不需要其他任何配置文件或者拦截器,直接就是一个独立的工具类)
package cn.whu;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
public class CodeGenerator {
public static void main(String[] args) {
//1. 创建代码生成器对象,执行生成代码操作
AutoGenerator autoGenerator = new AutoGenerator();
// 中间三大配置 让他一步步生成我们想要的代码样式
//设置全局配置
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setOutputDir(System.getProperty("user.dir")+"/mybatisplus_04_generator/src/main/java"); //设置代码生成位置
globalConfig.setOpen(false); //设置生成完毕后是否打开生成代码所在的目录
globalConfig.setAuthor("whu"); //设置作者
globalConfig.setFileOverride(true); //★小心被覆盖了★ 设置是否覆盖原始生成的文件
globalConfig.setMapperName("%sDao"); //设置数据层接口名,%s为占位符,指代模块名称 //默认后缀是Mapper %s就是表名
globalConfig.setIdType(IdType.ASSIGN_ID); //设置Id生成策略
autoGenerator.setGlobalConfig(globalConfig);
//设置包名相关配置
PackageConfig packageInfo = new PackageConfig();
packageInfo.setParent("cn.whu"); //设置生成的包名,与代码所在位置不冲突,二者叠加组成完整路径
packageInfo.setEntity("domain"); //设置实体类包名
packageInfo.setMapper("dao"); //设置数据层包名
autoGenerator.setPackageInfo(packageInfo);
//策略设置
StrategyConfig strategyConfig = new StrategyConfig();
//strategyConfig.setInclude("tbl_user"); //设置当前参与生成的表名,参数为可变参数 // 不写就是生成所有表
strategyConfig.setTablePrefix("tbl_"); //设置数据库表的前缀名称,模块名 = 数据库表名 - 前缀名 例如: User = tbl_user - tbl_
strategyConfig.setRestControllerStyle(true); //设置是否启用Rest风格
strategyConfig.setVersionFieldName("version"); //设置乐观锁字段名
strategyConfig.setLogicDeleteFieldName("deleted"); //设置逻辑删除字段名
strategyConfig.setEntityLombokModel(true); //设置是否启用lombok 实体类就自动用lombok了
autoGenerator.setStrategy(strategyConfig);
//2. 数据源相关配置:读取数据库中的信息,根据数据库表结构生成代码
DataSourceConfig dataSource = new DataSourceConfig();
dataSource.setDriverName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("1234");
autoGenerator.setDataSource(dataSource);
//autoGenerator.setTemplate()//不设置就是使用内置模板
//autoGenerator.setTemplateEngine()//一般这两个API都用不上,因为自定义模板工作量太大
//3. 执行生成操作
autoGenerator.execute();
}
}
执行这个文件:
全都按我们希望地生成了特别好的代码,太棒了~
正好到博客末尾了,贴出来看看,体会MP代码生成器的强大吧
UserController
@RestController
@RequestMapping("/user")
public class UserController {
}
UserDao
生成的是xml开发模式,我们用注解开发,就要手动加上@Mapper注解,然后删除下面mapper.xml整个mapper包了
public interface UserDao extends BaseMapper<User> {
}
User
@Data
@EqualsAndHashCode(callSuper = false) //比较时不调用父类的EqualsAndHashCode
@TableName("tbl_user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String name;
private String pwd;
private Integer age;
private String tel;
@TableLogic
private Integer deleted;
@Version
private Integer versio
}
UserMapper.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="cn.whu.dao.UserDao">
</mapper>
Service都帮你抽取并继承了BaseService(这里叫IService),显而易见,用不着,service层逻辑极端复杂,每个项目有每个项目的service业务逻辑,所以这两个文件基本删了自己重新创建。
.
或者留着不删,有些简单的直接用人家的,实在有自己的业务逻辑,再重写对应的方法或者自定义新方法呗
毕竟人家生成了,要用就用,不用搁那放着呗,反正也都是继承来的,也不占地方
IUserService
public interface IUserService extends IService<User> {
}
UserServiceImpl
@Service
public class UserServiceImpl extends ServiceImpl<UserDao, User> implements IUserService {
}
学习时别用,实际开发时疯狂用~