mybatis-plus
Table of Contents generated with DocToc (opens new window)
特性
无侵入∶只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
损耗小:启动即会自动注入基本CURD,性能基本无损耗,直接面向对象操作
强大的CRUD操作∶内置通用Mapper、通用Service,仅仅通过少量配置即可实现单表大部分CRUD操作,更有强大的条件构造器,满足各类使用需求
支持Lambda形式调用:通过Lambda表达式,方便的编写各类查询条件,无需再担心字段写错
支持主键自动生成:支持多达4种主键策略(内含分布式唯一ID生成器- Sequence ),可自由配置,完美解决主键问题
支持ActiveRecord模式:支持ActiveRecord 形式调用,实体类只需继承Model类即可进行强大的CRUD操作
支持自定义全局通用操作∶支持全局通用方法注入( Write once, use anywhere )
内置代码生成器︰采用代码或者Maven插件可快速生成Mapper ,Model 、Service 、Controller层代码,支持模板引擎,更有超多自定义配置等您来使用
内置分页插件:基于MyBatis物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通List查询
分页插件支持多种数据库∶支持MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre,SQLServer等多种数据库
内置性能分析插件:可输出Sql语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
内置全局拦截插件∶提供全表delete 、update 操作智能分析阻断,也可自定义拦截规则,预防误操作
支持数据库 mysql 、mariadb、oracle 、db2、h2 、hsql、sqlite 、postgresql、sqlserver、达梦数据库、虚谷数据库、人大金仓数据库
# 快速开始
新建springboot项目
导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <!-- mybatis-plus依赖--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3.1</version> </dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31编辑数据库配置文件
spring: datasource: username: root password: root url: jdbc:mysql://localhost:3306/study?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=utf8 driver-class-name: com.mysql.cj.jdbc.Driver
1
2
3
4
5
6导入数据表和测试数据
DROP TABLE IF EXISTS user; CREATE TABLE user ( id BIGINT(20) NOT NULL COMMENT '主键ID', name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名', age INT(11) NULL DEFAULT NULL COMMENT '年龄', email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱', PRIMARY KEY (id) );
1
2
3
4
5
6
7
8
9
10DELETE FROM user; INSERT INTO user (id, name, age, email) VALUES (1, 'Jone', 18, 'test1@baomidou.com'), (2, 'Jack', 20, 'test2@baomidou.com'), (3, 'Tom', 28, 'test3@baomidou.com'), (4, 'Sandy', 21, 'test4@baomidou.com'), (5, 'Billie', 24, 'test5@baomidou.com');
1
2
3
4
5
6
7
8实体类
@Data @AllArgsConstructor @NoArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; }
1
2
3
4
5
6
7
8
9mapper
@Repository public interface UserMapper extends BaseMapper<User> { }
1
2
3
4启动类扫描mapper包
@SpringBootApplication @MapperScan("com.zdk.mapper") public class MybatisPlusStudyApplication { public static void main(String[] args) { SpringApplication.run(MybatisPlusStudyApplication.class, args); } }
1
2
3
4
5
6
7测试
@SpringBootTest class MybatisPlusStudyApplicationTests { @Autowired private UserMapper userMapper; @Test void contextLoads() { List<User> userList = userMapper.selectList(null); userList.forEach(System.out::println); } }
1
2
3
4
5
6
7
8
9
10
11
12结果:
user = User(id=1, name=Jone, age=18, email=test1@baomidou.com) user = User(id=2, name=Jack, age=20, email=test2@baomidou.com) user = User(id=3, name=Tom, age=28, email=test3@baomidou.com) user = User(id=4, name=Sandy, age=21, email=test4@baomidou.com) user = User(id=5, name=Billie, age=24, email=test5@baomidou.com)
1
2
3
4
5
# 配置日志输出
使SQL可见,方便调试
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
2
3
# CRUD扩展
插入:
@Test
public void testInsert(){
User user = new User();
user.setName("zdk").setAge(1).setEmail("369365576@qq.com");
int result = userMapper.insert(user);
System.out.println("result = " + result);
System.out.println("user = " + user);
}
2
3
4
5
6
7
8
输出为:
出现现象:id被自动填充,且回写到了user对象里
探究:主键生成策略:
自增id
UUID
zookeeper生成
redis生成
雪花算法(snowflake)
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是∶使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心工5个bit的机器ID )),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生4096个ID),最后还有一个符号位,永远是0。
1
其中,IdType枚举类中包含以下几个常量
public enum IdType {
AUTO(0),//id自增
NONE(1),//不设置
INPUT(2),//手动输入
ASSIGN_ID(3),//默认的全局唯一id策略
ASSIGN_UUID(4);//UUID
private final int key;
private IdType(int key) {
this.key = key;
}
public int getKey() {
return this.key;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
一般使用ASSIGN_ID作为唯一主键生成策略
mybatis-plus主键自增生成策略:在实体类中加注解,IdType为AUTO,同时数据库中字段也要为自增
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
}
2
3
4
5
6
7
测试:此前数据库中只有id为1-5
更新
@Test
public void testUpdate(){
User user = new User();
user.setId(6L).setName("zdk222").setAge(20).setEmail("369365576@qq.com");
int result = userMapper.updateById(user);
System.out.println("result = " + result);
System.out.println("user = " + user);
}
2
3
4
5
6
7
8
mybatis-plus会根据条件自动拼接SQL
自动填充一些字段
创建时间、更新时间等
方式一:数据库级别
- 在表中新增字段create_time,update_time(类型为datetime时)
- 将数据库字段设置为根据当前时间戳更新
方式二:代码层面填充
- 使用tableField注解,fill是填充,值为:当执行insert、update等操作时填充属性
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
2
3
4
5
编写处理器处理注解
自定义自己的metaObjectHandler类,实现MetaObjectHandler接口
@Slf4j @Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { log.info("插入时开始填充--------"); setFieldValByName("createTime",new Date(),metaObject); setFieldValByName("updateTime",new Date(),metaObject); } @Override public void updateFill(MetaObject metaObject) { log.info("更新时开始填充--------"); setFieldValByName("updateTime",new Date(),metaObject); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16使用setFieldValByName方法即可按属性名、要填充的值来填充数据
执行测试即可
# 乐观锁、悲观锁:
乐观锁:它总是认为不会出现问题,无论干什么不去上锁!如果出现了问题,再次更新值测试
悲观锁:它总是认为总是出现问题,无论干什么都会上锁!
乐观锁:当要更新一条记录时,希望这条记录没有被别人更新
乐观锁实现方式:
- 取出记录时,获取当前version
- 更新时,带上这个version,
- 执行更新时,set version =newVersion where version =oldVersion
- 如果version不对,就会更新失败
乐观锁:1、先查询,获得版本号 version = 1
-- A
update user set name = "kuangshen", version = version + 1
where id = 2 and version = 1
--B线程抢先完成,这个时候version = 2,会导致A修改失败!
update user set name = "kuangshen", version = version + 1
where id = 2 and version = 1
-- 最终,A线程更新时version已变为2,A更新就会失败
2
3
4
5
6
7
8
9
10
Java实现乐观锁
数据库表增加version字段(默认值为1),实体类增加version属性
在实体类属性上加注解@Version,表示个属性是个乐观锁
@Version private Integer version;
1
2注册组件:
public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { //新版乐观锁插件 mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return mybatisPlusInterceptor; }
1
2
3
4
5
6
7进行测试:
@Test public void testLock(){ User user1 = userMapper.selectById(8L); user1.setName("乐观锁测试用户1111").setAge(100).setEmail("xxx"); User user2 = userMapper.selectById(8L); user2.setName("乐观锁测试用户2222").setAge(202).setEmail("aaaa"); userMapper.updateById(user2); //线程user1被插队,version变为了3,但user1执行时还是按version为2区更新,所以更新失败 userMapper.updateById(user1); }
1
2
3
4
5
6
7
8
9
10
11测试结果:
# 基本查询操作
@Test
public void testSelectMethod(){
//1 查询list
List<User> users = userMapper.selectList(null);
//2 按id查询
User user = userMapper.selectById(8L);
//3 按id批量查询
List<User> userList = userMapper.selectBatchIds(Arrays.asList(1, 2, 3, 4));
//4 通过map进行条件查询:查询name=张迪凯的数据
HashMap<String, Object> params = new HashMap<>();
params.put("name", "张迪凯");
List<User> list = userMapper.selectByMap(params);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
mybatis-plus分页查询插件:
如何使用
配置拦截器组件
public class MybatisPlusConfig { /** * 新版 */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); //新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false // 避免缓存出现问题(该属性会在旧插件移除后一同移除) mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); //乐观锁插件 mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return mybatisPlusInterceptor; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14new出Page泛型对象并设置页数和每页size,然后进行查询,结果回显到Page对象
@Test public void testPage(){ //参数一:当前页;参数二:页面大小 Page<User> pages = new Page<>(1,3); //执行分页查询 userMapper.selectPage(pages, null); //获取分页查询结果 pages.getRecords().forEach(System.out::println); System.out.println("pages.getTotal() = " + pages.getTotal()); }
1
2
3
4
5
6
7
8
9
10
# 删除操作
@Test
public void testDelete(){
//1.通过id删除
int delete = userMapper.deleteById(1L);
System.out.println("delete = " + delete);
//2.批量删除
int batchIds = userMapper.deleteBatchIds(Arrays.asList(1, 2, 3));
System.out.println("batchIds = " + batchIds);
//3 通过map进行条件删除:删除name=张迪凯的数据
HashMap<String, Object> params = new HashMap<>();
params.put("name", "张迪凯");
int deleteByMap = userMapper.deleteByMap(params);
System.out.println("deleteByMap = " + deleteByMap);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 逻辑删除
物理删除:从数据库直接移除
逻辑删除:没有从数据库移除。是使用一个变量来标记这条数据已"被删除",只是不查询出来deleted=0->1
管理员可以查看被删除的记录。防止数据丢失,类似于回收站
需要在数据表中新加deleted字段,类型为tinyint,默认值为0,当"被删除"时,set为1
步骤1.配置
com.baomidou.mybatisplus.core.config.GlobalConfig$DbConfig
mybatis-plus: global-config: db-config: logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2) logic-delete-value: 1 # 逻辑已删除值(默认为 1) logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
1
2
3
4
5
6步骤2.实体类添加deleted字段,并加上注解@TableLogic(since 3.3.0,配置后可以忽略不配置)
@TableLogic private Boolean deleted;
1
2测试
@Test public void testLogicDelete(){ userMapper.deleteById(1L); }
1
2
3
4实际走的是更新操作:
==> Preparing: UPDATE user SET deleted=1 WHERE id=? AND deleted=0 ==> Parameters: 1(Long) <== Updates: 1
1
2
3这样删除以后再通过id去查这条数据,就查询不到了,但数据库中数据还在。
# 条件构造器
使用queryWrapper或updateWrapper构造查询条件,类似以下
@Test
public void testWrapper(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.ge("id", 4);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
2
3
4
5
6
7
使用LambdaQueryWrapper
构造LambdaQueryWrapper对象,传入参数执行查询
@Test public void testLambdaQueryWrapper(){ LambdaQueryWrapper<User> lambdaQueryWrapper = new QueryWrapper<User>().lambda(); lambdaQueryWrapper.eq(User::getId,5); User user = userMapper.selectOne(lambdaQueryWrapper); System.out.println("user = " + user); }
1
2
3
4
5
6
7
# 代码生成器
public class MybatisPlusCodeGenerator {
public static void main(String[] args) {
AutoGenerator generator = new AutoGenerator();
//配置生成策略
//1.全局配置
GlobalConfig config = new GlobalConfig();
//获取当前目录
String projectPath = System.getProperty("user.dir");
config.setOutputDir(projectPath+"/src/main/java");
config.setAuthor("zdk");
//设置生成后是否打开对应文件夹
config.setOpen(false);
//设置生成是否覆盖原来的文件
config.setFileOverride(false);
//去掉生成的service的I前缀
config.setServiceName("%sService");
config.setServiceImplName("%sServiceImpl");
config.setIdType(IdType.AUTO);
config.setDateType(DateType.ONLY_DATE);
config.setSwagger2(false);
generator.setGlobalConfig(config);
//2.配置数据源
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setDbType(DbType.MYSQL);
dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/study?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=utf8");
dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
dataSourceConfig.setUsername("root");
dataSourceConfig.setPassword("root");
generator.setDataSource(dataSourceConfig);
//3.包的配置
PackageConfig packageConfig = new PackageConfig();
packageConfig.setModuleName("genTest");
packageConfig.setParent("com.zdk");
packageConfig.setEntity("entity");
packageConfig.setMapper("mapper");
packageConfig.setService("service");
packageConfig.setServiceImpl("service.serviceImpl");
packageConfig.setController("controller");
generator.setPackageInfo(packageConfig);
//4.策略配置
StrategyConfig strategyConfig = new StrategyConfig();
//设置要(读取)映射的表
strategyConfig.setInclude("user");
strategyConfig.setNaming(NamingStrategy.underline_to_camel);
strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
//自动lombok
strategyConfig.setEntityLombokModel(true);
//设置逻辑删除
strategyConfig.setLogicDeleteFieldName("deleted");
//设置自动填充
TableFill tableInsert = new TableFill("create_time", FieldFill.INSERT);
TableFill tableUpdate = new TableFill("update_time", FieldFill.INSERT_UPDATE);
ArrayList<TableFill> tableFills = new ArrayList<>();
tableFills.add(tableInsert);
tableFills.add(tableUpdate);
strategyConfig.setTableFillList(tableFills);
//乐观锁
strategyConfig.setVersionFieldName("version");
//设置controller的驼峰命名格式
strategyConfig.setRestControllerStyle(true);
//请求会变成localhost:8080/hello_id_2
strategyConfig.setControllerMappingHyphenStyle(true);
generator.setStrategy(strategyConfig);
//执行生成
generator.execute();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73