一、MP简介 MyBatis-Plus (简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
1.1、特性 无侵入 :只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑损耗小 :启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作1.2、支持的数据库 任何能使用 mybatis 进行 crud, 并且支持标准 sql 的数据库
二、快速入门 使用一个springboot项目来体验MP的强大之处。
2.1、建立入门项目的数据库与项目 建表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 create database mp; use mp; create table user( id bigint(20) primary key auto_increment, name varchar(50), email varchar(50), age int ); insert into user(name, email, age) VALUES ('A','a@qq.com',18), ('B','b@qq.com',19), ('C','c@qq.com',12), ('D','d@qq.com',11), ('E','e@qq.com',111);
建立spring-boot项目,引入依赖
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 32 33 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > <version > 2.1.3.RELEASE</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > <version > 2.1.3.RELEASE</version > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > 1.16.22</version > </dependency > <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-boot-starter</artifactId > <version > 3.1.0</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.19</version > </dependency > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 4.13</version > </dependency > </dependencies >
项目的application.yml配置文件如下
1 2 3 4 5 6 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mp?useUnicode=true&characterEncoding=utf8&useSSL=false&nullCatalogMeansCurrent=true&serverTimezone=Asia/Shanghai password: tianxin1230. username: root
编写主启动类
1 2 3 4 5 6 7 @MapperScan("com.hzx.mpblog.mapper") @SpringBootApplication public class MpApplication { public static void main (String[] args) { SpringApplication.run(MpApplication.class); } }
根据数据库表建立对应entity实体类,这里使用Lombok插件并开启链式编程来简化开发
1 2 3 4 5 6 7 8 @Data @Accessors(chain = true) public class User { private Long id; private String name; private Integer age; private String email; }
编写UserMapper接口,该接口需要继承BaseMapper< T >,其中T为要操作的数据库表对应的实体类
1 2 3 @Repository public interface UserMapper extends BaseMapper <User > {}
2.2、编写测试类进行测试 编写测试类,测试mybatis-plus为我们提供的查询所有方法
在selectList中,可以传入一个QueryWrapper查询条件构造器对象,但由于这里需要查询所有对象,所以不加条件筛选,传入null 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @SpringBootTest @RunWith(SpringRunner.class) public class MpApplicationTest { @Autowired private UserMapper userMapper; @Test public void testFindAll () { List<User> userList = userMapper.selectList(null ); userList.forEach(user -> System.out.println(user) ); } }
结果
2.3、添加配置输出sql语句 我们可以在application.yml中添加配置,让mybatis-plus在执行时输出的sql语句
1 2 3 4 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
重新启动,测试结果
三、CRUD扩展 3.1、insert测试插入数据 使用Mybatis-plus自带的插入方法进行测试
1 2 3 4 5 6 7 8 9 @Test public void testInsert () { User user = new User(); user.setName("小明" ) .setAge(18 ) .setEmail("xiaoming@qq.com" ); int result = userMapper.insert(user); System.out.println(result == 1 ? "插入成功!" : "插入失败!" ); }
查看testInsert()的执行结果并再次启动testFindAll()方法
在这次插入操作中,我们没有指定插入数据库对象的id,这个属性是Mybatis-plus为我们生成的,Mybatis-plus提供的主键生成策略在枚举类IdType中.
1 2 3 4 5 6 7 8 public enum IdType { AUTO(0 ), NONE(1 ), INPUT(2 ), ID_WORKER(3 ), UUID(4 ), ID_WORKER_STR(5 ); }
3.1.1、雪花算法 雪花算法是推特开源的分布式ID生成算法,结果是一个long型的ID。 核心思想是:使用41bit作为毫秒数,10bit作为及其的ID(5个bit是数据中心,5个bit的及其ID),12bit作为毫秒的流水号(意味着每个节点在每秒可以产生4096个Id),最后还有一个符号位永远是0 3.1.2、主键自增 配置主键自增
在实体类字段上使用@TableId(type = IdType.AUTO) 要求数据库字段一定是自增的,否则会报错 3.1.3、其余主键类型 AUTO(0):自增 NONE(1):不使用 INPUT(2):手动输入 ID_WORKER(3):默认唯一全局id UUID(4):uuid ID_WORKER_STR(5):默认唯一全局id字符串形式
3.2、update更新操作 使用mybatis-plus提供的updateById方法可以快速修改表中数据,更新Id为2L的对象信息
1 2 3 4 5 6 7 8 9 10 @Test public void testUpdate () { User user = new User(); user.setId(2L ) .setName("芜湖" ) .setEmail("wuhu@qq.com" ) .setAge(18 ); int result = userMapper.updateById(user); System.out.println(result == 1 ? "修改成功!" : "修改失败!" ); }
3.3、Mybatis-plus的自动填充功能 在实际开发中,数据库往往有create_time和update_time两个字段,对于这两个字段,我们不希望手动填充,而希望程序或者数据库自动填充,mp提供了这个功能。
在数据库中添加create_time和update_time两个属性,并在实体类中添加对于字段。
3.3.1、数据库级别填充 在新建的两个属性下面勾选根据时间戳自动更新即可,这种方式不推荐使用
3.3.2、代码级别自动填充 在实体类的createTime和updateTime字段上添加注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Data @Accessors(chain = true) public class User { private Long id; private String name; private Integer age; private String email; @TableField(fill = FieldFill.INSERT) private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Component @Slf4j public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill (MetaObject metaObject) { this .setFieldValByName("createTime" ,new Date(),metaObject); this .setFieldValByName("updateTime" ,new Date(),metaObject); } @Override public void updateFill (MetaObject metaObject) { this .setFieldValByName("updateTime" ,new Date(),metaObject); } }
在主启动类中添加组件扫描注解 测试插入,查看是否自动填充 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Test public void testInsert () { User user = new User(); user.setName("小明111" ) .setAge(18 ) .setEmail("xiaoming@qq.com" ); int result = userMapper.insert(user); System.out.println(result == 1 ? "插入成功!" : "插入失败!" ); } @Test public void testUpdate () { User user = new User(); user.setId(2L ) .setName("芜湖222" ) .setEmail("wuhu@qq.com" ) .setAge(18 ); int result = userMapper.updateById(user); System.out.println(result == 1 ? "修改成功!" : "修改失败!" ); }
3.4、乐观锁和悲观锁 悲观锁十分悲观,他总是认为会出现问题,无论干什么都会上锁!
而乐观锁十分乐观,他总是认为不会出现问题,无论干什么都不去上锁!如果出现了问题,再次更新值测试。
下面介绍Mybatis-plus中乐观锁的实现方法
3.4.1、乐观锁的实现方式 取出记录时,获取当前version
更新时带上这个version
执行更新时,set version = newVersion where version = oldVersion
如果version不对,就更新失败
1 2 3 4 5 # 乐观锁: # 1 先查询,获得版本号version = 1 # 假设现有AB两个线程在执行这条update语句,当A未执行完成时,B抢先完成这次更新,那么此时由于版本号version已经为2,所以A线程的更新工作不会成功,此时就保证了线程间的通讯安全 update user set name = "芜湖",version = version + 1 where id = 2 and version = 1;
给数据库表中添加一个version属性
给实体类中添加一个version字段,同时在上面添加一个@Version注解,表明这是一个乐观锁
1 2 @Version private Integer version;
在配置类中注册乐观锁组件
1 2 3 4 5 6 7 8 9 10 11 @Configuration @EnableTransactionManagement public class MyBatisPlusConfig { @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor () { return new OptimisticLockerInterceptor(); } }
3.5、select查询操作 3.5.1、根据id查询数据 根据传入的主键值查询数据
1 2 3 4 5 @Test public void testSelectById () { User user = userMapper.selectById(1L ); System.out.println(user); }
3.5.2、根据集合对象查询数据 传入一个主键集合,根据传入的多个主键值查询多条数据,使用selectBatchIds方法,该方法实际上使用mysql中的in关键字
1 2 3 4 5 6 7 @Test public void testSelectByIds () { List<User> users = userMapper.selectBatchIds(Arrays.asList(1L , 2L , 5L )); users.forEach(user -> System.out.println(user) ); }
3.5.3、根据Map对象查询数据 传入一个map,mp将会以键为字段,值为字段值进行拼接并查询,如果map中有多个键值对,那么会以and关键词拼接
1 2 3 4 5 6 7 @Test public void testSelectByMap () { Map<String, Object> map = new HashMap<String, Object>(); map.put("id" ,1L ); List<User> users = userMapper.selectByMap(map); users.forEach(user -> System.out.println(user)); }
1 2 3 4 5 6 7 8 @Test public void testSelectByMaps () { Map<String, Object> map = new HashMap<String, Object>(); map.put("id" ,2L ); map.put("name" ,"芜湖222" ); List<User> users = userMapper.selectByMap(map); users.forEach(user -> System.out.println(user)); }
3.5.4、分页查询 一般来说,分页查询的实现有以下几种方式
使用limit关键字进行分页 使用PageHelper和PageInfo等第三方插件进行分页 使用Mybatis-plus自带的分页插件进行分页 下面介绍mp的分页插件
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void testPage () { Page<User> page = new Page<>(1 ,2 ); IPage<User> userIPage = userMapper.selectPage(page, null ); List<User> records = userIPage.getRecords(); records.forEach(user -> System.out.println(user)); }
3.5.5、分页插件Page属性简介 1 2 3 4 5 6 7 8 9 10 public class Page <T > implements IPage <T > { private List<T> records; private long total; private long size; private long current; private String[] ascs; private String[] descs; private boolean optimizeCountSql; private boolean isSearchCount; }
Listrecords–用来存放查询出来的数据 long total–返回记录的总数 long size–每页显示条数,默认 10 long current–当前页,默认1 String[] ascs–升序字段集合 String[] descs–降序字段集合 boolean optimizeCountSql–自动优化count sql,默认为true boolean isSearchCount–是否进行count查询,默认为true 3.6、delete删除操作 3.6.1、根据id删除记录 使用mp提供的deleteById方法,返回值为数据库表中受影响数据条数
1 2 3 4 5 @Test public void testDeleteById () { int result = userMapper.deleteById(1L ); System.out.println(result == 1 ? "删除成功!" : "删除失败!" ); }
3.6.2、通过传入的集合对象进行批量删除 使用mp提供的deleteBatchIds方法
1 2 3 4 5 @Test public void testDeleteByIds () { int result = userMapper.deleteBatchIds(Arrays.asList(2L ,3L ,1L )); System.out.println(result); }
结果:由于数据库中id为1L的数据不存在,所以影响的数据条数只有两条,与批量插入一样,使用了mysql中的In关键字
3.6.3、通过传入的map进行删除 与通过map进行查询类似,当有多个键值对时,使用and拼接
1 2 3 4 5 6 7 @Test public void testDeleteByMap () { Map<String, Object> map = new HashMap(); map.put("name" ,"E" ); int result = userMapper.deleteByMap(map); System.out.println(result); }
1 2 3 4 5 6 7 8 @Test public void testDeleteByMaps () { Map<String, Object> map = new HashMap(); map.put("name" ,"小明" ); map.put("age" ,18 ); int result = userMapper.deleteByMap(map); System.out.println(result); }
3.7、配置逻辑删除 逻辑删除:在数据库中没有被移除,只是使用一个变量来使这一条记录失效
物理删除:从数据库中直接移除
管理员可以查看被删除的记录,类似于回收站
3.7.1、配置步骤 在表中添加一个deleted字段,
在实体类中添加一个deleted字段,并添加注解
1 2 3 4 5 6 @TableLogic private Integer deleted;
配置逻辑删除组件
1 2 3 4 5 @Bean public ISqlInjector sqlInjector () { return new LogicSqlInjector(); }
在application.yml文件中配置
1 2 3 4 5 mybatis-plus: global-config: db-config: logic-delete-value: 1 logic-not-delete-value: 0
当deleted值为1时,证明该数据被逻辑删除 当deleted值为0时,证明该数据没有被逻辑删除 3.7.2、测试逻辑删除 先在数据库中新插入几条数据,以便后面的测试
1 2 3 4 @Test public void testLoginDeleted () { int result = userMapper.deleteById(3L ); }
四、性能分析插件 我们在平时开发中,会遇到一些慢sql,mp提供了一个性能分析插件,如果超过指定时间,就停止运行
4.1、作用 性能分析拦截器,用于输出每条sql语句及其执行时间
4.2、使用 在application.yml中配置开发环境,将当前环境设置为dev
1 2 3 spring: profiles: active: dev
在配置类中配置sql执行效率分析插件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Bean @Profile({"dev","test"}) public PerformanceInterceptor performanceInterceptor () { PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor(); performanceInterceptor.setMaxTime(1 ); performanceInterceptor.setFormat(true ); return performanceInterceptor; }
4.3、测试 1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void testUpdateAndSelect () { User user = new User(); user.setId(1L ); user.setName("芜湖" ); user.setEmail("wuhu@qq.com" ); user.setAge(1222 ); int result = userMapper.updateById(user); System.out.println(result); List<User> userList = userMapper.selectList(null ); userList.forEach(System.out :: println); }
将最大时间设置为100ms,再次测试,修改 performanceInterceptor.setMaxTime(100);
五、条件构造器Wrapper Wrapper是一个接口,我们使用Wrapper的实现类:QueryWrapper来实现条件构造
5.1、QueryWrapper的一些方法 5.1.1、isNotNull(“列名”) 这个字段的值不为空
5.1.2、ge/eq/ne/le/gt/lt(“列名”,值) 这个字段的值必须大于等于/等于/不等于/小于等于/严格大于/严格小于传入的值
5.1.3、between(“列名”,左边值,右边值) 这个属性的值必须在左边值和右边值之间
5.1.4、notLike(“列名”,值) 字段值 not like “%值%”
5.1.5、likeLeft/likeRight(“列名”,值) 字段 like “%值”/“值%”
5.1.6、in/notIn(“列名”,Object …values) 字段 in/notIn(v0,v1…vn)
5.1.7、inSql/noInSql(“列名”,sql语句) 字段 in/notIn (sql语句),例如 inSql/notInSql(“id”,select * from user where id < 3)
等价于id in/notIn (select * from user where id < 3)
5.1.8、分组排序 groupBy(R… columns); // 等价于 GROUP BY 字段, …, 例: groupBy(“id”, “name”) —> group by id,name orderByAsc(R… columns); // 等价于 ORDER BY 字段, … ASC, 例: orderByAsc(“id”, “name”) —> order by id ASC,name ASC orderByDesc(R… columns); // 等价于 ORDER BY 字段, … DESC, 例: orderByDesc(“id”, “name”) —> order by id DESC,name DESC having(String sqlHaving, Object… params); // 等价于 HAVING ( sql语句 ), 例: having(“sum(age) > {0}”, 11) —> having sum(age) > 11 5.2、查询示例 查询name、email不为空,且年龄大于等于12岁的用户(.isNotNull()、.ge())
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void testQuestion01 () { QueryWrapper wrapper = new QueryWrapper(); wrapper.isNotNull("email" ); wrapper.isNotNull("name" ); wrapper.ge("age" ,12 ); List users = userMapper.selectList(wrapper); users.forEach(user -> System.out.println(user)); }
1 2 ==> Preparing: SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0 AND email IS NOT NULL AND name IS NOT NULL AND age >= ? ==> Parameters: 12(Integer)
查询name属性等于”小明”的用户
1 2 3 4 5 6 7 8 9 10 @Test public void testQuestion02 () { QueryWrapper wrapper = new QueryWrapper(); wrapper.eq("name" ,"小明" ); List users = userMapper.selectList(wrapper); users.forEach(user -> System.out.println(user)); }
查询年龄在10-30之间的用户个数
1 2 3 4 5 6 7 8 9 10 @Test public void testQuestion03 () { QueryWrapper wrapper = new QueryWrapper(); wrapper.between("age" ,10 ,30 ); int count = userMapper.selectCount(wrapper); System.out.println(count); }
查询id小于等于4且没被逻辑删除的用户
1 2 3 4 5 6 7 8 9 10 11 @Test public void testQuestion04 () { QueryWrapper wrapper = new QueryWrapper(); String inSql = "select id from user where id <= 4" ; wrapper.inSql("id" ,inSql); List users = userMapper.selectList(wrapper); users.forEach(user -> System.out.println(user)); }
1 SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0 AND id IN (select id from user where id <= 4)
通过id进行降序排序
1 2 3 4 5 6 7 8 9 10 @Test public void testQuestion05 () { QueryWrapper wrapper = new QueryWrapper(); wrapper.orderByDesc("id" ); List users = userMapper.selectList(wrapper); users.forEach(user -> System.out.println(user)); }
六、代码生成器 6.1、介绍 AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
6.2、使用 6.2.1、引入依赖 代码生成器需要使用模板引擎,这里使用velocity模板引擎
1 2 3 4 5 <dependency > <groupId > org.apache.velocity</groupId > <artifactId > velocity-engine-core</artifactId > <version > 2.2</version > </dependency >
配置需要生成对于后台的数据库,这里以leyou_shop数据库的user表为例,生成后台代码
6.2.2、编写代码自动生成器CodeAutoCreator类 这里以leyou_shop库的spu表为例
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 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 import com.baomidou.mybatisplus.annotation.DbType;import com.baomidou.mybatisplus.annotation.IdType;import com.baomidou.mybatisplus.generator.AutoGenerator;import com.baomidou.mybatisplus.generator.config.DataSourceConfig;import com.baomidou.mybatisplus.generator.config.GlobalConfig;import com.baomidou.mybatisplus.generator.config.PackageConfig;import com.baomidou.mybatisplus.generator.config.StrategyConfig;import com.baomidou.mybatisplus.generator.config.rules.DateType;import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;import org.junit.Test;public class CodeAutoGenerator { @Test public void generator () { AutoGenerator mpg = new AutoGenerator(); GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty("user.dir" ); gc.setOutputDir("E:\\java\\blog\\src\\main\\java" ); gc.setAuthor("蔡大头" ); gc.setOpen(false ); gc.setFileOverride(false ); gc.setServiceName("%sService" ); gc.setIdType(IdType.AUTO); gc.setDateType(DateType.ONLY_DATE); gc.setSwagger2(true ); mpg.setGlobalConfig(gc); DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://localhost:3306/leyou_shop?serverTimezone=UTC" ); dsc.setDriverName("com.mysql.cj.jdbc.Driver" ); dsc.setUsername("root" ); dsc.setPassword("tianxin1230." ); dsc.setDbType(DbType.MYSQL); mpg.setDataSource(dsc); PackageConfig pc = new PackageConfig(); pc.setParent("com.hzx" ); pc.setModuleName("mpblog" ); pc.setController("controller" ); pc.setEntity("entity" ); pc.setService("service" ); pc.setMapper("mapper" ); mpg.setPackageInfo(pc); StrategyConfig strategy = new StrategyConfig(); strategy.setInclude("spu" ); strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setTablePrefix(pc.getModuleName() + "_" ); strategy.setColumnNaming(NamingStrategy.underline_to_camel); strategy.setEntityLombokModel(true ); strategy.setRestControllerStyle(true ); strategy.setControllerMappingHyphenStyle(true ); mpg.setStrategy(strategy); mpg.execute(); } }
6.3、测试及说明 生成路径、主键策略
设置包名和模块名
设置要生成的表
启动程序,查看结果
Mybatis-plus只对mybatis做增强,不做修改,所以Mybatis有的功能,MP均能实现,对于一些复杂的查询,可以使用写xml文件的方式来完成。