Mybatis-plus
一、快速入门
1、导入Jar包
<!-- 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- lombok表达式-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
**说明:**我们使用 mybatis-plus 可以节省很多时间和代码,尽量不要同时导入mybatis和mybatis-plus版本
2、编写配置文件
# mybatis
spring.datasource.username = root
spring.datasource.password = 2019
spring.datasource.url = jdbc:mysql://localhost:3306/数据库名?useSSL=false&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver
3、创建实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class A_user {
private Long Id;
private String name;
private String password;
}
4、创建Mapper
public interface UserMapper extends BaseMapper<A_user> {
}
5、测试
@Test
void contextLoads() {
List<A_user> a_users = userMapper.selectList(null);
a_users.forEach(System.out::println);
}
二、配置日志
我们所有的Sql都是不可见的,我们希望知道是怎么执行的,所以我们必须配置日志
# 配置日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
三、CRUD扩展
insert 插入 int insert = userMapper.insert(a_user);
数据库生成的ID默认值为:全局的ID
1、主键生成策略
@Test
public void testInset(){
Admin a_user = new Admin();
a_user.setName("西奥");
a_user.setPassword("***");
int insert = userMapper.insert(a_user);
System.out.println(insert);
System.out.println(a_user);
}
@TableId(type = IdType.AUTO)
public enum IdType {
AUTO(0), // 数据库自增
NONE(1), // 未设置主键
INPUT(2), // 手动输入
ID_WORKER(3), // 默认的全局ID
UUID(4), // 全局唯一ID
ID_WORKER_STR(5); // 字符串
2、更新操作
@Test
public void testUpdate(){
Admin a_user = new Admin();
a_user.setId(6L);
a_user.setName("西奥s");
a_user.setPassword("***1");
userMapper.updateById(a_user);
}
所有的sql可以动态配置
3、自动填充
创建时间、修改时间、这个操作一遍都是自动化完成的,我们不希望手动更新!
方式一:数据库级别
1、在表中的新增字段,create_time,update_time
方式二:代码级别
在实体类字符的属性上添加注解
// 字段添加填充内容
@TableField(fill = FieldFill.INSERT)
private Date createTime;
// 添加填充内容
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
实体类的字段属性要操作注解了
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
// 插入时的填充策略
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill......");
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);
}
}
- @TableField(fill = FieldFill.INSERT):执行添加操作时的自动填充
- @TableField(fill = FieldFill.INSERT_UPDATE):执行修改操作时的自动填充
4、乐观锁
- 乐观锁
- 悲观锁
面试高频
**乐观锁:**顾名思义,十分乐观。总也为不会出现问题,无论干什么不会上锁!如果出现了问题再此更新值测试
**悲观锁:**顾名思义,十分悲观。总也为都会出现问题,无论干什么都会上锁!再去操作
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
修改表,添加乐观锁。默认值为1
修改实体类,添加乐观锁属性。添加乐观锁注解(代表此字段为乐观锁)
注册组件
修改标和实体类
// 乐观锁
@Version
private Integer version;
添加配置文件乐观锁
@MapperScan("com.example.demo.mapper")
@EnableTransactionManagement
@Configuration // 配置类
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
**总:**如果设置了乐观锁,当用户执行一次修改后,乐观锁会进行一次迭代,当执行增删改会判断version号是否与上次一致,因为每次修改标时,版本也会跟着修改,所以期间有线程进行了修改,就会版本不一致,修改失败
5、查询
-
单条通过ID查询
@Test public void testSelectById(){ Admin admin = userMapper.selectById(2L); System.out.println(admin); }
-
多条查询通过数组
@Test public void selectBatchIds(){ // 查询数组里的ID List<Admin> adminList = userMapper.selectBatchIds(Arrays.asList(1, 2, 3)); adminList.forEach(System.out::println); }
-
map条件查询
@Test public void testSelectByBatchIds(){ HashMap<String, Object> map = new HashMap<>(); // 自定义要查询的条件 map.put("name","西奥"); map.put("password","****"); userMapper.selectByMap(map); }
6、分页查询
分页在网站使用的十分之多
1、原始的是使用 limit 进行分页
2、第三方插件 pageHelper
3、MybatisPlus也内置了分页插件!
如何使用MybatisPlus的分页插件
添加分页插件的配置
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 配置乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 配置分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
return interceptor;
}
添加测试单元
@Test
public void testPage(){
// 参数1:当前页
// 参数2:页面大小
Page<Admin> objectPage = new Page<>(2,3);
userMapper.selectPage(objectPage,null);
objectPage.getRecords().forEach(System.err::println);
}
7、删除模块
删除单个
// 测试删除
@Test
public void testDeleteById(){
userMapper.deleteById(2L);
}
删除多个
// 批量删除
@Test
public void testDeleteBatchBy(){
userMapper.deleteBatchIds(Arrays.asList(1,2,3,4));
}
添加删除
@Test
public void testDeleteMap(){
HashMap<String, Object> map = new HashMap<>();
map.put("password","****");
userMapper.deleteByMap(map);
}
逻辑删除
- 物理删除:从数据库中直接移除
- 逻辑删除:在数据库中没有被移除,而是通过一个变量来修改,让它失效,
管理员可以查看已经删除的数据,因为管路员可以查看已经失效的账户,类似于回收站!
1、在数据表中增加deletd字段
2、在实体类中更新字段
// 逻辑删除
@TableLogic
private int deleted;
3、添加配置
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)
4、删除字段进行删除
@Test
public void testDeleteById(){
userMapper.deleteById(11L);
}
在进行原来的删除时,会将字段deleted修改为1,代表删除操作
四、性能分析插件
我们在平时的开发中,会遇到一些慢查询
MybatisPlus
也提供了性能分析插件,如果超过这个时间就停止运行!
1、导入依赖
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.9.1</version>
</dependency>
2、测试使用
spring.datasource.url = jdbc:p6spy:mysql://localhost:3306/security?useSSL=false&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name = com.p6spy.engine.spy.P6SpyDriver
3、添加设置配置
#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
注意!
-
driver-class-name 为 p6spy 提供的驱动类
-
url 前缀为 jdbc:p6spy 跟着冒号为对应数据库连接地址
-
打印出sql为null,在excludecategories增加commit
-
批量操作不打印sql,去除excludecategories中的batch
-
批量操作打印重复的问题请使用MybatisPlusLogFactory (3.2.1新增)
-
该插件有性能损耗,不建议生产环境使用。
五、条件构造器
创建对象,用来封装条件
QueryWrapper<Admin> wrapper = new QueryWrapper<>();
设置Wrapper的条件
wrapper.isNotNull("name");
wrapper.isNotNull("password");
wrapper.eq("password","***0");
遍历查出的结果
userMapper.selectList(wrapper).forEach(System.out::println);
-
条件查询单个数据
@Test void contextLoads(){ // 查询name不为空的用户,并且已经删除了的用户 QueryWrapper<Admin> wrapper = new QueryWrapper<>(); wrapper.isNotNull("name"); // 不为空的字段 wrapper.isNotNull("password"); // 不为空的字段 wrapper.eq("name","西奥50"); // 字段等于 wrapper.eq("password","***0"); // 字段等于 userMapper.selectList(wrapper).forEach(System.out::println); }
-
模糊查询
@Test void test3(){ // 查询名字 QueryWrapper<Admin> wrapper = new QueryWrapper<>(); wrapper.likeLeft("name","0") // 左边模糊 .likeRight("name","西"); // 右边模糊 List<Map<String, Object>> maps = userMapper.selectMaps(wrapper); maps.forEach(System.out::println); }
-
自定义Sql查询
@Test void test4(){ // 查询名字 QueryWrapper<Admin> wrapper = new QueryWrapper<>(); // id 在子查询中查询出来 wrapper.inSql("id","select id from admin where id > 50"); List<Map<String, Object>> maps = userMapper.selectMaps(wrapper); maps.forEach(System.out::println); }
-
查询排序
@Test void test6(){ QueryWrapper<Admin> wrapper = new QueryWrapper<>(); wrapper.orderByDesc("id"); // 通过ID进行降序排序 List<Map<String, Object>> maps = userMapper.selectMaps(wrapper); maps.forEach(System.out::println); }