
- java中访问数据库各种方式的区别
- mybatis-plus入门
- 开发步骤
- 新建springboot工程
- 添加maven依赖
- 数据库配置
- 实体类
- 创建Dao接口
- 在springboot的启动类上,加入Maper扫描器
- 测试使用
- 配置mybatis日志
- CRUD基本用法
- ActiveRecord(AR)
- AR之insert
- 表和列
- 指定表名
- 驼峰命名
- 自定义sql
- 查询和分页
- 查询构造器:Wrapper
- QueryWrapper:查询条件封装类
- UpdateWrapper:更新条件封装类
- 查询
- allEq
- eq
- ne
- gt
- ge
- lt
- le
- between
- notBetween
- like,notlike
- likeLeft,liekRight
- isNull,isNotNull
- in,notIn
- inSql,notInSql
- groupBy
- orderByAsc,orderByDesc,orderBy
- or,and
- last
- exists,notExists
- 分页
- 通用service
- crud
- 分页查询
- 条件构造器
- id生成策略
- 逻辑删除
- 数据自动填充
- 执行 SQL 分析打印
- 数据安全保护
- 乐观锁
- 代码生成器
- 快速入门
- 代码生成器(3.5.1+版本)快速入门
- 官网代码生成器(3.5.1+)
- 数据库配置
- 包配置
pom依赖
数据库配置4.0.0 org.springframework.boot spring-boot-starter-parent2.5.6 com.example demo0.0.1-SNAPSHOT demo Demo project for Spring Boot 1.8 org.springframework.boot spring-boot-starterorg.springframework.boot spring-boot-starter-testtest com.baomidou mybatis-plus-boot-starter3.4.3.4 mysql mysql-connector-java8.0.27 org.projectlombok lombok1.18.22
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
server:
port: 9090
实体类
package com.example.demo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
@Data
public class User {
//定义属性,属性名和表中的列名一样
@TableId(value = "id",type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
}
创建Dao接口
Mapper
package com.example.demo.mapper; import com.baomidou.mybatisplus.core.mapper.baseMapper; import com.example.demo.entity.User; public interface UserMapper extends baseMapper在springboot的启动类上,加入Maper扫描器{ }
package com.example.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(value = "com.example.demo.mapper")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
测试使用
在测试类或service注入Dao接口,框架实现动态代理创建Dao的实体类对象,
调用baseMapper中的方法,完成CRUD
测试
package com.example.demo;
import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.util.List;
@SpringBootTest
class DemoApplicationTests {
//使用自动注入,注入Mapper对象(Dao)
@Autowired
private UserMapper userMapper;
@Test
public void testUser(){
User user =new User();
user.setName("张三");
user.setAge(2);
user.setEmail("zhangsan@163.com");
//调用userMapper的方法,也就是父接口baseMapper中提供的方法
int rows = userMapper.insert(user);
System.out.println("insert的结果:"+rows);
}
}
输出insert的结果:1
配置mybatis日志控制台输出正在执行的sql语句
application.yml
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
CRUD基本用法
- insert
public void testUser(){
User user =new User();
user.setName("zhangsan");
user.setAge(2);
user.setEmail("zhangsan@163.com");
//调用userMapper的方法,也就是父接口baseMapper中提供的方法
int rows = userMapper.insert(user);
System.out.println("insert的结果:"+rows);
}
//添加数据后,获取主键值
@Test
public void testInsertGetId(){
User user =new User();
user.setName("李斯");
user.setAge(2);
user.setEmail("lisi@163.com");
int rows=userMapper.insert(user);
System.out.println("insert的结果:"+rows);
//获取刚添加到数据库中的数据的主键id
System.out.println("主键id="+user.getId());
}
- update
@Test
public void testUpdate(){
User user =new User();
user.setName("修改的数据");
user.setEmail("edit@163.com");
user.setId(6L);;
int rows=userMapper.updateById(user);
System.out.println("update rows: " +rows);
}
@Test
public void testUpdate2(){
User user =new User();
user.setId(2L);;
user.setName("张三");
int i = userMapper.updateById(user);
System.out.println("i="+i);
}
@Test
public void testUpdate3(){
User user =new User();
user.setId(3L);
user.setEmail("lisi@163.com");
int rows = userMapper.updateById(user);
System.out.println("rows="+rows);
}
- delete
@Test
public void testDelete(){
//DELETE FROM user WHERe id=?
int rows =userMapper.deleteById(3);
System.out.println("rows="+rows);
}
主键不存在的时候,删除0行
@Test
public void testDeleteMap(){
//创建Map对象,保存条件值
Map map=new HashMap<>();
//put("表的字段名",条件值)
map.put("name","zhangsan");
map.put("age",2);
//调用删除方法
// DELETE FROM user WHERe name = ? AND age = ?
int rows=userMapper.deleteByMap(map);
System.out.println("rows="+rows);
}
@Test
public void testDeleteBatchIds(){
Listids=new ArrayList<>();
ids.add(1);
ids.add(5);
ids.add(6);
//DELETE FROM user WHERe id IN ( ? , ? , ? )
int rows=userMapper.deleteBatchIds(ids);
System.out.println("rows="+rows);
}
使用lamda表达式创建list集合
@Test
public void testDeleteBatchIds(){
//使用lamda表达式创建list集合
Listids= Stream.of(2,4).collect(Collectors.toList());
//DELETE FROM user WHERe id IN ( ? , ? , ? )
int rows=userMapper.deleteBatchIds(ids);
System.out.println("rows="+rows);
}
- select
@Test
public void testSelectById(){
User user =userMapper.selectById(1);
// 在使用对象之前,需要判断对象是否为null
if(user!=null){
//业务方法的调用
}
System.out.println("user="+user);
}
@Test
public void testSelectBatchIds(){
//lambda表达式
//SELECT id,name,age,email FROM user WHERe id IN ( ? , ? , ? )
Listids=Stream.of(4,8,9).collect(Collectors.toList());
Listusers=userMapper.selectBatchIds(ids);
for (User user:users){
System.out.println(user);
}
}
@Test
public void testSelectMap(){
//创建Map,封装查询条件
Mapmap=new HashMap<>();
//key是字段名,value:字段值,多个key,and连接
map.put("name","zhangsan");
map.put("age",20);
Listusers=userMapper.selectByMap(map);
//SELECT id,name,age,email FROM user WHERe name = ? AND age = ?
users.forEach(user -> {
System.out.println(user);
});
}
mybatis-plus的CRUD底层源码调用的是mybatis的SqlSession的方法
ActiveRecord
- 每一个 数据库表对应创建一个类,类的每一个对象实力对应于数据库中表的一行记录;通常表的每个字段在类中都有相应的Field
- ActiveRecord负责把自己持久化,在ActiveRecord中封装了对数据库的访问,通过对象自己实现CRUD,实现优雅的数据库 *** 作
- ActiveRecord也封装了部分业务逻辑,可以作为业务对象使用
1、dept表设计
2、entity实体类
package com.example.demo.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.extension.activerecord.Model; import lombok.Data; @Data public class Dept extends Model{ @TableId(value = "id",type = IdType.AUTO)//自动增长,数据库中的字段为id private Integer id; private String name; private String mobile; private Integer manager; }
3、Mapper
package com.example.demo.mapper; import com.baomidou.mybatisplus.core.mapper.baseMapper; import com.example.demo.entity.Dept; public interface DeptMapper extends baseMapper{ }
4、测试AR
package com.example.demo;
import com.example.demo.entity.Dept;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class DeptARTest {
@Test
public void testARInsert(){
Dept dept = new Dept();
dept.setName("销售部");
dept.setMobile("010-12345678");
dept.setManager(1);
//调用实体对象自己的方法,完成对象自身到数据库的 *** 作
boolean flag = dept.insert();
System.out.println("insert="+flag);
}
}
@Test
public void testUpdateById(){
Dept dept =new Dept();
dept.setId(3);
dept.setManager(2);
boolean flag =dept.updateById();
System.out.println("update flag="+flag);
}
@Test
public void testARDelete(){
Dept dept =new Dept();
dept.setId(1);
boolean flag = dept.deleteById();//拿实体自身的id作为删除的主键
boolean result=dept.deleteById(1);
System.out.println("删除是否成功:"+flag);
System.out.println("result="+result);
}
@Test
public void testSelectById(){
Dept dept =new Dept();
dept.setId(1);
Model model=dept.selectById();
System.out.println(model);
}
@Test
public void testSelect(){
Dept dept =new Dept();
Dept dept2=dept.selectById(null);
System.out.println(dept2);
}
表和列
主键,TableName,TableId
IdType枚举类,主键定义如下:
表
实体类
package com.example.demo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName(value = "address")
public class Address {
@TableId(value = "id",type = IdType.AUTO)
private Integer id;
@TableField(value = "city")
private String city;
private String street;
private String zipcode;
}
Mapper
package com.example.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.baseMapper;
import com.example.demo.entity.Address;
public interface AddressMapper extends baseMapper {
}
测试
package com.example.demo;
import com.example.demo.entity.Address;
import com.example.demo.mapper.AddressMapper;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
@SpringBootTest
public class AddressTest {
@Resource
public AddressMapper addressMapper;
@Test
public void testInsert(){
Address address = new Address();
address.setCity("北京");
address.setStreet("长安大街");
address.setZipcode("010");
int rows=addressMapper.insert(address);
System.out.println("rows="+rows);
}
}
驼峰命名
表
实体类
package com.example.demo.entity;
import lombok.Data;
@Data
public class Customer {
private Integer id;
private String custName;
private String custEmail;
private Integer custAge;
}
Mapper
package com.example.demo.mapper; import com.baomidou.mybatisplus.core.mapper.baseMapper; import com.example.demo.entity.Customer; public interface CustomerMapper extends baseMapper{ }
测试
package com.example.demo;
import com.example.demo.entity.Customer;
import com.example.demo.mapper.CustomerMapper;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
@SpringBootTest
public class CustomerTest {
@Resource
public CustomerMapper dao;
@Test
public void test(){
Customer customer =new Customer();
customer.setCustName("张三");
customer.setCustAge(28);
customer.setCustEmail("zhangsan@163.com");
int rows=dao.insert(customer);
System.out.println("rows="+rows);
}
}
自定义sql
表
实体类
package com.example.demo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
@Data
public class Student {
@TableId(value = "id",type = IdType.AUTO)
private Integer id;
private String name;
private Integer age;
private String email;
private Integer status;
}
Mapper
package com.example.demo.mapper; import com.baomidou.mybatisplus.core.mapper.baseMapper; import com.example.demo.entity.Student; import java.util.List; public interface StudentMapper extends baseMapper{ List selectByName(); public int insertStudent(Student student); public Student selectStudentByName(String name); }
新建sq映射文件
配置xml文件位置
测试
package com.example.demo;
import com.example.demo.entity.Student;
import com.example.demo.mapper.StudentMapper;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
@SpringBootTest
public class StudentTest {
@Resource
private StudentMapper mapper;
@Test
public void testInsert(){
Student student = new Student();
student.setName("李四");
student.setEmail("lisi@163.com");
student.setAge(20);
student.setStatus(1);
mapper.insertStudent(student);
}
@Test
public void testSeectById(){
Student student = mapper.selectStudentByName("李四");
Student stu = mapper.selectById(2);
System.out.println(stu);
if(stu!=null){
//其他业务 *** 作
}
System.out.println(student);
}
}
查询和分页
查询构造器:Wrapper
在idea中ctr+N搜索Wrapper,鼠标选中Wrapper,ctrl+f12查看当前类的结构信息
Student表:初始数据
以Map为参数条件
1、条件:name是张三,age=22
@Test
public void testAllEq(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
Mapmap=new HashMap<>();
map.put("name","张三");
map.put("age",22);
//组装条件
queryWrapper.allEq(map);
Liststudents=mapper.selectList(queryWrapper);//调用mp自己的查询方法
students.forEach(student -> {
//循环遍历输出
System.out.println(student);
});
}
@Test
public void testAllEq2(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
Mapmap=new HashMap<>();
map.put("name","张三");
map.put("age",null);
//组装条件
queryWrapper.allEq(map,false);
Liststudents=mapper.selectList(queryWrapper);//调用mp自己的查询方法
students.forEach(stu-> System.out.println(stu));
}
eq
等于 =
name等于李四
@Test
public void testEq(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
//组成条件
queryWrapper.eq("name","张三");
Liststudents=mapper.selectList(queryWrapper);//调用mp自己的查询方法
students.forEach(stu-> System.out.println(stu));
}
ne
ne 不等于
@Test
public void testNe(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
queryWrapper.ne("name","张三");
Liststudents=mapper.selectList(queryWrapper);
students.forEach(student -> System.out.println(student));
}
gt
gt大于
@Test
public void testGt(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
queryWrapper.gt("age",20);
Liststudents=mapper.selectList(queryWrapper);
students.forEach(student -> System.out.println(student));
}
ge
ge大于等于
@Test
public void testGe(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
queryWrapper.ge("age",30);
Liststudents=mapper.selectList(queryWrapper);
students.forEach(student -> System.out.println(student));
}
lt
lt小于
@Test
public void testLt(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
queryWrapper.lt("age",30);
Liststudents=mapper.selectList(queryWrapper);
students.forEach(student -> System.out.println(student));
}
le
le小于等于
@Test
public void testLe(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
queryWrapper.le("age",30);
Liststudents=mapper.selectList(queryWrapper);
students.forEach(student -> System.out.println(student));
}
between
between 在两个值范围之间(包括开始区间和结束区间)
@Test
public void testBetween(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
queryWrapper.between("age",18,28);
Liststudents=mapper.selectList(queryWrapper);
students.forEach(student -> System.out.println(student));
}
notBetween
不在范围区间内
@Test
public void testNotBetween(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
queryWrapper.notBetween("age",18,28);
Liststudents=mapper.selectList(queryWrapper);
students.forEach(student -> System.out.println(student));
}
like,notlike
like 匹配值 “%值”
notLike 不匹配"%值"
@Test
public void testLike(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
queryWrapper.like("name","张");
Liststudents=mapper.selectList(queryWrapper);
students.forEach(student -> System.out.println(student));
}
@Test
public void testNotLike(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
queryWrapper.notLike("name","张");
Liststudents=mapper.selectList(queryWrapper);
students.forEach(student -> System.out.println(student));
}
likeLeft,liekRight
likeLeft:LIKE ‘%值’
likeRight:LIKE ‘值%’
@Test
public void testLikeLeft(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
queryWrapper.likeLeft("name","张");
Liststudents=mapper.selectList(queryWrapper);
students.forEach(student -> System.out.println(student));
}
@Test
public void testLikeRight(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
queryWrapper.likeRight("name","张");
Liststudents=mapper.selectList(queryWrapper);
students.forEach(student -> System.out.println(student));
}
isNull,isNotNull
isNull:字段 IS NULL
isNotNull:字段 IS NOT NULL
@Test
public void testIsNull(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
queryWrapper.isNull("email");
Liststudents=mapper.selectList(queryWrapper);
students.forEach(student -> System.out.println(student));
}
@Test
public void testIsNotNull(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
queryWrapper.isNotNull("email");
Liststudents=mapper.selectList(queryWrapper);
students.forEach(student -> System.out.println(student));
}
in,notIn
in后面值列表,在列表中都是符合条件的
notIn 不在列表中的
@Test
public void testIn(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
queryWrapper.in("name","张三","李四");
Liststudents=mapper.selectList(queryWrapper);
students.forEach(student -> System.out.println(student));
}
@Test
public void testNotIn(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
queryWrapper.notIn("name","张三","李四");
Liststudents=mapper.selectList(queryWrapper);
students.forEach(student -> System.out.println(student));
}
inSql,notInSql
inSql常用来做子查询类似 in()
notInSql 类似notIn()
@Test
public void testInSql(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
queryWrapper.inSql("age","select age from student where id = 1");
Liststudents=mapper.selectList(queryWrapper);
students.forEach(student -> System.out.println(student));
}
@Test
public void testNotInSql(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
queryWrapper.notInSql("age","select age from student where id = 1");
Liststudents=mapper.selectList(queryWrapper);
students.forEach(student -> System.out.println(student));
}
groupBy
groupBy基于多个字段分组
@Test
public void testGroupBy(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
queryWrapper.select("name,count(*) personNumbers");
queryWrapper.groupBy("name");
Liststudents=mapper.selectList(queryWrapper);
students.forEach(student -> System.out.println(student));
}
orderByAsc,orderByDesc,orderBy
orderByAsc 按字段降序
orderBy 每个字段指定排序方向
@Test
public void testOrderBy(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
//orderBy(条件内容是否添加到sql语句后面,true为添加,false为不添加)
queryWrapper.orderBy(true,true,"name");
Liststudents=mapper.selectList(queryWrapper);
students.forEach(student -> System.out.println(student));
}
@Test
public void testOrderBy2(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
//添加多个排序字段
queryWrapper.orderBy(true,true,"name")
.orderBy(true,true,"email");
Liststudents=mapper.selectList(queryWrapper);
students.forEach(student -> System.out.println(student));
}
or,and
or 连接条件用or
and 连接条件用and
@Test
public void testOr(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
queryWrapper.eq("name","张三")
.or()
.eq("age",20);
Liststudents=mapper.selectList(queryWrapper);
students.forEach(student -> System.out.println(student));
}
last
last拼接sql语句
@Test
public void testLst(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
queryWrapper.eq("name","张三")
.or()
.eq("age",20)
.last("limit 1");
Liststudents=mapper.selectList(queryWrapper);
students.forEach(student -> System.out.println(student));
}
exists,notExists
exists 拼接 EXISTS ( sql语句 )
notExists:是相反的 *** 作
@Test
public void testExist(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
queryWrapper.exists("select id from student where age>20");
Liststudents=mapper.selectList(queryWrapper);
students.forEach(student -> System.out.println(student));
}
@Test
public void testNotExist(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
queryWrapper.notExists("select id from student where age>20");
Liststudents=mapper.selectList(queryWrapper);
students.forEach(student -> System.out.println(student));
}
分页
前提:配置分页插件,实现物理分页。默认是内存分页
由于springBoot启动类本身也是一个配置类(@Configuration修饰的类)
可以在springBoot启动类里main方法下面配置分页
package com.example.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(value = "com.example.demo.mapper")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
mybatis-plus 3.4.3.4配置分页插件如下
package com.example.demo.config;
import com.baomidou.mybatisplus.annotation.DbType;
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 Config {
// 最新版
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//DbType.MYSQL:分页使用的语句是mysql类型
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
测试
@Test
public void testPage(){
QueryWrapperqueryWrapper = new QueryWrapper<>();
IPagepage =new Page<>();
//设置分页的数据
page.setCurrent(1);//设置当前页为第一页
page.setSize(3);//每页的记录数为3
IPage result = mapper.selectPage(page,queryWrapper);
//获取分页后的记录
Liststudents = result.getRecords();
students.forEach(student -> {
System.out.println(student);
});
//分页的信息
System.out.println("size="+result.getSize());
System.out.println(result.getPages());
System.out.println("页数:"+result.getPages());
System.out.println("总记录数:"+result.getTotal());
System.out.println("当前的页码:"+result.getCurrent());
System.out.println("每页的记录数:"+result.getSize());
}
mybati-plus官网分页的介绍
通用service crudpackage com.example.demo.service; import com.baomidou.mybatisplus.extension.service.IService; import com.example.demo.entity.Student; public interface StudentService extends IService{ }
package com.example.demo.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.demo.entity.Student; import com.example.demo.mapper.StudentMapper; import com.example.demo.service.StudentService; import org.springframework.stereotype.Service; @Service public class StudentServiceImpl extends ServiceImplimplements StudentService{ }
测试
package com.example.demo;
import com.example.demo.entity.Student;
import com.example.demo.service.StudentService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@SpringBootTest
public class serviceTest {
@Resource
private StudentService studentService;
@Test
public void getList(){
List list=studentService.list();
list.forEach(student -> System.out.println(student));
}
@Test
public void query(){
Student student = studentService.getById(1);
System.out.println(student);
}
@Test
public void insetBath(){
Listlist =new ArrayList<>();
Student student = new Student();
student.setName("测试1");
student.setEmail("123@163.com");
student.setAge(23);
student.setStatus(1);
Student student2 = new Student();
student2.setName("测试2");
student2.setEmail("124@163.com");
student2.setAge(24);
student2.setStatus(1);
list.add(student);
list.add(student2);
boolean rows = studentService.saveBatch(list);
System.out.println("rows="+rows);
}
@Test
public void testSaveOrUpdate(){
Student stu = new Student(8,"测试2",22,"bxq7342@163.com",1);
boolean flag = studentService.saveOrUpdate(stu);
System.out.println("flag="+flag);
}
@Test
public void testSaveOrUpdate2(){
Student stu = new Student(11,"测试11",11,"11@163.com",1);
boolean flag = studentService.saveOrUpdate(stu);
System.out.println("flag="+flag);
}
@Test
public void removeById(){
Listids = Arrays.asList(8,9);
boolean flag = studentService.removeByIds(ids);
System.out.println("flag="+flag);
}
}
分页查询
在配置好分页插件之后,分页查询才会生效
package com.example.demo;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.entity.Student;
import com.example.demo.service.StudentService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@SpringBootTest
public class serviceTest {
@Resource
private StudentService studentService;
@Test
public void page(){
IPageiPage =new Page<>(1,2);
IPagepage = studentService.page(iPage);
Listlist =page.getRecords();
System.out.println(list);//[Student(id=1, name=丽斯, age=20, email=lisi@163.com, status=1), Student(id=2, name=李四, age=20, email=lisi@163.com, status=1)]
System.out.println(page.getPages());//4
}
}
条件构造器
package com.example.demo;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.entity.Student;
import com.example.demo.service.StudentService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@SpringBootTest
public class serviceTest {
@Resource
private StudentService studentService;
@Test
public void contextLoad(){
QueryWrapperqueryWrapper=new QueryWrapper<>();
queryWrapper.select("id","name","age","email","status")
.eq("age",20);
studentService.list(queryWrapper).forEach(student -> System.out.println(student));
}
@Test
public void updateWrapperTest(){
UpdateWrapperupdateWrapper =new UpdateWrapper<>();
updateWrapper.set("age",20)
.eq("name","张三");
boolean flag = studentService.update(updateWrapper);
System.out.println("flag="+flag);
}
@Test
public void queryWrapperTest() {
QueryWrapper queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().select(Student::getId, Student::getName, Student::getAge, Student::getEmail, Student::getStatus)
.eq(Student::getAge, 20);
studentService.list(queryWrapper).forEach(student -> System.out.println(student));
}
}
id生成策略
全局id生成策略配置:
在全局配置文件中,配置id生成策略,就不需要在每个实体类上添加主键配置了
mybatis-plus:
global-config:
db-config:
id-type: auto
逻辑删除
物理删除:在删除的时候,直接将数据从数据库中删除
逻辑删除:从逻辑层面控制删除,通常会在表里添加一个逻辑删除的字段比如enabled,is_delete,数据默认是有效的(值为1),当用户删除时将数据修改
update 0,在查询的时候就只查where enabled = 1,
1.需要添加逻辑删除的字段
2.局部单表逻辑删除,需要在对应的pojo类加入对应的逻辑删除标识字段
package com.example.demo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
@TableId(value = "id",type = IdType.AUTO)
private Integer id;
private String name;
private Integer age;
private String email;
@TableLogic //代表逻辑删除
private Integer status;
}
全局逻辑删除,如果进行了全局逻辑删除配置并且指定了,就可以不用在每个实体类中配置了 @TableLogic
mybatis-plus:
global-config:
db-config:
logic-delete-field: status # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
测试
有逻辑删除,mp封装的list()会自动过滤逻辑删除状态为已删除的字段
package com.example.demo;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.entity.Student;
import com.example.demo.service.StudentService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@SpringBootTest
public class serviceTest {
@Resource
private StudentService studentService;
@Test
public void logicDel(){
studentService.removeById(1);
}
@Test
public void listTest(){
Listlist=studentService.list();
list.forEach(student -> System.out.println(student));
}
}
数据自动填充
官网介绍
- 在实体类的属性上添加@TableField(fill = FieldFill.INSERT) ,标记填充字段
package com.example.demo.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
@TableId(value = "id",type = IdType.AUTO)
private Integer id;
private String name;
private Integer age;
private String email;
@TableLogic //代表逻辑删除
private Integer status;
// 创建时间:希望在添加是数据的时候填充:当前时间
@TableField(fill = FieldFill.INSERT)
private Date createTime;
//修改时间:希望在修改数据的时候填充当前时间
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
- 自定义实现类 MymetaObjectHandler
package com.example.demo.component;
import com.baomidou.mybatisplus.core.handlers.metaObjectHandler;
import org.apache.ibatis.reflection.metaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class MymetaObjectHandler implements metaObjectHandler {
@Override
public void insertFill(metaObject metaObject) {
// 插入时:创建时间字段为当前时间
this.setFieldValByName("createTime",new Date(),metaObject);
}
@Override
public void updateFill(metaObject metaObject) {
// 修改时:修改时间字段为当前时间
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
测试
package com.example.demo;
import com.example.demo.entity.Student;
import com.example.demo.service.StudentService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
@SpringBootTest
public class AutoTest {
@Resource
private StudentService studentService;
@Test
public void test(){
Student student = new Student();
student.setName("张三");
student.setAge(23);
student.setStatus(0);
student.setEmail("zhansan@163.com");
studentService.save(student);
}
@Test
public void update(){
Student student = new Student();
student.setId(1);
student.setName("张三");
student.setAge(22);
student.setStatus(0);
student.setEmail("zhansan@163.com");
boolean flag = studentService.updateById(student);
System.out.println("flag="+flag);
}
}
执行 SQL 分析打印
添加maven依赖
p6spy p6spy3.9.1
mysql中sql分析打印配置
spring:
datasource:
#url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
#driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql://127.0.0.1:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
在resources目录下添加spy.properties,配置如下:
#3.2.1以上使用 modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory #3.2.1以下使用或者不配置 #modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory # 自定义日志打印 logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger #日志输出到控制台 appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger # 使用日志系统记录 sql #appender=com.p6spy.engine.spy.appender.Slf4JLogger # 设置 p6spy driver 代理 deregisterdrivers=true # 取消JDBC URL前缀 useprefix=true # 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset. excludecategories=info,debug,result,commit,resultset # 日期格式 dateformat=yyyy-MM-dd HH:mm:ss # 实际驱动可多个 #driverlist=org.h2.Driver # 是否开启慢SQL记录 outagedetection=true # 慢SQL记录标准 2 秒 outagedetectioninterval=2
执行测试语句
@Test
public void update(){
Student student = new Student();
student.setId(1);
student.setName("张三");
student.setAge(22);
student.setStatus(0);
student.setEmail("zhansan@163.com");
boolean flag = studentService.updateById(student);
System.out.println("flag="+flag);
}
控制台输出,显示当前sql执行所消耗时间
防止删库跑路
1.得到16位随机密钥
package com.example.demo;
import com.baomidou.mybatisplus.core.toolkit.AES;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class randomKeyTest {
@Test
public void test(){
String randomKey = AES.generateRandomKey();
}
}
2.根据密钥加密数据库连接信息
package com.example.demo;
import com.baomidou.mybatisplus.core.toolkit.AES;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class randomKeyTest {
@Test
public void test(){
String randomKey = AES.generateRandomKey();
String username = AES.encrypt("root",randomKey);
String pwd = AES.encrypt("root",randomKey);
System.out.println(username);
System.out.println(pwd);
}
}
3.修改配置文件
username: mpw:3vOuDLOc1ZjXczflw9LCYA==
password: mpw:3vOuDLOc1ZjXczflw9LCYA==
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
4.在部署的时候需要解密
java -jar xxx.jar --mpw.key=你的16位随机密钥
官网介绍
官网介绍
1.修改表结构,添加version字段,默认为1
2.在实体类的字段上加上@Version注解
package com.example.demo.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
@TableId(value = "id",type = IdType.AUTO)
private Integer id;
private String name;
private Integer age;
private String email;
@TableLogic //代表逻辑删除
private Integer status;
// 创建时间:希望在添加是数据的时候填充:当前时间
@TableField(fill = FieldFill.INSERT)
private Date createTime;
//修改时间:希望在修改数据的时候填充当前时间
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
@Version
private Integer version;
}
3.配置乐观锁插件
package com.example.demo.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Config {
// 最新版
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
测试
package com.example.demo;
import com.example.demo.entity.Student;
import com.example.demo.service.StudentService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
@SpringBootTest
public class StudentVersionTest {
@Resource
private StudentService studentService;
@Test
public void testCAS(){
// 线程1 age:50,version:1
Student student1 = studentService.getById(1);
student1.setAge(50);
//线程2 age:100 version:1
Student student2 = studentService.getById(1);
student2.setAge(100);
//update 50 version:2 where version1 = 数据库version
System.out.println(studentService.updateById(student1));/true
//update 100 version:2 where version1 = 数据库version
System.out.println(studentService.updateById(student2));//false
if(studentService.updateById(student1)){
System.out.println("更新成功");
}
if(studentService.updateById(student2)){
System.out.println("更新失败,请重新刷新页面并更新");
}
}
}
更新时候,需要设置version字段
代码生成器官网教程
引入依赖
com.baomidou
mybatis-plus-generator
3.5.1
Generator源码地址
GitHub
Gitee
官方文档
Maven仓库坐标
maven依赖
4.0.0 org.springframework.boot spring-boot-starter-parent2.5.6 com.example demo0.0.1-SNAPSHOT demo Demo project for Spring Boot 1.8 org.springframework.boot spring-boot-starter-weborg.springframework.boot spring-boot-starter-testtest com.alibaba druid1.2.8 mysql mysql-connector-java8.0.27 com.baomidou mybatis-plus-boot-starter3.4.3.4 com.baomidou mybatis-plus-generator3.5.1 org.apache.velocity velocity-engine-core2.3 org.projectlombok lombok1.18.22 provided org.springframework.boot spring-boot-maven-pluginalimaven spring plugin alimaven spring plugin https://maven.aliyun.com/repository/spring-plugin
package com.example.demo.builder;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class CodeGenerator {
private String datasource;
private String packageConfig;
private String strategyConfig;
public void execute(){
System.out.println(datasource+" "+packageConfig+" "+strategyConfig);
}
}
package com.example.demo.builder;
public class CodeGeneratorBuilder {
private CodeGenerator codeGenerator;
public CodeGeneratorBuilder(CodeGenerator codeGenerator){
this.codeGenerator=codeGenerator;
}
public static CodeGeneratorBuilder create(String datasource){
CodeGenerator cg = new CodeGenerator();
cg.setDatasource(datasource);
return new CodeGeneratorBuilder(cg);
}
public CodeGeneratorBuilder packageConfig(String packageConfig){
codeGenerator.setPackageConfig(packageConfig);
return this;
}
public CodeGeneratorBuilder strategyConfig(String strategyConfig){
this.codeGenerator.setStrategyConfig(strategyConfig);
return this;
}
public void execute(){
codeGenerator.execute();
}
}
测试
package com.example.demo.builder;
public class Test {
public static void main(String[] args) {
//普通模式
CodeGenerator codeGenerator = new CodeGenerator();
codeGenerator.setDatasource("mysql");
codeGenerator.setPackageConfig("com.baomidou");
codeGenerator.setStrategyConfig("驼峰命名");
codeGenerator.execute();
//构建者模式
CodeGeneratorBuilder.create("mysql").packageConfig("com.baomidou").strategyConfig("驼峰命名").execute();
}
}
输出
数据表创建语句
drop table if exists `t_simple`;
create table `t_simple`
(
id int primary key auto_increment comment 'id',
name varchar(50) comment '姓名',
age int comment '年龄',
delete_flag tinyint(1) comment '删除标识1',
deleted tinyint(1) comment '删除标识2',
version bigint comment '版本',
create_time datetime comment '创建时间',
update_time datetime comment '更新时间'
) COMMENT = '测试表';
package com.example.demo.generator;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
public class CodeGenerator {
static final String URL = "jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf8";
public static void main(String[] args) {
FastAutoGenerator.create(URL, "root", "root")
// 全局配置
.globalConfig(builder -> builder.outputDir("D:\code"))
// 策略配置
.strategyConfig(builder -> builder.addInclude("t_simple"))
//执行
.execute();
}
}
输出实体类,Mapper,Service,Controller如下
package com.baomidou.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
@TableName("t_simple")
public class TSimple implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private String name;
private Integer age;
private Boolean deleteFlag;
private Boolean deleted;
private Long version;
private LocalDateTime createTime;
private LocalDateTime updateTime;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Boolean getDeleteFlag() {
return deleteFlag;
}
public void setDeleteFlag(Boolean deleteFlag) {
this.deleteFlag = deleteFlag;
}
public Boolean getDeleted() {
return deleted;
}
public void setDeleted(Boolean deleted) {
this.deleted = deleted;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
@Override
public String toString() {
return "TSimple{" +
"id=" + id +
", name=" + name +
", age=" + age +
", deleteFlag=" + deleteFlag +
", deleted=" + deleted +
", version=" + version +
", createTime=" + createTime +
", updateTime=" + updateTime +
"}";
}
}
package com.baomidou.mapper; import com.baomidou.entity.TSimple; import com.baomidou.mybatisplus.core.mapper.baseMapper; public interface TSimpleMapper extends baseMapper{ }
package com.baomidou.service; import com.baomidou.entity.TSimple; import com.baomidou.mybatisplus.extension.service.IService; public interface ITSimpleService extends IService{ }
package com.baomidou.service.impl; import com.baomidou.entity.TSimple; import com.baomidou.mapper.TSimpleMapper; import com.baomidou.service.ITSimpleService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; @Service public class TSimpleServiceImpl extends ServiceImplimplements ITSimpleService { }
package com.baomidou.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.stereotype.Controller;
@Controller
@RequestMapping("/tSimple")
public class TSimpleController {
}
官网代码生成器(3.5.1+)
package com.example.demo.generator;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import java.util.Collections;
public class CodeGenerator {
static final String URL = "jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf8&allowPublicKeyRetrieval=true&useSSL=false";
public static void main(String[] args) {
FastAutoGenerator.create(URL, "root", "root")
.globalConfig(builder -> {
builder.author("baomidou") // 设置作者
.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir("D://"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("com.baomidou.mybatisplus.samples.generator") // 设置父包名
.moduleName("system") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://")); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.addInclude("t_simple") // 设置需要生成的表名
.addTablePrefix("t_", "c_"); // 设置过滤表前缀
}).execute();
}
}
文件夹格式
实体类
package com.baomidou.mybatisplus.samples.generator.system.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@TableName("t_simple")
@ApiModel(value = "Simple对象", description = "测试表")
public class Simple implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("id")
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@ApiModelProperty("姓名")
private String name;
@ApiModelProperty("年龄")
private Integer age;
@ApiModelProperty("删除标识1")
private Boolean deleteFlag;
@ApiModelProperty("删除标识2")
private Boolean deleted;
@ApiModelProperty("版本")
private Long version;
@ApiModelProperty("创建时间")
private LocalDateTime createTime;
@ApiModelProperty("更新时间")
private LocalDateTime updateTime;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Boolean getDeleteFlag() {
return deleteFlag;
}
public void setDeleteFlag(Boolean deleteFlag) {
this.deleteFlag = deleteFlag;
}
public Boolean getDeleted() {
return deleted;
}
public void setDeleted(Boolean deleted) {
this.deleted = deleted;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
@Override
public String toString() {
return "Simple{" +
"id=" + id +
", name=" + name +
", age=" + age +
", deleteFlag=" + deleteFlag +
", deleted=" + deleted +
", version=" + version +
", createTime=" + createTime +
", updateTime=" + updateTime +
"}";
}
}
数据库配置
- 添加指定数据库驱动依赖jar
- 数据库网络可用,云主机注意!防火墙、网络安全组对应端口是否开放
- 严禁使用数据库关键词命名
- 务必表字段添加注释
- 多schema非默认必须指定
查询数据库中某张表的字段
查询当前数据库中的表
- 生成当前项目目录
- System.getProperty(“user.dir”)+“src/main/java”
- 定义父目录 parent - 自定义输出pathInfo
- 模块化输出指定parent动态路径
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)