一、前置场景准备(基于springboot)
pom
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.2</version>
</parent>
<groupId>com.chenpei.springboot</groupId>
<artifactId>springboot-mybatis-plus</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--引入mybatis-plus的springboot启动器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>
<!--引入mybatis-plus的依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>3.5.3</version>
</dependency>
<!-- 数据库相关-->
<dependency><!--注意这里要在resource下作适配-->
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>1.2.18</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.19</version>
</dependency>
<!-- 数据库引擎-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>
<!-- 测试依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>3.1.0</version>
</dependency>
</dependencies>
application.yml
pring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:#mybatis-plus默认开启驼峰命名
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: chen13515216766
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8
实体类:
@TableName("t_emp")//当表名和实体类名不一样时要指定对应的表名
public class Employee {
@TableId//这里可以指定主键的列名,默认跟属性名一样
Integer eid;//要用实体类指定,因为使用实体类默认值是null,
// 在更新表格操作中,避免因为0而改了所有本不应该改的信息(mybatis-plus更新时,属性为null不修改)
String empName;
String empSex;
Integer empAge;
String empEmail;
Integer did;
@Override
public String toString() {
return "Employee{" +
"eid=" + eid +
", empName='" + empName + '\'' +
", empSex='" + empSex + '\'' +
", empAge=" + empAge +
", empEmail='" + empEmail + '\'' +
", did=" + did +
'}';
}
public Integer getEid() {
return eid;
}
public void setEid(Integer eid) {
this.eid = eid;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public String getEmpSex() {
return empSex;
}
public void setEmpSex(String empSex) {
this.empSex = empSex;
}
public Integer getEmpAge() {
return empAge;
}
public void setEmpAge(Integer empAge) {
this.empAge = empAge;
}
public String getEmpEmail() {
return empEmail;
}
public void setEmpEmail(String empEmail) {
this.empEmail = empEmail;
}
public Integer getDid() {
return did;
}
public void setDid(Integer did) {
this.did = did;
}
}
二、基于mapper的接口强化
A、我们不需要xml文件,只需要指定接口,也不需要在接口上加@Mapper注解,代替用@MapperScan("mapper接口所在的文件夹")
B、Mapper内容如下:
public interface EmployeeMapper extends BaseMapper<Employee> {
//泛型指定要操作表对应的实体类
//里面什么都不用指定
}
C、主启动类的注解:
@SpringBootApplication
@MapperScan("com.chenpei.springboot.mappers")//指定mapper所在包
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
D、测试
@SpringBootTest
public class MyTest {
//注入mapper的代理对象
@Autowired
EmployeeMapper employeeMapper;
@Test
public void testOne(){
//根据id批量查询数据
ArrayList<Integer> numall= new ArrayList<>();
numall.add(3);
numall.add(4);
List<Employee> employees = employeeMapper.selectBatchIds(numall);
System.out.println(employees);
//测试更新数据,根据id更新,主键属性不能为空
Employee employee = new Employee();
employee.setEid(13);
//仅仅修改姓名属性
employee.setEmpName("春空千鹤");
int i = employeeMapper.updateById(employee);
System.out.println(i==1);//如果成功控制台为true
//测试删除方法
employee.setEid(19);
int rel = employeeMapper.deleteById(employee);
System.out.println(rel);
}
}
三、基于service层的再增强(直接通过Service层直接调用crud方法)
A、在实际业务中我们会碰到查询等情况,service层在业务中更多仅是传递的作用,这时增强service层就显得很有必要。
B、值得一提的是,Mapper接口并不能丢掉,因为会被service继承
C、增强Service层需要我们的Service层接口和Service实现类都继承特定的接口,接口或类中也要指定特定的泛型实现
service层如下:
public interface EmployeeService extends IService<Employee> {
//继承该接口中有一半方法已经有默认实现,但是还有一半没有默认实现。
}
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService{
//继承接口是为了实现接口中没有被实现的方法,在ServiceImpl中得到了实现,
// 当中的两个泛型,分别是Employee的mapper接口,另一个是这个接口对应的实体类
}
D、这样的方式相比于直接使用mapper接口来讲有两个变化
- 这种方式,service层就相当于天然开启了事务
- 会有更多的方法,比如批量添加等等
E、下面我们进行测试:
@SpringBootTest
public class MyTest {
EmployeeService employeeService;
@Test
public void twoTest(){
//除了接口提供的增删改查方法,Service方式强化的crud还提供了批量插入和根据id有无判断
//是插入还是修改的方法。
// Employee meEmployee = new Employee();
// meEmployee.setEid(14);
// meEmployee.setEmpName("闻善");
//首先测试有id的情况,会update
// boolean rel = employeeService.saveOrUpdate(meEmployee);
// Employee myEmployee = new Employee(null,"向羽","男",45,"xiangyu@123.com",1);
//测试没有id变插入方法
// boolean b = employeeService.saveOrUpdate(myEmployee);
// System.out.println(rel);
// System.out.println(b);
//测试批量插入方法
ArrayList<Employee> myEmployees = new ArrayList<>();
Employee myEmployee = new Employee(19,"彭于晏","男",36,"pengyuyan@123.com",1);
Employee myEmployeeTwo = new Employee(20,"Jack","男",24,"jack@123.com",3);
myEmployees.add(myEmployee);
myEmployees.add(myEmployeeTwo);
boolean saved = employeeService.saveBatch(myEmployees);
System.out.println(saved);
}
}
四、分页插件的使用
A、mybatis-plus自身就整合了分页插件。同时Mybatis提供了一个专门放置插件的集合,一旦该集合中被放入相关插件,就会起效,所以我们第一步是要将集合注入bean容器,并将分页插件放入集合(在主启动类进行)
@SpringBootApplication
@MapperScan("com.chenpei.springboot.mappers")
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
//向专门放置mybatisPlus的插件的集合中放置分页插件的实现
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//因为mybatisPlus集成了分页插件,所以直接装入相关实例PaginationInnerInterceptor即可·
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
B、在mybatis-plus提供的方法中使用分页插件
@SpringBootTest
public class MyTest {
EmployeeMapper employeeMapper;
@Test
public void testOne(){
//首先要用IPage的实现类Page指定分页的当前页和每页容量(跟之前的PageHelper指定内容一致)
Page<Employee> myPage = new Page<>(1,3);
//调用提供的获取页面的方法,传入page对象,第二个参数设置查询条件,null值为全部查询
employeeMapper.selectPage(myPage,null);
//上面方法执行完毕后就会将分页后相关信息注入咱自己创建的Page对象(跟之前的创建PageInfo对象,传入list对象功能一样)
List<Employee> records = myPage.getRecords();//获取当前页的数据集合
long total = myPage.getTotal();//获取总记录条数,即记录条数
long myPageSize = myPage.getSize();//获取每页的页容量
System.out.println("数据为: " + records +"\n总记录数: " + total +
"\n本页有数据: " + myPageSize + "条");
}
}
C、如何在自定义的方法中使用分页插件
在mapper接口中声明方法,并将返回值设置为IPage类型
public interface EmployeeMapper extends BaseMapper<Employee> {
//声明自定义方法,将返回值指定为IPage接口
IPage<Employee> getAllEmployee(IPage<Employee> myIPage);
}
xml映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace = 接口的全限定符 -->
<mapper namespace="com.chenpei.springboot.mappers.EmployeeMapper">
<!--虽然接口中定义的返回值为Ipage接口,但xml文件中还是指定为返回集合泛型的映射-->
<select id="getAllEmployee" resultType="com.chenpei.springboot.pojo.Employee">
select * from t_emp<!--注意这里不能加;,不然无法加limit语句,切记-->
</select>
</mapper>
在application.xml中指定映射文件位置:
mybatis-plus:
#指定映射文件地址,默认地址为resource的mapper文件夹下
mapper-locations: classpath:/mappers/*.xml
测试方法中调用
@Test//上面省略了mapper接口的注入
public void twoTest(){
//指定分页数据
Page<Employee> myPage = new Page<>(1,3);
//调用自定义方法,并传入page对象作为参数
employeeMapper.getAllEmployee(myPage);
//下面正常进行即可
List<Employee> records = myPage.getRecords();//获取当前页的数据集合
long total = myPage.getTotal();//获取总记录条数,即记录条数
long myPageSize = myPage.getSize();//获取每页的页容量
System.out.println("数据为: " + records +"\n总记录数: " + total +
"\n本页有数据: " + myPageSize + "条");
}
D、相关报错:
lineNumber: 1; columnNumber: 1; 前言中不允许有内容。
这是因为你映射文件地址指定有问题
五、Wrapper实现类的使用(用来给mybatis-plus提供的各种方法拼接条件语句)
A、Wrapper 接口的结构(部分):
- QueryWrapper是最常用的实现类,可用于删除/查询/修改场景
- UpdateWrapper则只能用于修改场景。
B、实现大概逻辑:
- 创建QueryWrapper对象或者UpdateWrapper对象
- 用对象调用相关方法,指定拼接到语句的条件(具体方法可查官网:条件构造器 | MyBatis-Plus),传入参数大概是列名和被判断的值,方法即条件
- 每次调用方法之后都会返回本身对象类型,因此可以链式调用
- 将设置好的Wrapper对象放入方法中,执行后即使拼接了条件的结果
C、具体方法的尝试
- 大于、小于、等于
@Test//省略了mapper接口的注入,下面所有测试方法亦如是
public void testOne(){
//创建Wrapper
QueryWrapper<Employee> myFirst = new QueryWrapper<>();
//翻译:条件是emp_age这列中,值大于18的
myFirst.gt("emp_age", 18)
//链式调用,eid这列小于20
.lt("eid",20)
//emp_sex这列数据中等于“女”
.eq("emp_sex","女");
//注:上面这三个条件(替他条件方法一样)在默认情况下使用and拼接
List<Employee> employees = employeeMapper.selectList(myFirst);
System.out.println(employees);
}
- 那么条件和条件之间如何由默认的and变为or
myFirst.gt("emp_age", 18).or()
//or方法加载gt和lt直接,就是说gt和lt中间的拼接条件变为or了
//链式调用,eid这列小于20
.lt("eid",20)//这里没有or所以还是默认的and拼接
//emp_sex这列数据中等于“女”
.eq("emp_sex","女");
- 模糊查询(省略结构)
//查询名字中有暴的,会帮助我们在前后加%
myFirst.like("emp_name","暴");
- 排序
//查询结果按照emp_age升序排序,如果emp_age相同则根据eid降序排列
//这种中间就是,不用担心冲突
myFirst.orderByAsc("emp_age").orderByDesc("eid");
D、UpdateWrapper的不同之处
- 因为其是QueryWrapper的子类,所以他也拥有上面的方法,同时多出来了set方法,用来给wrapper设置数据。
- 那么为什么要有set方法
- 因为update相关方法都需要传入对象,为空的值不会被修改,那么如果我们真想把某个值改为空值就不行了。
- 将要修改的数据直接通过set方法放在UpdateWrapper中就可以了,不用再自己创建对象。
- 使用:
@Test
public void testOne(){
//创建Wrapper
UpdateWrapper<Employee> updateWrapper = new UpdateWrapper<>();
//放置条件和要修改的数据
updateWrapper.set("emp_sex","男").set("emp_email",null).eq("emp_name","闻善");
//执行方法
int update = employeeMapper.update(null, updateWrapper);
System.out.println(update);
}
E、LambdaQueryWrapper和LambdaUpdateWrapper跟QueryWrapper和UpdateWrapper的不同
- 允许使用lambda表达式来指定列名参数,减少自己拼接字符串出现错误。
- lambda表达式就是简写的单方法接口实现
- 当接口有参数且有多个语句,仅能简写为:(arg1,arg2)->{}
- 当方法没有参数,且语句只有一个则能简写为:()->语句;如果有返回值会自动返回
- 上面单语句无参数的情况,还能通过方法引用再简化:如语句为System.out.println(),可以变为System.out:pritln 也不用写括号和箭头了。
- 例子:
@Test
public void testOne(){
//创建Wrapper
LambdaUpdateWrapper<Employee> updateWrapper = new LambdaUpdateWrapper<>();
//放置条件和要修改的数据,指定列名使用方法引用,这样就直接通过实体类属性找列名了
updateWrapper.set(Employee::getEmpEmail,"wenshan@qq.com").eq(Employee::getEmpName,"闻善");
//执行方法
int update = employeeMapper.update(null, updateWrapper);
System.out.println(update);
}
六、注解实现实体类与表的自定义映射
A、@TableName
- 用在实体类上,指定实体类对应的表名(默认使用类名寻找表名)
B、@TableId
- 用在实体类的属性值上,指定对应的主键名,其有两个属性
- value 指定对应的列名
- type 指定插入数据时,主键的赋值方式,默认使用雪花算法
C、@TableField
- 用在实体类非主键的属性上指定对应的列名
D、展示
@TableName("t_emp")//当表名和实体类名不一样时要指定对应的表名
public class Employee {
//当主键名和表列名不一致时可以使用@TableId中的value属性指明
//type属性指定主键赋值方式,mybatis-plus默认使用雪花算法赋值,要用long类型。
//如果指定为AUTO则变为自增长类型。
@TableId( type= IdType.AUTO)
Integer eid;//要用实体类指定,因为使用实体类默认值是null,
// 在更新表格操作中,避免因为0而改了所有本不应该改的信息(mybatis-plus更新时,属性为null不修改)
@TableField("emp_name")//这里有驼峰映射没必要,仅为了展示使用
String empName;
String empSex;
Integer empAge;
String empEmail;
Integer did;
//省略必要结构
}
E、全局指定实体类和表明的映射及主键增长策略
mybatis-plus:
global-config:
db-config:
table-prefix: t_ #给每个类名前加上t_后寻找表映射
id-type: auto #指定全局主键策略为自增长
F、雪花算法和自增长的使用场景
- 雪花算法或者uuid
当一个实体类映射多个表时,使用防止不同表的自增长导致主键冲突。
- 自增长
当一个实体类对应一个表时可以使用自增长。
七、逻辑删除和物理删除的快速实现
(一)逻辑删除和物理删除的区别
A、物理删除
真的就是在数据库将数据删除了,删除后这条数据在数据库中就不是真实存在的
B、逻辑删除
通过表中某个字段的变化代表改数据被删除,例如有个deleted字段,若改字段值为0则说明未被删除,为1则说明该数据已被删除,这样在查询时将已删除数据过滤掉即可,实际的数据并未在数据库中被改变。
(二)利用mybatis-plus快速实现逻辑删除
A、用@TableLogic注解表明作为逻辑删除标识的列对应的属性
@TableLogic//将部门号标注为是否删除标识字段(省个事,懒得增加表结构了)
Integer did;
可以通过两个属性指定删除和未删除的标识
/**
* 默认逻辑未删除值(该值可无、会自动获取全局配置)
*/
String value() default "";
/**
* 默认逻辑删除值(该值可无、会自动获取全局配置)
*/
String delval() default "";
B、指定删除标识规则(在application中全局指定)
mybatis-plus:
global-config:
db-config:
#logic-delete-field: did #指定全局的逻辑删除标识字段
logic-delete-value: 2 # 指定被删除的数据标识,默认值为1
logic-not-delete-value: 1 #指定未被删除的数据标识,默认值为0
(三)指定逻辑删除后删除和查询操作的变化
A、删除操作
删除操作由delete变为update set 标识删除字段;
B、查询操作
每次查询都会拼接上条件 标识字段 = 未被删除标识符
八、乐观锁和悲观锁的实现
(一)乐观锁和悲观锁的区别
A、乐观锁就是保证当并发的数据修改和查询操作时,保证修改者只能有一个,当第二个人来修改时,检查是否为最新数据,如果不是就驳回,等待其再次请求
B、悲观锁就是当一个人修改数据时,其他用户指定等该用户操作完毕后才能进行数据库操作。
(二)mybatis-plus实现乐观锁
A、首先要给实体类增加属性,同时对应表加上version字段,然后在对饮version属性上添加@Version标签
@TableName("t_emp")//当表名和实体类名不一样时要指定对应的表名
public class Employee {
@TableId( type= IdType.AUTO)
Integer eid;
@TableField("emp_name")
String empName;
String empSex;
Integer empAge;
String empEmail;
@TableLogic
Integer did;
@Version //标识为版本字段
Integer version;
}//省略结构
C、增加乐观锁插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
//向专门放置mybatisPlus的插件的集合中放置分页插件的实现
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//因为mybatisPlus集成了分页插件,所以直接装入相关实例PaginationInnerInterceptor即可·
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
//增加乐观锁插件
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mybatisPlusInterceptor;
}
C、这样之后,每次对数据进行修改时,就会在version字段加上1,当并发操作存在时,修改者都会携带version属性,当version属性值跟数据库一致时,就可以修改,如果不一致,则需要重新查询再修改。下面进行测试
@Test
public void testOne(){
//模拟并发操作,两个用户同时获取数据
Employee employee1 = employeeMapper.selectById(6);
Employee employee2 = employeeMapper.selectById(6);
//用户一率先修改,赠加了version,
employee1.setEmpName("忆长安");
employeeMapper.updateById(employee1);
//用户二同时准备修改,但是发现version不一致,修改失败;
employee2.setEmpName("侬江南");
employeeMapper.updateById(employee2);
}
九、防止全表更新和删除操作
非常简单,直接在mybatis-plus的插件集合中增加一个拦截器即可
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
//向专门放置mybatisPlus的插件的集合中放置分页插件的实现
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//因为mybatisPlus集成了分页插件,所以直接装入相关实例PaginationInnerInterceptor即可·
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
//增加乐观锁插件
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
//增加防全局更新和删除拦截器
mybatisPlusInterceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return mybatisPlusInterceptor;
}
测试:
@Test
public void testOne(){
//模拟全表删除操作
employeeMapper.delete(null);
}
因为我们配置了拦截器,所以会被拦截并报错:Prohibition of table update operation
十、利用mybatisX插件实现mybatis-plus的逆向工程
A、首先要在插件市场中下载该插件,然后按下面步骤进行即可
ok后
然后选择要进行逆向的表,右键,选中插件选项
完成后生成了包括接口,xml映射文件,service接口及实现类的curd强化
B、利用插件进行自定义方法按照指定格式命名方法可以快速生成xml文件对应结构
以查询为例:
以select开头即可
命名完毕后选中option+回车即可快速生成。