MybatisPlus学习总结(下)
一、条件构造器
在MP中,Wrapper接口的实现类关系如下:
## mybatis plus基本操作
查询方式 说明
setSqlSelect 设置 SELECT 查询字段
where WHERE 语句,拼接 + WHERE 条件
and AND 语句,拼接 + AND 字段=值
andNew AND 语句,拼接 + AND (字段=值)
or OR 语句,拼接 + OR 字段=值
orNew OR 语句,拼接 + OR (字段=值)
eq 等于=
allEq 基于 map 内容等于=
ne 不等于<>
gt 大于>
ge 大于等于>=
lt 小于<
le 小于等于<=
like 模糊查询 LIKE
notLike 模糊查询 NOT LIKE
in IN 查询
notIn NOT IN 查询
isNull NULL 值查询
isNotNull IS NOT NULL
groupBy 分组 GROUP BY
having HAVING 关键词
orderBy 排序 ORDER BY
orderAsc ASC 排序 ORDER BY
orderDesc DESC 排序 ORDER BY
exists EXISTS 条件语句
notExists NOT EXISTS 条件语句
between BETWEEN 条件语句
notBetween NOT BETWEEN 条件语句
addFilter 自由拼接 SQL
last 拼接在最后,例如:last("LIMIT 1")
注意! xxNew 都是另起 ( ... ) 括号包裹。
1.1 allEq
allEq(Map<R, V> params)
allEq(Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, Map<R, V> params, boolean null2IsNull)
个别参数说明: params : key 为数据库字段名, value 为字段值 null2IsNull : 为 true 则在 map 的 value 为
null 时调用 isNull 方法,为 false 时则忽略 value 为 null 的
- 例1: allEq({id:1,name:“老王”,age:null}) —> id = 1 and name = ‘老王’ and age is null
- 例2: allEq({id:1,name:“老王”,age:null}, false) —> id = 1 and name = ‘老王’
allEq(BiPredicate<R, V> filter, Map<R, V> params)
allEq(BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, BiPredicate<R, V> filter, Map<R, V> params, boolean
null2IsNull)
个别参数说明: filter : 过滤函数,是否允许字段传入比对条件中 ,params 与 null2IsNull : 同上
- 例1: allEq((k,v) -> k.indexOf(“a”) > 0, {id:1,name:“老王”,age:null}) —> name = ‘老王’
and age is null- 例2: allEq((k,v) -> k.indexOf(“a”) > 0, {id:1,name:“老王”,age:null}, false) —> name =
‘老王’
代码示例:
@Test
public void testAllEq(){
Map<String,Object> params = new HashMap<>();
params.put("name", "李四");
params.put("age", "20");
params.put("password", null);
QueryWrapper<User> wrapper = new QueryWrapper<>();
//SELECT id,user_name,name,age,email FROM tb_user WHERE (password IS NULL AND name = ? AND age = ?)
// wrapper.allEq(params);
//SELECT id,user_name,name,age,email FROM tb_user WHERE (name = ? AND age = ?)
// wrapper.allEq(params,false);
//过滤函数表示是否允许字段传入比对条件中 params
//SELECT id,user_name,name,age,email FROM tb_user WHERE (password IS NULL AND age = ?)
wrapper.allEq((k, v) -> (k.equals("age") || k.equals("id") || k.equals("password")) , params);
List<User> users = userMapper.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}
1.2 基本比较操作
@Test
public void testEq() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
//SELECT id,user_name,name,age,email FROM tb_user WHERE (password = ? AND age >= ? AND name IN (?,?,?))
wrapper.eq("password", "123456")
.ge("age", 20)
.in("name", "李四", "王五", "赵六");
List<User> users = this.userMapper.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}
结果:
1.3 模糊查询
代码示例:
@Test
public void testLike(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
//SELECT id,user_name,name,age,email FROM tb_user WHERE (name LIKE ?)
// 参数:%五(String)
wrapper.likeLeft("name", "五");
List<User> users = this.userMapper.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}
结果:
1.4 排序
代码示例:
@Test
public void testOrderByAgeDesc(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
//按照年龄倒序排序
// SELECT id,user_name,name,age,email FROM tb_user ORDER BY age DESC
wrapper.orderByDesc("age");
List<User> users = this.userMapper.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}
结果:
1.5 逻辑查询
代码示例:
@Test
public void testOr(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
// SELECT id,user_name,name,age,email FROM tb_user WHERE (name = ? OR age = ?)
wrapper.eq("name", "王五").or().eq("age", 21);
List<User> users = this.userMapper.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}
结果:
1.6 select
在MP查询中,默认查询所有的字段,如果有需要也可以通过select方法进行指定字段。
代码示例:
@Test
public void testSelect(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
//SELECT id,name,age FROM tb_user WHERE (name = ? OR age = ?)
wrapper.eq("name", "王五")
.or()
.eq("age", 21)
.select("id","name","age"); //指定查询的字段
List<User> users = this.userMapper.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}
结果:
二、ActiveRecord
在Mybatis-Plus中提供了ActiveRecord
的模式,支持 ActiveRecord 形式调用,实体类只需继承 Model
类即可实现基本 CRUD 操作,简单来说就是一个实体类继承Model类,并通过注解与数据库的表名进行关联,这样就可以通过实体类直接进行表的简单增删改查操作
实现步骤:
-
Mapper对象要先继承BaseMapper对象
public interface UserMapper extends BaseMapper<User> { }
-
实体对象要继承Model对象
@Data @TableName("tb_user") public class User extends Model<User> { @TableId(value = "id",type = IdType.AUTO) private Long id; private String userName; @TableField(select = false)//查询的时候不查询该字段 private String password; private String name; private Integer age; private String email; @TableField(exist = false) private String address; //在数据库表中是不存在的 }
-
代码测试
@Test public void testSelectById(){ User user = new User(); user.setId(16L); //SELECT id,user_name,name,age,email FROM tb_user WHERE id=? User user1 = user.selectById(); System.out.println(user1); } @Test public void testInsert(){ User user = new User(); user.setUserName("diaochan"); user.setPassword("123456"); user.setAge(20); user.setName("貂蝉"); user.setEmail("diaochan@itcast.cn"); //INSERT INTO tb_user ( user_name, password, name, age, email ) VALUES ( ?, ?, ?, ?, ? ) // 调用AR的insert方法进行插入数据 boolean insert = user.insert(); System.out.println("result => " + insert); }
三、插件
3.1 mybatis的插件机制
参考博客:https://blog.csdn.net/oneby1314/article/details/116289510
3.2 常用插件配置
1、 防止全表更新与删除插件
- 针对 update 和 delete 语句 作用: 阻止恶意的全表更新删除
- SpringBoot配置:
如果进行全表的更新和删除操作则会抛出:@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 防止全表更新与删除 // 针对 update 和 delete 语句 interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor()); return interceptor; }
MybatisPlusException: Prohibition of table update operation
2、分页插件
-
插件配置:(SpringBoot)
@Configuration public class MybatisPlusConfig { // 配置分页插件,最新版 @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2)); return interceptor; } }
-
测试:
// 测试分页查询,前提:配置分页插件 @Test public void testSelectPage(){ Page<User> page = new Page<>(3,2); //查询 QueryWrapper<User> wrapper = new QueryWrapper<>(); //设置查询条件 // wrapper.like("email", "itcast");//like表示模糊查询 IPage<User> iPage = userMapper.selectPage(page, wrapper); System.out.println("数据总条数: " + iPage.getTotal()); System.out.println("数据总页数: " + iPage.getPages()); System.out.println("当前页数: " + iPage.getCurrent()); List<User> records = iPage.getRecords(); for (User record : records) { System.out.println(record); } }
执行结果:
3、乐观锁插件
-
插件配置:(SpringBoot)
@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 添加乐观锁插件 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; }
-
为表添加version字段,并且设置初始值为1
ALTER TABLE `tb_user` ADD COLUMN `version` int(10) NULL AFTER `email`; UPDATE `tb_user` SET `version`='1';
-
为User实体对象添加version字段,并且添加@Version注解
@Version private Integer version;
-
测试
@Test public void testUpdateVersion(){ User user = new User(); user.setId(2L);// 查询条件 User userVersion = user.selectById(); user.setAge(23); // 更新的数据 user.setVersion(userVersion.getVersion()); // 当前的版本信息 boolean result = user.updateById(); System.out.println("result => " + result); }
执行结果:
-
注意:
支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
整数类型下 newVersion = oldVersion + 1
newVersion 会回写到 entity 中
四、MybatisPlus 扩展
4.1 Sql注入器
SQL注入器主要用于扩充BaseMapper中的方法。以扩展findAll方法为例。
-
编写MyBaseMapper
public interface MyBaseMapper<T> extends BaseMapper<T> { List<T> findAll(); }
其他的Mapper都可以继承该Mapper,这样实现了统一的扩展
public interface UserMapper extends MyBaseMapper<User> { }
-
编写MySqlInjector
如果直接继承
AbstractSqlInjector
的话,原有的BaseMapper中的方法将失效,所以我们选择继承DefaultSqlInjector
进行扩展。public class MySqlInjector extends DefaultSqlInjector { @Override public List<AbstractMethod> getMethodList(Class<?> mapperClass) { List<AbstractMethod> methodList = super.getMethodList(mapperClass); // 再扩充自定义的方法 methodList.add(new FindAll()); return methodList; } }
-
编写FindAll
public class FindAll extends AbstractMethod { @Override public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { String sqlMethod = "findAll"; String sql = "select * from " + tableInfo.getTableName(); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); return this.addSelectMappedStatementForTable(mapperClass, sqlMethod, sqlSource, tableInfo); } }
-
注册到Spring容器
/** * 自定义SQL注入器 */ @Bean public MySqlInjector mySqlInjector(){ return new MySqlInjector(); }
-
测试
@Test public void testFindAll(){ List<User> users = this.userMapper.findAll(); for (User user : users) { System.out.println(user); } }
结果:
4.2 自动填充功能
有些时候我们可能会有这样的需求,插入或者更新数据时,希望有些字段可以自动填充数据,比如密码、时间等。在MP中提供了这样的功能,可以实现自动填充。
-
添加@TableField注解
@TableField(fill = FieldFill.INSERT) //插入数据时进行填充 private String password;
为password添加自动填充功能,在新增数据时有效。FieldFill提供了多种模式选择:
-
自定义实现类 MyMetaObjectHandler
注意:使用@Component
注解标记@Component public class MyMetaObjectHandler implements MetaObjectHandler { /** * 插入数据时填充 * @param metaObject */ @Override public void insertFill(MetaObject metaObject) { // 先获取到password的值,再进行判断,如果为空,就进行填充,如果不为空,就不做处理 Object password = getFieldValByName("password", metaObject); if(null == password){ // setFieldValByName("password", "888888", metaObject); //或者 this.strictInsertFill(metaObject, "password",String.class,"888889" ); // 起始版本 3.3.0(推荐使用) } } /** * 更新数据时填充 * @param metaObject */ @Override public void updateFill(MetaObject metaObject) { } }
-
测试
@Test public void testInsert() { User user = new User(); user.setAge(29); user.setUserName("guanyu"); user.setName("关于"); //没有设置密码 // user.setPassword("123456"); int result = userMapper.insert(user); //result数据库受影响的行数 System.out.println("result => " + result); }
结果:
4.3 逻辑删除
逻辑删除就是将数据标记为删除,而并非真正的物理删除(非DELETE操作),查询时需要携带状态条件,确保被标记的数据不被查询到。这样做的目的就是避免数据被真正的删除。
MP就提供了这样的功能,下面看一下怎么使用
-
修改表结构
为tb_user表增加deleted字段,用于表示数据是否被删除,1代表删除,0代表未删除。ALTER TABLE `tb_user` ADD COLUMN `deleted` int(1) NULL DEFAULT 0 COMMENT '1代表删除,0代表未删除' AFTER `version`;
-
配置(如果和默认值相同也可以不配置)
application.properties:# 逻辑已删除值(默认为 1) mybatis-plus.global-config.db-config.logic-delete-value=1 # 逻辑未删除值(默认为 0) mybatis-plus.global-config.db-config.logic-not-delete-value=0
-
同时,修改User实体,增加deleted属性并且添加@TableLogic注解:
@TableLogic // 逻辑删除字段 ,1-删除,0-未删除 private Integer deleted;
如果删除状态和配置的不一样可以使用
valuehe
和delval
属性指定@TableLogic(value = "1", delval = "0")
-
测试
删除测试:
@Test public void testDeleteById(){ // 根据id删除数据 int result = userMapper.deleteById(2L); System.out.println("result => " + result); }
结果:
查询测试:@Test public void testSelectById() { User user = userMapper.selectById(3L); System.out.println(user); }
结果:
4.4 通用枚举
解决了繁琐的配置,让 mybatis 优雅的使用枚举属性
-
修改表结构和User实体
ALTER TABLE `tb_user` ADD COLUMN `sex` int(1) NULL DEFAULT 1 COMMENT '1-男,2-女' AFTER `deleted`;
User类中添加sex字段
private SexEnum sex;//性别
-
定义枚举
public enum SexEnum implements IEnum<Integer> { MAN(1,"男"), WOMAN(2,"女"); private int value; private String desc; SexEnum(int value, String desc) { this.value = value; this.desc = desc; } @Override public Integer getValue() { return this.value; } @Override public String toString() { return this.desc; } }
-
配置
# 枚举包扫描 mybatis-plus.type-enums-package=com.zb.enums
-
测试
新增测试:
@Test public void testInsert(){ User user = new User(); user.setUserName("zhangfei"); user.setPassword("123456"); user.setAge(20); user.setName("张飞"); user.setEmail("zhangfei@itcast.cn"); user.setVersion(1); user.setSex(SexEnum.WOMAN); //使用的是枚举 // 调用AR的insert方法进行插入数据 boolean insert = user.insert(); System.out.println("result => " + insert); }
结果:
查询测试:
4.5 执行 SQL 分析打印(待补)
五、代码生成器
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper、XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
-
导入依赖
<!--mybatis-plus的springboot支持--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.4.1</version> </dependency> <!--使用Freemarker模板引擎--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency>
-
执行下面代码
//mysql 代码生成器演示例子 public class MysqlGenerator { /** * <p> * 读取控制台内容 * </p> */ public static String scanner(String tip) { Scanner scanner = new Scanner(System.in); StringBuilder help = new StringBuilder(); help.append("请输入" + tip + ":"); System.out.println(help.toString()); if (scanner.hasNext()) { String ipt = scanner.next(); if (StringUtils.isNotBlank(ipt)) { return ipt; } } throw new MybatisPlusException("请输入正确的" + tip + "!"); } /** * RUN THIS */ public static void main(String[] args) { // 代码生成器 AutoGenerator mpg = new AutoGenerator(); // 全局配置 GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty("user.dir"); gc.setOutputDir(projectPath + "/src/main/java"); gc.setAuthor("zhaobin"); //设置作者 gc.setOpen(false); mpg.setGlobalConfig(gc); // 数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&useSSL=false&characterEncoding=utf8"); // dsc.setSchemaName("public"); dsc.setDriverName("com.mysql.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("root"); mpg.setDataSource(dsc); // 包配置 PackageConfig pc = new PackageConfig(); pc.setModuleName(scanner("模块名")); pc.setParent("com.zb"); mpg.setPackageInfo(pc); // 自定义配置 InjectionConfig cfg = new InjectionConfig() { @Override public void initMap() { // to do nothing } }; List<FileOutConfig> focList = new ArrayList<>(); focList.add(new FileOutConfig("/templates/mapper.xml.ftl") { @Override public String outputFile(TableInfo tableInfo) { // 自定义输入文件名称 return projectPath + "/itcast-mp-generator/src/main/resources/mapper/" + pc.getModuleName() + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; } }); cfg.setFileOutConfigList(focList); mpg.setCfg(cfg); mpg.setTemplate(new TemplateConfig().setXml(null)); // 策略配置 StrategyConfig strategy = new StrategyConfig(); strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); // strategy.setSuperEntityClass("com.baomidou.mybatisplus.samples.generator.common.BaseEntity"); strategy.setEntityLombokModel(true); // strategy.setSuperControllerClass("com.baomidou.mybatisplus.samples.generator.common.BaseController"); strategy.setInclude(scanner("表名")); strategy.setSuperEntityColumns("id"); strategy.setControllerMappingHyphenStyle(true); strategy.setTablePrefix(pc.getModuleName() + "_"); mpg.setStrategy(strategy); // 选择 freemarker 引擎需要指定如下加,注意 pom 依赖必须有! mpg.setTemplateEngine(new FreemarkerTemplateEngine()); mpg.execute(); } }
-
执行结果
MP会生成mapper、service、controller三层,其中service层接口还继承了IService
,service层实现类继承了ServiceImpl
。下面看一下这两个怎么使用。
5.1 IService使用
mp框架同样提供了service层的封装支持,让我们能够简化service层的开发;
- IService的方法:
具体使用:
-
service接口继承IService
public interface UserService extends IService<User> { }
-
service实现类继承ServiceImpl
@Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { }
-
测试
@Test //测试分页 void testPage() { //分页查询数据 Page<User> page = userService.page(new Page<User>(1, 2)); System.out.println("数据总条数: " + page.getTotal()); System.out.println("数据总页数: " + page.getPages()); System.out.println("当前页数: " + page.getCurrent()); List<User> records = page.getRecords(); for (User record : records) { System.out.println(record); } } @Test //测试删除 void testRemove() { boolean result = userService.removeById(1); }