Java SpringBoot Mybatis-plus

一、添加依赖pom.xml

  1. <dependency>
  2. <groupId>mysql</groupId>
  3. <artifactId>mysql-connector-java</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>com.alibaba</groupId>
  7. <artifactId>druid-spring-boot-starter</artifactId>
  8. <version>1.1.20</version>
  9. </dependency>
  10. <dependency>
  11. <groupId>com.baomidou</groupId>
  12. <artifactId>mybatis-plus-boot-starter</artifactId>
  13. <version>3.3.2</version>
  14. </dependency>
  15. <!-- mybatis plus 代码生成器依赖 -->
  16. <dependency>
  17. <groupId>com.baomidou</groupId>
  18. <artifactId>mybatis-plus-generator</artifactId>
  19. <version>3.3.2</version>
  20. </dependency>
  21. <!-- 代码生成器模板 -->
  22. <dependency>
  23. <groupId>org.freemarker</groupId>
  24. <artifactId>freemarker</artifactId>
  25. </dependency>

二、application.yml添加配置

  1. spring:
  2. #数据库配置
  3. datasource:
  4. url: jdbc:mysql://127.0.0.1:3306/user_role?useUnicode=true&useSSL=false&characterEncoding=utf-8
  5. username: root
  6. password: root
  7. # 使用druid数据源
  8. type: com.alibaba.druid.pool.DruidDataSource
  9. driver-class-name: com.mysql.jdbc.Driver

三、Application启动类配置@MapperScan

  1. @SpringBootApplication
  2. @MapperScan("cn.com.vicente.demo.mapper")
  3. public class BdDemoApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(BdDemoApplication.class, args);
  6. }
  7. }

到这里就引入了MyBatis-Plus了。

四、代码生成器

很多时候都不想写entity,mapper等文件,这个时候就可以使用代码生成器来自动生成对应的文件了。
需要修改几个地方:
1、数据库连接
2、文件需要放置的文件夹地址。
具体代码:

  1. import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
  2. import com.baomidou.mybatisplus.core.toolkit.StringPool;
  3. import com.baomidou.mybatisplus.core.toolkit.StringUtils;
  4. import com.baomidou.mybatisplus.generator.AutoGenerator;
  5. import com.baomidou.mybatisplus.generator.InjectionConfig;
  6. import com.baomidou.mybatisplus.generator.config.*;
  7. import com.baomidou.mybatisplus.generator.config.po.TableInfo;
  8. import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
  9. import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
  10. import java.util.ArrayList;
  11. import java.util.List;
  12. import java.util.Scanner;

演示例子,执行 main 方法控制台输入模块表名回车自动生成对应项目目录中

  1. public class CodeGenerator {
  2. /**
  3. * <p>
  4. * 读取控制台内容
  5. * </p>
  6. */
  7. public static String scanner(String tip) {
  8. Scanner scanner = new Scanner(System.in);
  9. StringBuilder help = new StringBuilder();
  10. help.append("请输入" + tip + ":");
  11. System.out.println(help.toString());
  12. if (scanner.hasNext()) {
  13. String ipt = scanner.next();
  14. if (StringUtils.isNotEmpty(ipt)) {
  15. return ipt;
  16. }
  17. }
  18. throw new MybatisPlusException("请输入正确的" + tip + "!");
  19. }
  20. public static void main(String[] args) {
  21. // 代码生成器
  22. AutoGenerator mpg = new AutoGenerator();
  23. // 全局配置
  24. GlobalConfig gc = new GlobalConfig();
  25. String projectPath = System.getProperty("user.dir");
  26. gc.setOutputDir(projectPath + "/src/main/java");
  27. gc.setAuthor("vicente");
  28. gc.setOpen(false);
  29. // service 命名方式
  30. gc.setServiceName("%sService");
  31. // service impl 命名方式
  32. gc.setServiceImplName("%sServiceImpl");
  33. // 自定义文件命名,注意 %s 会自动填充表实体属性!
  34. gc.setMapperName("%sMapper");
  35. gc.setXmlName("%sMapper");
  36. gc.setFileOverride(true);
  37. gc.setActiveRecord(true);
  38. // XML 二级缓存
  39. gc.setEnableCache(false);
  40. // XML ResultMap
  41. gc.setBaseResultMap(true);
  42. // XML columList
  43. gc.setBaseColumnList(false);
  44. // gc.setSwagger2(true); 实体属性 Swagger2 注解
  45. mpg.setGlobalConfig(gc);
  46. // 数据源配置
  47. DataSourceConfig dsc = new DataSourceConfig();
  48. dsc.setUrl("jdbc:mysql://127.0.0.1:3306/user_role?useUnicode=true&useSSL=false&characterEncoding=utf-8");
  49. // dsc.setSchemaName("public");
  50. dsc.setDriverName("com.mysql.jdbc.Driver");
  51. dsc.setUsername("root");
  52. dsc.setPassword("root");
  53. mpg.setDataSource(dsc);
  54. // 包配置
  55. PackageConfig pc = new PackageConfig();
  56. //pc.setModuleName(scanner("模块名"));
  57. pc.setParent("cn.com.vicente.demo");
  58. pc.setEntity("entity");
  59. pc.setService("service");
  60. pc.setServiceImpl("service.impl");
  61. mpg.setPackageInfo(pc);
  62. // 自定义配置
  63. InjectionConfig cfg = new InjectionConfig() {
  64. @Override
  65. public void initMap() {
  66. // to do nothing
  67. }
  68. };
  69. // 如果模板引擎是 freemarker
  70. String templatePath = "/templates/mapper.xml.ftl";
  71. // 如果模板引擎是 velocity
  72. // String templatePath = "/templates/mapper.xml.vm";
  73. // 自定义输出配置
  74. List<FileOutConfig> focList = new ArrayList<>();
  75. // 自定义配置会被优先输出
  76. focList.add(new FileOutConfig(templatePath) {
  77. @Override
  78. public String outputFile(TableInfo tableInfo) {
  79. // 自定义输出文件名 , 如果 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
  80. String moduleName = pc.getModuleName()==null?"":pc.getModuleName();
  81. return projectPath + "/src/main/resources/mapper/" + moduleName
  82. + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
  83. }
  84. });
  85. /*
  86. cfg.setFileCreate(new IFileCreate() {
  87. @Override
  88. public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {
  89. // 判断自定义文件夹是否需要创建
  90. checkDir("调用默认方法创建的目录");
  91. return false;
  92. }
  93. });
  94. */
  95. cfg.setFileOutConfigList(focList);
  96. mpg.setCfg(cfg);
  97. // 配置模板
  98. TemplateConfig templateConfig = new TemplateConfig();
  99. // 配置自定义输出模板
  100. //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
  101. // templateConfig.setEntity("templates/entity2.java");
  102. // templateConfig.setService();
  103. // templateConfig.setController();
  104. templateConfig.setXml(null);
  105. mpg.setTemplate(templateConfig);
  106. // 策略配置
  107. StrategyConfig strategy = new StrategyConfig();
  108. strategy.setNaming(NamingStrategy.underline_to_camel);
  109. strategy.setColumnNaming(NamingStrategy.underline_to_camel);
  110. //strategy.setSuperEntityClass("cn.com.bluemoon.demo.entity");
  111. strategy.setEntityLombokModel(true);
  112. strategy.setRestControllerStyle(true);
  113. // 公共父类
  114. //strategy.setSuperControllerClass("cn.com.bluemoon.demo.controller");
  115. // 写于父类中的公共字段
  116. //strategy.setSuperEntityColumns("id");
  117. strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
  118. strategy.setControllerMappingHyphenStyle(true);
  119. strategy.setTablePrefix(pc.getModuleName() + "_");
  120. mpg.setStrategy(strategy);
  121. mpg.setTemplateEngine(new FreemarkerTemplateEngine());
  122. mpg.execute();
  123. }
  124. }

五、添加测试

这里主要是MyBatis-Plus的CURD等方法。

  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest
  3. public class SampleTest {
  4. private static Logger log = LoggerFactory.getLogger(SampleTest.class);
  5. @Autowired
  6. private MpUserService mpUserService;
  7. @Test
  8. public void test1() {
  9. // 插入新记录
  10. MpUser mpUser = new MpUser();
  11. //mpUser.setId(1L);
  12. mpUser.setEmail("test66@baomidou.com");
  13. mpUser.setAge(22);
  14. mpUser.setName("David Hong");
  15. mpUserService.save(mpUser);
  16. // 或者
  17. mpUser.insertOrUpdate();
  18. // 更新完成后,mpUser对象的id会被补全
  19. log.info("mpUser={}", mpUser.toString());
  20. }
  21. @Test
  22. public void test2() {
  23. // 通过主键id查询
  24. MpUser mpUser = mpUserService.getById(1);
  25. log.info("mpUser={}", mpUser.toString());
  26. }
  27. @Test
  28. public void test3() {
  29. // 条件查询,下面相当于xml中的 select * from mp_user where name = 'Tom' and age = '28' limit 1
  30. MpUser mpUser = mpUserService.getOne(new QueryWrapper<MpUser>().eq("name", "Tom").eq("age", "28").last("limit 1"));
  31. log.info("mpUser={}", mpUser.toString());
  32. // 批量查询
  33. List<MpUser> mpUserList = mpUserService.list();
  34. System.out.println("------------------------------all");
  35. mpUserList.forEach(System.out::println);
  36. // 分页查询
  37. int pageNum = 1;
  38. int pageSize = 10;
  39. IPage<MpUser> mpUserIPage = mpUserService.page(new Page<>(pageNum, pageSize), new QueryWrapper<MpUser>().gt("age", "20"));
  40. // IPage to List
  41. List<MpUser> mpUserList1 = mpUserIPage.getRecords();
  42. System.out.println("------------------------------page");
  43. mpUserList1.forEach(System.out::println);
  44. // 总页数
  45. long allPageNum = mpUserIPage.getPages();
  46. System.out.println("------------------------------allPageNum");
  47. System.out.println(allPageNum);
  48. }
  49. @Test
  50. public void test4() {
  51. MpUser mpUser = mpUserService.getById(2);
  52. // 修改更新
  53. mpUser.setName("广东广州");
  54. //mpUserService.updateById(mpUser);
  55. // 或者
  56. mpUser.insertOrUpdate();
  57. // 通过主键id删除
  58. mpUserService.removeById(1);
  59. // 或者
  60. //mpUser.deleteById();
  61. }
  62. }

六、数据分页

1、简单分页方法

  1. int pageNum = 1;
  2. int pageSize = 10;
  3. IPage<MpUser> mpUserIPage = mpUserService.page(new Page<>(pageNum, pageSize), new QueryWrapper<MpUser>().gt("age", "20"));

上面的分页其实是调用BaseMapper的selectPage方法,这样的分页返回的数据确实是分页后的数据,但在控制台打印的SQL语句上看到其实并没有真正的物理分页,而是通过缓存来获得全部数据中再进行的分页,这样对于大数据量操作时是不可取的,那么接下来就叙述一下,真正实现物理分页的方法。

2、物理分页方法

新建一个MybatisPlusConfig配置类文件

  1. //Spring boot方式
  2. @EnableTransactionManagement
  3. @Configuration
  4. @MapperScan("com.baomidou.cloud.service.*.mapper*")
  5. public class MybatisPlusConfig {
  6. @Bean
  7. public PaginationInterceptor paginationInterceptor() {
  8. PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
  9. // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
  10. // paginationInterceptor.setOverflow(false);
  11. // 设置最大单页限制数量,默认 500 条,-1 不受限制
  12. // paginationInterceptor.setLimit(500);
  13. return paginationInterceptor;
  14. }
  15. }

重新调用mpUserService.page可以看到数据有物理分页

3、XML自定义分页

UserMapper.java 方法内容

  1. public interface UserMapper{//可以继承或者不继承BaseMapper
  2. /**
  3. * <p>
  4. * 查询 : 根据state状态查询用户列表,分页显示
  5. * 注意!!: 如果入参是有多个,需要加注解指定参数名才能在xml中取值
  6. * </p>
  7. *
  8. * @param page 分页对象,xml中可以从里面进行取值,传递参数 Page 即自动分页,必须放在第一位(可以继承Page实现自己的分页对象)
  9. * @param state 状态
  10. * @return 分页对象
  11. */
  12. IPage<User> selectPageVo(Page page, @Param("age") Integer age);
  13. }

UserMapper.xml

等同于编写一个普通 list 查询,MyBatis-plus 自动分页

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3. <mapper namespace="cn.com.bluemoon.demo.mapper.MpUserMapper">
  4. <select id="selectPageVo" resultType="cn.com.bluemoon.demo.entity.MpUser">
  5. SELECT * FROM mp_user WHERE age=#{age}
  6. </select>
  7. </mapper>

UserServiceImpl.java 调用分页方法

  1. public IPage<User> selectUserPage(Page<User> page, Integer state) {
  2. // 不进行 count sql 优化,解决 MP 无法自动优化 SQL 问题,这时候需要自己查询 count 部分
  3. // page.setOptimizeCountSql(false);
  4. // 当 total 为小于 0 或者设置 setSearchCount(false) 分页插件不会进行 count 查询
  5. // 要点!! 分页返回的对象与传入的对象是同一个
  6. return baseMapper.selectPageVo(page, state);
  7. }

4、测试自定义方法

  1. @Test
  2. public void test5() {
  3. Page<MpUser> mpUserPage = new Page<>(1,2);
  4. IPage<MpUser> iPage = mpUserService.selectUserPage(mpUserPage,22);
  5. System.out.println("总页数:"+iPage.getPages());
  6. System.out.println("总记录数:"+iPage.getTotal());
  7. List<MpUser> mpUserList1 = iPage.getRecords();
  8. mpUserList1.forEach(System.out::println);
  9. }

七、打印sql日志

为了方便排查错误,很多时候需要打印MyBatis生成的sql语句,这时候就需要打印日志了。
在application.yml中添加:

  1. # Logger Config
  2. logging:
  3. level:
  4. cn.com.vicente.demo: debug

或者

  1. mybatis-plus:
  2. configuration:
  3. log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

八、逻辑删除

很多时候需要表的数据虽然删除了,但是还是希望不是真正删除数据,数据还是留在数据库中,只需要使用一个字段来做标志为即可,这时候就需要逻辑删除功能。
SpringBoot 配置方式:
application.yml 加入配置(如果默认值和mp默认的一样,该配置可无):

  1. mybatis-plus:
  2. global-config:
  3. db-config:
  4. logic-delete-value: 1 # 逻辑已删除值(默认为 1)
  5. logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

注册 Bean(3.1.1开始不再需要这一步):

  1. import com.baomidou.mybatisplus.core.injector.ISqlInjector;
  2. import com.baomidou.mybatisplus.extension.injector.LogicSqlInjector;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. @Configuration
  6. public class MyBatisPlusConfiguration {
  7. @Bean
  8. public ISqlInjector sqlInjector() {
  9. return new LogicSqlInjector();
  10. }
  11. }

实体类字段上加上@TableLogic注解

  1. @TableField(select = false)注解,可以不查询出deleted字段
  2. @TableLogic
  3. //@TableField(select = false)
  4. private Integer deleted;

效果: 使用mp自带方法删除和查找都会附带逻辑删除功能 (自己写的xml不会)
example
删除时 update user set deleted=1 where id =1 and deleted=0
查找时 select * from user where deleted=0
附件说明
逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。
如果需要再查出来就不应使用逻辑删除,而是以一个状态去表示。

九、主键策略

MyBatis-plus 的主键生成的类型 默认类型 是 IdType.ID_WORKER 全局唯一ID,内容为空自动填充(默认配置),雪花算法

1、局部主键策略实现

在实体类中 ID属性加注解

  1. @TableId(type = IdType.AUTO) 主键自增 数据库中需要设置主键自增
  2. private Long id;
  3. @TableId(type = IdType.NONE) 默认跟随全局策略走
  4. private Long id;
  5. @TableId(type = IdType.UUID) UUID类型主键
  6. private Long id;
  7. @TableId(type = IdType.ID_WORKER) 数值类型数据库中也必须是数值类型 否则会报错
  8. private Long id;
  9. @TableId(type = IdType.ID_WORKER_STR) 字符串类型 数据库也要保证一样字符类型
  10. private Long id;
  11. @TableId(type = IdType.INPUT) 用户自定义了 数据类型和数据库保持一致就行
  12. private Long id;

2、全局主键策略实现

需要在application.yml文件中添加

  1. mybatis-plus:
  2. global-config:
  3. db-config:
  4. id-type: uuid/none/input/id_worker/id_worker_str/auto

表示全局主键都采用该策略(如果全局策略和局部策略都有设置,局部策略优先级高)

十、自动填充

很多时候表中都需要添加创建时间,创建人,修改时间,修改人来跟踪数据的来源和变动,但每次插入数据和修改数据的时候都要set这几个字段又感觉很麻烦,这个时候就系统系统能自动填充这几个字段了。
字段必须声明@TableField注解,属性fill选择对应策略,该申明告知 Mybatis-Plus 需要预留注入 SQL 字段

  1. @TableField(fill = FieldFill.INSERT)
  2. private LocalDateTime createTime;
  3. @TableField(fill = FieldFill.INSERT_UPDATE)
  4. private LocalDateTime updateTime;

属性fill有四种对应策略,分别为:

  1. public enum FieldFill {
  2. /**
  3. * 默认不处理
  4. */
  5. DEFAULT,
  6. /**
  7. * 插入填充字段
  8. */
  9. INSERT,
  10. /**
  11. * 更新填充字段
  12. */
  13. UPDATE,
  14. /**
  15. * 插入和更新填充字段
  16. */
  17. INSERT_UPDATE
  18. }

自定义实现类 MyMetaObjectHandler:

  1. @Component
  2. public class MyMetaObjectHandler implements MetaObjectHandler {
  3. private static final Logger LOGGER = LoggerFactory.getLogger(MyMetaObjectHandler.class);
  4. @Override
  5. public void insertFill(MetaObject metaObject) {
  6. LOGGER.info("start insert fill ....");
  7. //this.setFieldValByName("createTime", LocalDateTime.now(), metaObject);
  8. this.setInsertFieldValByName("createTime", LocalDateTime.now(), metaObject);
  9. this.setInsertFieldValByName("updateTime", LocalDateTime.now(), metaObject);
  10. }
  11. @Override
  12. public void updateFill(MetaObject metaObject) {
  13. LOGGER.info("start update fill ....");
  14. this.setUpdateFieldValByName("updateTime", LocalDateTime.now(), metaObject);
  15. }
  16. }

测试使用

  1. @Test
  2. public void testInsert() {
  3. // 插入新记录
  4. MpUser mpUser = new MpUser();
  5. mpUser.setEmail("wm@baomidou.com");
  6. mpUser.setAge(28);
  7. mpUser.setName("王蒙");
  8. mpUserService.save(mpUser);
  9. log.info("mpUser={}", mpUser.toString());
  10. }
  11. @Test
  12. public void testUpdate() {
  13. // 更新记录
  14. MpUser mpUser = new MpUser();
  15. mpUser.setId(1182478087497998337L);
  16. MpUser newUser = mpUser.selectById();
  17. System.out.println(mpUser == newUser);
  18. mpUser.setName("王天");
  19. mpUser.updateById();
  20. log.info("mpUser={}", mpUser.toString());
  21. log.info("newUser={}", newUser.toString());
  22. }

自动填充优化
insertFill方法每次插入的时候都会调用,如果不存在createTime属性的话,每次插入都会白白调用了,浪费资源,所以可以判断是否存在该属性

  1. boolean hasCreateTime = metaObject.hasSetter(“createTime”);
  2. if (hasCreateTime){
  3. this.setInsertFieldValByName(“createTime”, LocalDateTime.now(), metaObject);
  4. }

希望,当更新时有设定时间,就用更新时设定的时间,当没有设定时就自动填充更新时间,可以这样设置

  1. Object fieldValue = getFieldValByName(“updateTime”, metaObject);
  2. if (fieldValue == null){
  3. this.setUpdateFieldValByName(“updateTime”, LocalDateTime.now(), metaObject);
  4. }