创作不易,各位看官点赞收藏.
Mybatis-Plus官网:https://baomidou.com/
MyBatis-Plus(MP):是一个 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
特点:
导入依赖:基于一个 Spring-Boot 整合 mybatis-plus。
<dependencies>
<dependency>
<groupId>com.mysqlgroupId>
<artifactId>mysql-connector-jartifactId>
<version>8.0.32version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.5.3version>
dependency>
dependencies>
编写 yaml 配置文件:
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/mybatis_plus?useUnicode=true&characterEncoding=utf-8&serveTimezone=UTC
username: root
password: 1234567
# mybatis-plus 配置
mybatis-plus:
# setting
configuration:
# 设置打印日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 开启驼峰映射
map-underscore-to-camel-case: true
# mapper 映射文件存放位置
mapper-locations: classpsth:mapper/**/*.xml
# 别名扫描包
type-aliases-package: com.jx.app
编写实体类:
@Data
// 实体对应数据库中表名
@TableName("user")
public class User {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String name;
private Integer age;
private String email;
private LocalDateTime createTime;
private LocalDateTime updateTime;
private Integer version;
private Integer deleted;
}
编写 mapper 接口、service 接口、mapper 映射文件:
// 只需要继承BaseMapper这个接口,这个接口的实现类就是CRUD代码,注意需要传入一个实体的泛型
public interface UserMapper extends BaseMapper<User> {
}
// service 接口文件,需要继承 IService 接口
public interface UserService extends IService<User> {
}
// 需要继承 ServiceImpl 实现类,两个泛型 mapper 接口和实体类
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
使用:
@SpringBootApplication
// mapper 接口的扫描包
@MapperScan({"com.jx.app.**.mapper"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class MyTest {
@Resource
private UserService userService;
@Test
public void test1(){
// userService 继承了很多方法可以直接使用
List<User> list = userService.list();
System.out.println(list);
// 也可以获取 mapper 接口,也可以使用对应封装了的方法
BaseMapper<User> baseMapper = userService.getBaseMapper();
User user = baseMapper.selectById(1);
System.out.println(user);
}
}
@TableName:作用在实体类上要和数据库表相对应,用于在动态生成 SQL 是确定表名,如果不设置这个注解默认使用实体类名第一个字母小写作为表名。
mybatis-plus:
global-config:
db-config:
# 全局表前缀
table-prefix: 't_'
@TableId:作用于属性上,表示这个数据为数据库表中的主键,如果实体类不设置这个注解,默认会把 id 名称的属性作为组件,如果连 id 属性名都没有,对于一些主键操作方法会报错。
// 将对应字段指定为主键
/**
* value属性:如果属性名称与数据库字段名称对应不上,可以使用 value 属性进行映射
* type属性:指定主键生成策略
* IdType.AUTO:数据库自增
* IdType.INPUT:用户输入
* IdType.ASSIGN_ID:如果插入数据 id 是空的,会使用雪花算法自动生成 id(默认是雪花算法)
* IdType.ASSIGN_UUID:使用 UUID 生成 id
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
全局设置主键生成策略:
# mybatis-plus 配置
mybatis-plus:
global-config:
db-config:
table-prefix: 't_'
# 全局主键生成策略
id-type: [assign_uuid|assign_id|auto|input|none]
雪花算法:是 Twitter 开源的分布式 id 生成算法。其核心思想就是:使用一个 64 bit 的 long 型的数字作为全局唯一 id。在分布式系统中的应用十分广泛,且ID 引入了时间戳,基本上保持自增的,后面的代码中有详细的注解。这 64 个 bit 中,其中 1 个 bit 是不用,然后用其中的 41 bit 作为毫秒数,用 10 bit 作为工作机器 id,12 bit 作为序列号。
@TableField:作用在属性上。
// 映射属性名与数据库字段名
/**
* value:映射的数据库字段名
* exist:boolean 值,指定这个属性在数据库表中是否存在,默认 true。
*/
@TableField(value = "name", exist = true)
private String name;
Wrapper 条件构造器:一些复杂的 sql 语句可以使用条件构造器来进行数据的查询,在 sql 能执行的一般 Wrapper 都可以实现。
QueryWrapper:查询封装条件构造器,用于查询或则删除。
@Test
public void test2(){
QueryWrapper<OssFile> wrapper = new QueryWrapper<>();
// 进行查询条件构造
wrapper.eq("file_name","test 1");
// 可以使用链式编程去构造条件
wrapper.groupBy("create_by").orderByAsc("id");
// 通过构造器去查询
List<OssFile> list = fileService.list(wrapper);
list.forEach(System.out::println);
}
UpdateWrapper:更新封装条件构造器,用于更新数据。
@Test
public void test3(){
UpdateWrapper<OssFile> wrapper = new UpdateWrapper<>();
// 进行修改条件构造器
wrapper.eq("file_name", "test 1").set("file_name", "测试数据");
// 进行更新操作
fileService.update(wrapper);
}
LambdaQueryWrapper | LambdaUpdateWrapper:使用 Lambda 表达式构建查询|更新条件构造器。
@Test
public void test4(){
LambdaQueryWrapper<OssFile> lambdaQueryWrapper = new LambdaQueryWrapper<>();
LambdaUpdateWrapper<OssFile> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
// 使用 lambda 表达式去完成指定字段
lambdaQueryWrapper.eq(OssFile::getFileName,"test 2");
lambdaUpdateWrapper.eq(OssFile::getFileName,"test 2").set(OssFile::getFileName,"测试 2");
// 查询数据
List<OssFile> list = fileService.list(lambdaQueryWrapper);
// 更新数据
fileService.update(lambdaUpdateWrapper);
list.forEach(System.out::println);
}
Wrappers:一个工具类可以创建所有的 wrapper 的示例,建议使用。
@Test
public void test7(){
QueryWrapper<OssFile> queryWrapper = Wrappers.query();
UpdateWrapper<OssFile> updateWrapper = Wrappers.update();
LambdaQueryWrapper<OssFile> lambdaQueryWrapper = Wrappers.lambdaQuery();
LambdaUpdateWrapper<OssFile> lambdaUpdateWrapper = Wrappers.lambdaUpdate();
}
条件优先级调整:对于一些查询条件可能存在执行的先后顺序。
@Test
public void test5(){
QueryWrapper<OssFile> wrapper = new QueryWrapper<>();
// and|or方法:相当于给条件加了一个括号,可以改变它的优先级,参数是一个消费型函数接口,就是 wrapper,
// 然后再通过 wrapper 参数进行条件构建
wrapper.eq("file_name","test 1").and(e->{
e.eq("url","test").or().eq("id","1");
});
// 通过构造器去查询
List<OssFile> list = fileService.list(wrapper);
list.forEach(System.out::println);
}
构建查询字段条件:可以指定查询哪些字段数据,不用把表中的所有字段查询出来。
@Test
public void test6(){
QueryWrapper<OssFile> wrapper = new QueryWrapper<>();
// 指定查询的字段,其它没有查询的属性全部为默认值
wrapper.select("file_name","id","url");
// 通过构造器去查询
List<OssFile> list = fileService.list(wrapper);
list.forEach(System.out::println);
}
构建子查询:可以使用构造器去创建子查询,然后把子查询作为一个表数据进行操作。
@Test
public void test8(){
QueryWrapper<OssFile> queryWrapper = Wrappers.query();
// 相当于使用in关键字拆查询,第二个参数是子查询的 SQL 语句
queryWrapper.inSql("id","select id from oss_file");
List<OssFile> list = fileService.list(queryWrapper);
list.forEach(System.out::println);
}
条件名 | 解释 | 例子 |
---|---|---|
allEq(map,boolean) | 参数为一个map,key为字段的名称,value是对应字段的值,第二个参数为true时值为null的条件会被解析为is Null,为false时会忽略值为null的数据。(默认为true) | allEq({id:1,name:"老王",age:null})--->id = 1 and name = '老王' and age is null |
eq(v1,v2) | v1为数据库字段名,v2是字段对应的值,选择出v1字段等于v2的数据 | eq("name", "老王")--->name = '老王' |
nq(v1,v2) | 不等于 | ne("name", "老王")--->name <> '老王' |
gt(v1,v2) | 大于 | gt("age", 18)--->age > 18 |
ge(v1,v2) | 大于等于 | ge("age", 18)--->age >= 18 |
lt(v1,v2) | 小于 | lt("age", 18)--->age < 18 |
le(v1,v2) | 小于等于 | le("age", 18)--->age <= 18 |
between(v1,v2,v3) | v1为数据库的字段名,v1字段的值在v2和v3之间的数据 | between("age", 18, 30)--->age between 18 and 30 |
notBetween(v1,v2,v3) | v1的值不在v2和v3之间 | notBetween("age", 18, 30)--->age not between 18 and 30 |
like(v1,v2) | v1是字段名,v2是值,相当于是一个模糊查询%v2% |
like("name", "王")--->name like '%王%' |
notLike(v1,v2) | 排除包括模糊查询的其他内容 | notLike("name", "王")--->name not like '%王%' |
likeLeft(v1,v2) | 相当于%v2 ,值得左边进行模糊查询 |
likeLeft("name", "王")--->name like '%王' |
likeRight(v1,v2) | 相当于v2% ,值得右边进行模糊查询 |
likeRight("name", "王")--->name like '王%' |
isNull(v1,v2) | v1字段名,v2字段值,查询v1字段值为null的数据 | isNull("name")--->name is null |
isNotNull(v1,v2) | 查询v1的值不为null的数据 | isNotNull("name")--->name is not null |
in(v1,v2) | v1字段名,v2是一个集合,查询v1字段中值是v2集合中的值 | in("age",{1,2,3})--->age in (1,2,3) |
notIn(v1,v2) | 查询v1字段的值不是v2集合中的值 | notIn("age",{1,2,3})--->age not in (1,2,3) |
inSql(v1,v2) | v1字段名,v2是一个子查询的sql语句,将查询的结果作为v1字段的in来使用(查询的字段和v1的字段名需要保持一致) | inSql("id", "select id from table where id < 3")--->id in (select id from table where id < 3) |
notInSql(v1,v2) | 不包含v2sql语句中查询到的结果 | notInSql("id", "select id from table where id < 3")--->id not in (select id from table where id < 3) |
groupBy(v…) | 按照字段进行分组查询,可以传入多个字段 | groupBy("id", "name")--->group by id,name |
orderByAsc(v…) | 按照字段将查询的结果进行升序 排序 |
orderByAsc("id", "name")--->order by id ASC,name ASC |
orderByDesc(v…) | 按照字段将查询的结果进行降序 排序 |
orderByDesc("id", "name")--->order by id DESC,name DESC |
having(v1,v2) | v1是sql语句,v2是可变参数 | having("sum(age) > {0}", 11)--->having sum(age) > 11 |
or() | 主动调用or表示紧接着下一个方法不是用and连接!(不调用or则默认为使用and连接) |
eq("id",1).or().eq("name","老王")--->id = 1 or name = '老王' |
and() | `and(i -> i.eq(“name”, “李白”).ne(“status”, “活着”))—>and (name = ‘李白’ and status <> ‘活着’) |
乐观锁:总是认为不会出现问题,所以无论干什么就不会去上锁,如果出现问题就加锁测试。
悲观锁:无论干什么都会觉得出现问题,所有所有操作都会加上锁然后操作。
set version = newVersion where version = oldVersion
( newVersion 就是原来的 version+1)。现在有 A 线程在执行更新数据的操作(获取到的 version 为1),但是还没有确定。突然B线程也来修改这一条记录,获取到 version 为1,执行完更新后的版本version就为2,这时 A 线程继续执行操作,发现原先取出来的 version(1) 和表中新的 version(2) 不一样,这就会跟新失败。实际就是在sql语句中添加了一个and 条件判断。
Mybatis-Plus实现乐观锁:
@Version
注解,表示是一个乐观锁。@Version
private Integer version;
MybatisPlusConfig
@Configuration
public class MybatisPlusConfig {
// 添加Mybatis-Plus的插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加乐观锁的插件,也可以添加其他的插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
@Test
public void updateTest(){
// 使用乐观锁之前需要将这个实体的version查询出来携带到查询实体中
User user = userMapper.selectById(1484525749204516866L);
user.setName("赵六");
int nums = userMapper.updateById(user);
System.out.println(nums);
}
@Test
public void updateTest1(){
// 线程A
User user = userMapper.selectById(1484525749204516866L);
user.setName("田七");
// 线程B
User user2 = userMapper.selectById(1484525749204516866L);
user.setName("张三");
// 线程B比线程A先执行操作,并更新了version为3
userMapper.updateById(user2);
// 线程A后执行更新操作,发现version不合法,更新就不会成功
userMapper.updateById(user);
}
支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
整数类型下 newVersion = oldVersion + 1
newVersion会回写到
entity中仅支持
updateById(id)与
update(entity, wrapper)` 方法
在 update(entity, wrapper)
方法下, wrapper
不能复用 !!!
Mybatis-Plus 中内置了分页插件,通过简单的分页插件的配置就可以使用了。
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 使用对应的插件
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
// 分页的测试
@Test
public void pageTest(){
// 参数1:查询的页码(为负数默认为第一页); 参数2:一页的数量(为负数就查询所有的数据); 泛型的查询的实体类
Page<User> page = new Page<>(1, -1);
// 进行查询,参数1:Page对象,第二个参数是Wrapper条件对象
// 查询完成后,将查询的数据都封装到了传入的page对象中
userMapper.selectPage(page,null);
// 获取查询的数据
List<User> users = page.getRecords();
for (User user : users) {
System.out.println(user);
}
System.out.println("================");
// 数据的总页数
long pages = page.getPages();
System.out.println(pages);
System.out.println("================");
// 当前的页码
long current = page.getCurrent();
System.out.println(current);
System.out.println("================");
// 一页的数量
long size = page.getSize();
System.out.println(size);
System.out.println("================");
// 总的数据数
long total = page.getTotal();
System.out.println(total);
}
物理删除:直接从数据库中删除,数据库中数据就不存在了。
逻辑删除:在数据库中没有被移除,而是通过一个字段表示是否被删除的状态,例如 0 表示未删除1表示已删除,但是并不是从数据库中删除了。
deleted
字段,用于标识是否删除,默认值为 0。在实体类对应字段上使用 @TableLogic
标识为逻辑删除属性。// 逻辑删除的注解
@TableLogic
private Integer deleted;
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# 也可以不用设置
logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值
logic-not-delete-value: 0 # 逻辑未删除值
注意:设置了逻辑删除后,在以后的查询都会在查询 SQL 语句中去拼接对应逻辑删除字段的条件。
在一些操作中某一些操作不需要手动去完成,直接可以通过自动去完成操作。例如创建的时间和更新的时间,直接通过系统的时间自动去完成操作。
数据库自动填充:添加
create_time
和update_time
两个字段,这两个字段的是timestamp
类型,在数据库中添加配置就能完成自动操作。
这样向表中添加数据时,create_time
字段就会有一个当前系统的默认值;更新的时候,update_time
字段的日期也会更新,即使 sql
语句中没有更新时间。但是在实际工作中不会接触到数据库,只会进行sql
的操作。
Mybatis-Plus 自动填充:
Mybatis-Plus
使用的是代码来进行的自动填充,进行下面操作完成。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
// 当插入的时候进行填充
@TableField(fill = FieldFill.INSERT)
private Date createTime;
// 当插入和更新的时候自动填充
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
// 需要将这个处理类放入spring容器中
@Component
public class MyMetaObjectHandler implements MetaObjectHandler{
// 需要实现MetaObjectHandler接口,并实现这两个方法
// 插入时的自动填充
@Override
public void insertFill(MetaObject metaObject) {
// 三个参数,(填充的实体属性名、填充的值、metaObject)
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
// 更新时的自动填充
@Override
public void updateFill(MetaObject metaObject) {
// 三个参数,(填充的实体属性名、填充的值、metaObject)
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-generatorartifactId>
<version>3.5.1version>
dependency>
public static void main(String[] args) {
// 获取项目的根目录
String projectPath = System.getProperty("user.dir");
System.out.println(projectPath);
// 代码生成
FastAutoGenerator.create("jdbc:mysql://127.0.0.1:3306/mybatis_plus?useUnicode=true&characterEncoding=utf-8&serveTimezone=UTC",
"root", "1234567")
.globalConfig(builder -> {
builder.author("xiaotanke") // 设置作者
.enableSwagger() // 开启 swagger 模式
// .fileOverride() // 覆盖已生成文件
.disableOpenDir() // 不打开资源管理器
.outputDir(projectPath); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("com.xiaotanke") // 设置父包名
.moduleName("system") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.mapperXml, projectPath)); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.addInclude("user") // 设置需要生成的表名
.addTablePrefix("t_", "c_"); // 设置过滤表前缀
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
}