Mybatis-plus学习
快速入门
-
导入依赖
<!--mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.0.5</version> </dependency> <!--数据库驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
-
编写实体类
@Data @AllArgsConstructor @NoArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; }
-
编写mapper接口,接口继承BaseMapper
@Repository public interface UserMapper extends BaseMapper<User> { }
-
开启扫描接口
//扫描我们的mapper文件夹 @MapperScan("com.fk.mapper") @SpringBootApplication public class MybatisPlusApplication { public static void main(String[] args) { SpringApplication.run(MybatisPlusApplication.class, args); } }
-
测试
@Test void contextLoads() { // 参数是一个wapper,条件构造器 // 查询全部用户 List<User> users = userMapper.selectList(null); users.forEach(System.out::println); }
配置日志
在application.properties中配置日志
# 配置日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
CRUD扩展
插入操作
-
实体类修改
@Data @AllArgsConstructor @NoArgsConstructor public class User { @TableId(type = IdType.AUTO) private Long id; private String name; private Integer age; private String email; }
-
测试
@Test void testInsert(){ User user = new User(); user.setName("测试插入"); user.setAge(18); user.setEmail("1762845033@qq.com"); int result = userMapper.insert(user);//自动生成id,并且id会自动回填到user对象 System.out.println(result); System.out.println(user); }
主键生成策略
IdType.AUTO | 自增Id |
---|---|
IdType.NONE | 未设置主键 |
IdType.INPUT | 手动输入 |
IdType.ID_WORKER | 默认的全局唯一id |
IdType.UUID | 全局唯一id uuid |
IdType.ID_WORKER_STR | ID_WORKER字符串表示 |
如果设置为INPUT手动输入,并且数据库没有设置自增,则无法插入成功
更新操作
//测试更新
@Test
void testUpdate(){
//所有sql都是自动拼接的
User user = new User();
user.setId(1359425363165110277L);
user.setName("测试更新");
user.setAge(19);
int i = userMapper.updateById(user);
System.out.println(i);
}
自动填充
-
数据库层面
根据阿里巴巴手册,我们每个表都需要 插入时间、更新时间 这两个字段,用于数据跟踪
所以我们在数据库中添加这两个字段 create_time update_time
然后设置默认值为CURRENT_TIMESTAMP
并且update_time字段勾选根据当前时间戳更新
-
代码层面
-
修改实体类,为要自动填充的字段加上 @TableField 注解
//字段添加填充内容 //fill指定填充时机 (DEFAULT,INSERT,UPDATE,INSERT_UPDATE) @TableField(fill = FieldFill.INSERT) private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime;
-
编写自动填充的处理器
@Slf4j @Component //一定不要忘记把处理器加到IOC容器中 public class MyMetaObjectHandler implements MetaObjectHandler { //插入时的填充策略 @Override public void insertFill(MetaObject metaObject) { log.info("start insert fill..."); //MetaObjectHandler setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject) this.setFieldValByName("createTime",new Date(),metaObject); //指定填充字段 this.setFieldValByName("updateTime",new Date(),metaObject); } //更新时的填充策略 @Override public void updateFill(MetaObject metaObject) { log.info("start update fill..."); this.setFieldValByName("updateTime",new Date(),metaObject); } }
-
乐观锁
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
乐观锁:先查询,获取版本号 version = 1
-- A线程
update user set name = "fk", version = version + 1
where id = 2 and version = 1
-- B线程 抢先完成,这个时候 version = 2, 会导致A修改失败!
update user set name = "fk", version = version + 1
where id = 2 and version = 1
测试MP的插件
-
给数据库中增加version字段
说明:
-
支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
-
整数类型下
newVersion = oldVersion + 1
-
newVersion
会回写到entity
中 -
仅支持
updateById(id)
与update(entity, wrapper)
方法 -
在
update(entity, wrapper)
方法下,wrapper
不能复用!!!
-
-
实体类添加version字段
@Version //乐观锁 private Integer version;
-
注册组件
//扫描我们的mapper文件夹 @MapperScan("com.fk.mapper") @EnableTransactionManagement //开启事务 @Configuration public class MybatisPlusConfig { //注册乐观锁插件 @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor(){ return new OptimisticLockerInterceptor(); } }
-
测试
//测试乐观锁成功 @Test public void testOptimisticLocker(){ // 1.查询用户信息 User user = userMapper.selectById(1L); // 2.修改用户信息 user.setName("fk"); user.setEmail("1762845033@qq.com"); // 3.执行更新操作 userMapper.updateById(user); } //测试乐观锁失败 多线程下 @Test public void testOptimisticLocker2(){ //线程1 User user = userMapper.selectById(1L); user.setName("fk111"); user.setEmail("1762845033@qq.com"); //线程2 User user2 = userMapper.selectById(1L); user2.setName("fk222"); user2.setEmail("1762845033@qq.com"); userMapper.updateById(user2); // 线程1可以使用 自旋锁来多次尝试提交! userMapper.updateById(user);// 如果没有乐观锁就会覆盖插队线程的值 }
查询操作
// 测试查询
@Test
public void testSelectById(){
User user = userMapper.selectById(1L);
System.out.println(user);
}
// 测试批量查询
@Test
public void testSelectBatchIds(){
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
users.forEach(System.out::println);
}
// 条件查询之一 map
@Test
public void testSelectBatchIds2(){
HashMap<String, Object> map = new HashMap<>();
//自定义要查询
map.put("name","fk222");
map.put("age", 18);
List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println);
}
分页查询
- 原始limit进行分页
- pageHelper第三方插件
- MP自带的分页插件
如何使用
1)配置拦截器组件即可
// 分页插件
@Bean
public PaginationInterceptor paginationInterceptor(){
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作,true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量, 默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
// paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
2)直接使用
// 测试分页查询
@Test
public void testPage(){
// 参数一:当前页码
// 参数二:页面大小
Page<User> page = new Page<>(2,5);
userMapper.selectPage(page, null);
page.getRecords().forEach(System.out::println);
System.out.println(page.getTotal());
}
删除操作
基本删除操作
// 测试删除
@Test
public void testDeleteById(){
userMapper.deleteById(1359425363165110277L);
}
// 通过id批量删除
@Test
public void testdeleteBatchIds(){
userMapper.deleteBatchIds(Arrays.asList(1359425363165110276L,5L,4L));
}
// 通过map删除
@Test
public void testDeleteMap(){
Map<String,Object> map = new HashMap<>();
map.put("name","tom");
userMapper.deleteByMap(map);
}
逻辑删除
物理逻辑 :从数据库中直接移除
逻辑删除 :在数据库中没有被删除,而是通过一个变量来让它失效 deleted = 0 => deleted = 1
管理员可以查看被删除的记录!防止数据的丢失,类似于回收站!
测试一下!
1)在数据库中增加一个deleted字段
2)在实体类中增加属性
@TableLogic //逻辑删除
private Integer deleted;
3)配置
//逻辑删除组件
@Bean
public ISqlInjector sqlInjector(){
return new LogicSqlInjector();
}
#配置逻辑逻辑删除
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
4)测试删除
数据库中的记录依然存在,但是deleted的值发生了变化
性能分析插件
作用:性能分析拦截器,用于输出每条SQL语句及其执行时间
MP提供了性能分析插件,如果超过这个时间就停止运行!
1)导入插件
/**
该插件很多功能已经不可用了,只能打印执行时间和执行sql语句
*/
@Bean
@Profile({"dev","test"}) // 设置 dev test 环境开启,保证开发效率
public PerformanceInterceptor performanceInterceptor(){
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(1); //ms毫秒为单位 设置sql执行的最大时间,如果超过了则不执行
performanceInterceptor.setFormat(true); //开启格式化支持
return new PerformanceInterceptor();
}
注意:要在SpringBoot中配置环境为dev或者test
条件构造器
测试一
@Test
void contextLoads() {
// 查询name不为空,并且邮箱不为空,年龄大于12岁的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.isNotNull("name")
.isNotNull("email")
.ge("age",12); //大于等于
userMapper.selectList(wrapper).forEach(System.out::println);
}
测试二
@Test
void test2(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name","fk");
User user = userMapper.selectOne(wrapper); // 查询一个数据,出现多个结果用 List 或者 Map
System.out.println(user);
}
测试三
@Test
void test3(){
// 查询年龄在 20-30 岁之间用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age",10,18); // 区间
Integer count = userMapper.selectCount(wrapper);// 查询结果数
System.out.println(count);
}
测试四,记住查看输出的SQL进行分析
// 模糊查询
@Test
void test4(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
// like -> (左和右) -> %o% likeLeft表示 % 在左边
wrapper.notLike("name","fk")
.likeRight("email","t");
List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}
测试五
// 子查询
@Test
void test5(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
// id 在子查询中查询出来
wrapper.in("id","select id from user where id < 3");
List<Object> objects = userMapper.selectObjs(wrapper);// 返回值为List
objects.forEach(System.out::println);
}
测试六
// 排序
@Test
void test6(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
//通过id进行排序
wrapper.orderByDesc("id");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
代码自动生成器
// 代码生成器
public class MyCode {
public static void main(String[] args) {
// 需要构建一个 代码生成器对象
AutoGenerator mpg = new AutoGenerator();
// 配置策略
// 1.全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath+"/src/main/java");
gc.setAuthor("fengkai");
gc.setOpen(false); //是否打开资源管理器
gc.setFileOverride(false); // 是否覆盖原有代码
gc.setServiceName("%sService"); // 去Service的I前缀
//这里也是优先读取数据库中的id策略,如果数据库没有设置,则使用我们自己设置的id策略
gc.setIdType(IdType.ID_WORKER);
gc.setDateType(DateType.ONLY_DATE);
gc.setSwagger2(true);
mpg.setGlobalConfig(gc);
// 2.设置数据源
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUsername("root");
dsc.setPassword("root");
dsc.setUrl("jdbc:mysql://localhost:3306/mybatisplus?useUnicode=true&useSSL=false&serverTimezone=UTC&characterEncoding=utf-8");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
// 3.包的配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("demo"); //模块的包名
pc.setParent("com.fk");
pc.setEntity("entity");
pc.setMapper("mapper");
pc.setService("service");
pc.setController("controller");
mpg.setPackageInfo(pc);
// 4.策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("user"); // 设置要映射的表名
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);
strategy.setLogicDeleteFieldName("deleted");//逻辑删除字段
// 自动填充策略
TableFill createTime = new TableFill("create_time", FieldFill.INSERT);
TableFill updateTime = new TableFill("update_time", FieldFill.INSERT_UPDATE);
ArrayList<TableFill> tableFills = new ArrayList<>();
tableFills.add(createTime);
tableFills.add(updateTime);
strategy.setTableFillList(tableFills);
// 乐观锁
strategy.setVersionFieldName("version");
strategy.setRestControllerStyle(true);
strategy.setControllerMappingHyphenStyle(true); // localhost:8080/hello_id_2
mpg.setStrategy(strategy);
mpg.execute();// 执行
}
}
注意事项
-
AutoGenerator是 com.baomidou.mybatisplus.generator 包下的,不要导错了
-
该有的config配置类还是要配置
-
application.properties中的相应配置还是要写,并没有自动生成