Mybatis进阶02-MybatisPlus
1.修改数据
- 测试用例,SQL自动拼接不为null的字段。
@Test
void testUpdate() {
User user = new User();
user.setId(1011L);
user.setName("");
// 通过id更新,会自动填充更新不为null的列
int result = userMapper.updateById(user);
System.out.println(result);
}
2.修改数据-自动填充
-
阿里开发手册上说:数据库中的表需要有create_time和update_time字两个字段,有两种方式可以实现。
- 实现方式一:数据库层面。
# 通过Navicat增加两个字段 #创建列 create_time 类型datetime 默认值为 current_timestamp #创建列 update_time 类型datetime 默认值为 current_timestamp 并勾选 根据当前时间戳更新
- 实现方式二。使用MybatisPlus的MetaObjectHandler,自动补全create_time和update_time。
-
实现方式二,数据库和Java实体类增加字段。
//`create_time` datetime(0) NULL DEFAULT NULL COMMENT ‘创建时间‘,
//`update_time` datetime(0) NULL DEFAULT NULL COMMENT ‘更新时间‘,
// 插入时更新
@TableField(fill = FieldFill.INSERT)
private Date createTime;
// 插入和更新时更新数据
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
- 实现方式二,增加Handle进行SQL拦截。
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 插入数据时,设置create_time和update_time值,值为new Date()
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert data {}", metaObject);
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
}
/**
* 更新时设置update_time的值
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", new Date(), metaObject);
}
}
3.MybatisPlus实现乐观锁
- 数据库和Java类增加Version字段。
//`version` int(0) NULL DEFAULT 1 COMMENT ‘乐观锁‘,
@Version
private Integer version;
- 配置乐观锁的拦截器。
@MapperScans({
@MapperScan("com.my.mybatisplus.demo01.dao")
})
@Configuration
public class MybatisPlusConfig {
/**
* 注入乐观锁组件。本质是通过拦截器进行SQL拼接。
* update set version=oldVersion+1 where id=? and version=oldVersion
* @return
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 乐观锁
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
4.分页查询
- 配置分页查询的拦截器。
@MapperScans({
@MapperScan("com.my.mybatisplus.demo01.dao")
})
@Configuration
public class MybatisPlusConfig {
/**
* 注入分页插件
* @return
*/
//@Bean
public MybatisPlusInterceptor interceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
- 代码实现。执行分页查询前,会执行count(*)操作获取总记录数。
@Test
void testPage() {
// 分页本质,通过拦截器在SQL执行前进行 LIMIT ? OFFSET ? 的拼接
// MybatisPlus会将数据存放在Page中
Page<User> page = new Page<>(2, 3);
userMapper.selectPage(page, null);
page.getRecords().forEach(System.out::println);
// 获得查询的数据
page.getRecords();
// 获取总共的数据
page.getTotal();
// 是否存在上一页
page.hasPrevious();
// 是否存在下一页
page.hasNext();
}
5.逻辑删除
- 数据库和Java实体类增加字段。
//`deleted` int(0) NULL DEFAULT 0 COMMENT ‘逻辑删除‘,
@TableLogic
private Integer deleted;
-
新旧版本的配置。
- 旧版本,配置拦截器。
@Bean public ISqlInjector sqlInjector() { return new LogicSqlInjector(); }
- 新版本直接在yaml中进行配置。
mybatis-plus: global-config: db-config: logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以不在实体类上加 @TableLogic) logic-delete-value: 1 # 逻辑已删除值(默认为 1) logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
-
局逻辑删除的实体字段名(since 3.3.0,在yaml配置logic-delete-field后可以不在实体类上加 @TableLogic)。
-
逻辑删除测试代码,delete变为update操作。
// 测试 逻辑删除
@Test
void testDelete01() {
// 逻辑删除 delete变为
// UPDATE user SET deleted=1 WHERE id=? AND deleted=0
int i = userMapper.deleteById(99L);
// 查询时会添加 WHERE id=? AND deleted=0
// 分页查询的count(*)操作
// SELECT COUNT(*) FROM user WHERE deleted = 0
userMapper.selectById(99L);
System.out.println(i);
}
6.MybatisPlus中SQL性能分析插件
- 旧版本,配置拦截器。
@MapperScans({
@MapperScan("com.my.mybatisplus.demo01.dao")
})
@Configuration
public class MybatisPlusConfig {
@Bean
开发和测试环境使用
@Profile({"dev", "test"})
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
// 设置SQL最大执行时间100秒,超过100秒将不执行当前SQL,并且报错。
performanceInterceptor.setMaxTime(100);
// 控制台格式化输出SQL
performanceInterceptor.setFormat(true);
return performanceInterceptor;
}
}
-
新版本,官方推荐使用p6spy。
- 导入依赖。
<!-- MyBatis-Plus3.2.0以上版本移除了PerformanceInterceptor,可以使用第三方 --> <dependency> <groupId>p6spy</groupId> <artifactId>p6spy</artifactId> <version>3.9.1</version> </dependency>
- yaml改变 DataSource的配置。
spring: datasource: #driver-class-name: com.mysql.cj.jdbc.Driver driver-class-name: com.p6spy.engine.spy.P6SpyDriver url: jdbc:p6spy:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=UTF8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456
- 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
7.MybatisPlus条件查询构造器Wrapper
- 查询多个数据selectList()。
// 查询多个数据 selectList()
// 不是null isNotNull
// 大于等于 ge
@Test
void test01() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// WHERE (name IS NOT NULL AND age >= ?)
wrapper.isNotNull("name")
.ge("age", "25");
// 查询 多个数据
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
- 查询一条数据selectOne()。
// 查询一个,如果结果有多个数据报错
// eq
@Test
void test02() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// WHERE (name = ?)
wrapper.eq("name", "tom");
// 查询 多个数据
User user = userMapper.selectOne(wrapper);
System.out.println(user);
}
- count(*)查询。
// count(*) between
@Test
void test03() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// (age BETWEEN ? AND ?)
wrapper.between("age", 20, 25);
// 查询 多个数据
Integer count = userMapper.selectCount(wrapper);
System.out.println(count);
}
- like模糊查询。
// maps
// like
@Test
void test04() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// wrapper.like("name", "t"); ==>> WHERE (name LIKE ?) %t%
// wrapper.like(true, "name", "t"); ==>> WHERE (name LIKE ?) %t%
// wrapper.like(false, "name", "t"); ==>> 条件失效
//wrapper.like(true, "name", "t");
// wrapper.likeRight("name", "t"); ==>> WHERE (name LIKE ?) t%(String)
wrapper.likeRight(false, "name", "t");
// 查询 多个数据 将查询的多个数据封装为Map
List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}
- selectObjs(),将查询结果封装为List(Object),inSql()进行内查询。
// selectObjs()
// inSql 内查询
@Test
void test05() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 内查询 (name IN (select name from user where id in (1,2)))
wrapper.inSql("name", "select name from user where id in (1,2)");
// 查询 多个数据
List<Object> objects = userMapper.selectObjs(wrapper);
objects.forEach(System.out::println);
}
- allEq()使用Map作为查询参数。
// allEq()
@Test
void test06() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
Map<String, Object> map = new HashMap<>();
map.put("name", "tom");
map.put("age", 12);
map.put("id", null);
// wrapper.allEq(map); WHERE (name = ? AND id IS NULL AND age = ?)
// 默认为true,map中value为null,调用 id IS NULL。
// false,则忽略map中为null的value
//wrapper.allEq(map, false); WHERE (name = ? AND age = ?)
wrapper.allEq((k, v) -> {
// 返回true时,将才使用map的key和value拼接SQL
// 相当与 WHERE (name = ?) tom(String)
return k.equals("name") ? v.equals("tom") : false;
},map);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
- func()。
// func 方法(主要方便在出现if...else下调用不同方法能不断链)
//例: func(i -> if(true) {i.eq("id", 1)} else {i.ne("id", 1)})
@Test
void test07() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
int i = 10;
wrapper.func(w -> {
if (i > 10) {
w.orderByAsc("id");
}else {
w.orderByAsc("name");
}
});
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
- or()。
@Test
void test08() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 默认使用and连接
// WHERE (name = ? OR name = ? AND name = ?)
//wrapper.eq("name", "tom")
//.or().eq("name", "bob")
//.eq("name", "alice");
// WHERE (name = ? OR (id = ? AND id = ?))
// or中拼接参数
wrapper.eq("name", "tom")
.or(w -> {
w.eq("id", 1).eq("id", 2);
});
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
- LambdaQueryWrapper。
@Test
void test08() {
LambdaQueryWrapper<User> w = new LambdaQueryWrapper<>();
w.eq(User::getAge, "10");
List<User> users = userMapper.selectList(w);
users.forEach(System.out::println);
}
- 其他查询。
// ne <> 不等于
// ge 大于等于 gt 大于
// le 小于等于 lt 小于
// notBetween("age", 18, 30) --->age not between 18 and 30
// in("age", 1, 2, 3)--->age in (1,2,3)
// notIn("age", 1, 2, 3)--->age not in (1,2,3)
// groupBy("id", "name")--->group by id,name
// orderByAsc("id", "name")--->order by id ASC,name ASC 升序
// orderByDesc("id", "name")--->order by id DESC,name DESC 降序
// having("sum(age) > 10")--->having sum(age) > 10
// having("sum(age) > {0}", 11)--->having sum(age) > 11
8.代码生成器
- 配置依赖。
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
<!-- freemarker模板需要的依赖 -->
<!--<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.28</version>
</dependency>
<dependency>
<groupId>com.ibeetl</groupId>
<artifactId>beetl</artifactId>
<version>3.0.7.RELEASE</version>
</dependency>-->
- 代码生成。
public class CodeGenerator {
public static void main(String[] args) {
AutoGenerator ag = new AutoGenerator();
// 1 全局配置
GlobalConfig gc = new GlobalConfig();
// 获取用户工程目录
String dir = System.getProperty("user.dir");
System.out.println(dir);
gc.setOutputDir(dir + "\\src\\main\\java");
gc.setAuthor("wwt");
// 生成代码后是否自动打开文件管理器
gc.setOpen(false);
// 文件存在是否覆盖
gc.setFileOverride(false);
// 去掉Server接口的 I。如默认为IUserService,去掉后为UserService
//gc.setServiceName("%sServer");
//gc.setServiceImplName("");
gc.setIdType(IdType.NONE);
// 只使用 java.util.date 代替
gc.setDateType(DateType.ONLY_DATE);
// 是否生成swagger注释,通过数据库字段注释,生成实体类swagger的注释
gc.setSwagger2(true);
ag.setGlobalConfig(gc);
// 2 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=UTF8&useSSL=false&serverTimezone=Asia/Shanghai");
// dsc.setSchemaName("public");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
ag.setDataSource(dsc);
// 3 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("demo03");
pc.setParent("com.my.mybatisplus");
//pc.setEntity("entity");
//pc.setMapper("mapper");
//pc.setService("service");
//pc.setServiceImpl("service/impl");
//pc.setController("controller");
pc.setXml(null);
ag.setPackageInfo(pc);
// 4 策略配置
StrategyConfig strategy = new StrategyConfig();
// 要生成代码的表
strategy.setInclude("user");
// 数据库表映射到实体的命名策略 下划线转驼峰命名
strategy.setNaming(NamingStrategy.underline_to_camel);
// 数据库表字段映射到实体的命名策略 下划线转驼峰命名
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
//strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
// lombok开启注解
strategy.setEntityLombokModel(true);
// restful 形式的url
//strategy.setRestControllerStyle(true);
// 逻辑删除
strategy.setLogicDeleteFieldName("deleted");
// 自动填充
TableFill createTime = new TableFill("create_time", FieldFill.INSERT);
TableFill updateTime = new TableFill("update_time", FieldFill.INSERT_UPDATE);
strategy.setTableFillList(Arrays.asList(createTime, updateTime));
// 公共父类
//strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");
// 写于父类中的公共字段
//strategy.setSuperEntityColumns("id");
// 乐观锁
strategy.setVersionFieldName("version");
// controller相关
strategy.setRestControllerStyle(true);
strategy.setControllerMappingHyphenStyle(true);
ag.setStrategy(strategy);
// 将mapper.xml生成到resource下
String templatePath = "/templates/mapper.xml.vm";
InjectionConfig cfg = new InjectionConfig(){
@Override
public void initMap() {
}
};
List<FileOutConfig> list = new ArrayList<>();
list.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
return dir + "/demo03/src/main/resources/mapper/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
cfg.setFileOutConfigList(list);
ag.setCfg(cfg);
// 配置模板
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setXml(null);
ag.setTemplate(templateConfig);
ag.execute();
}
}