第5章 Spring Boot整合MyBatisPlus MyBatisPlus是国内团队苞米豆在MyBatis的基础上开发的增强框架,扩展了一些功能,以提高效率。引入MyBatisPlus不会对现有的MyBatis框架产生任何影响,而且MyBatisPlus支持所有MyBatis原生的特性。 使用MyBatisPlus可以无须编写SQL语句就能进行基本的CRUD操作。MyBatisPlus内置了BaseMapper,提供了大量的CRUD方法,可以满足大部分单表的简单查询。 MyBatisPlus具有无侵入、损耗小等特性,支持强大的CRUD操作、支持Lambda形式调用、支持主键自动生成、支持ActiveRecord模式、支持自定义全局通用操作,具有内置代码生成器、内置分页插件、内置性能分析插件、内置全局拦截插件。 5.1基本CRUD查询 MyBatisPlus封装了BaseMapper接口,MyBatisPlus启动时会自动解析实体表关系映射并转换为MyBatis内部对象注入容器。开发者只需创建数据访问层接口,继承BaseMapper就可直接使用。查看源码,可以看到BaseMapper提供的方法如下所示,各方法的作用见其注释。 //插入一条记录 int insert(T entity); //根据 entity 条件,删除记录 int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper); //删除(根据ID 批量删除) int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); //根据 ID 删除 int deleteById(Serializable id); //根据 columnMap 条件,删除记录 int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap); //根据 whereEntity 条件,更新记录 int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper); //根据 ID 修改 int updateById(@Param(Constants.ENTITY) T entity); //根据 ID 查询 T selectById(Serializable id); //根据 entity 条件,查询一条记录 T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); //查询(根据ID 批量查询) List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); //根据 entity 条件,查询全部记录 List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); //查询(根据 columnMap 条件) List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap); //根据 Wrapper 条件,查询全部记录 List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); //根据 Wrapper 条件,查询全部记录。注意: 只返回第1个字段的值 List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); //根据 entity 条件,查询全部记录(并翻页) IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); //根据 Wrapper 条件,查询全部记录(并翻页) IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); //根据 Wrapper 条件,查询总记录数 Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); 在上述代码中,泛型T为任意实体对象,参数Serializable为任意类型主键,MyBatisPlus不推荐使用复合主键约定每张表都有自己的唯一id主键,对象Wrapper为条件构造器。 【例51】查询图书信息。 (1) 创建项目。创建Spring Boot项目mybatisplus,导入MyBatisPlus等依赖,pom.xml文件中的关键代码如下: //第5章/mybatisplus1/pom.xml <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.25</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> (2) 创建实体类Book,代码如下: //第5章/mybatisplus1/Book.java @Data @AllArgsConstructor @NoArgsConstructor @TableName("book") public class Book { @TableId(value="id",type= IdType.AUTO) private Integer id; @TableField("name") private String name; private double price; private String category; private int pnum; private String imgurl; private String description; private String author; private int sales; } 其中注解@TableName表示该实体类对应的数据库表名,如果实体类与表名不一致,则这一步是必要的,否则可以不添加该注解,默认表名与实体类名称相同。注解@TableId表示实体类中对应数据库表的主键的属性,其中type= IdType.AUTO表示主键由数据库自增长。 (3) 配置文件application.properties。在此文件中配置的数据库连接信息内容跟4.1节案例相同。 此外也可全局设置表名的前缀,例如数据库中的表名有可能是带t_开头的,但实体类一般不这样开头,所以会导致表名与实体类名称不一致,主要差别可能就是这个t_,这时就可以在这里配置,参考代码如下: mybatis-plus.global-config.db-config.table-prefix=t_ 还可在这里全局设置主键的增长方式,参考代码如下: mybatis-plus.global-config.db-config.id-type=auto 说明: 上述配置非必要,如果实体类已经对表名进行了映射,并且主键也指定了增长策略,则无须上述两个配置。 其他常用配置的参考代码如下: //第5章/mybatisplus1/application.yml mybatis-plus: #扫描XML映射文件,多个目录用逗号或者分号分隔(告诉 Mapper所对应的XML文件位置) mapper-locations: classpath:mapper/*.xml #实体类路径 type-aliases-package: com.sike.entity #以下配置均有默认值,可以不设置 global-config: db-config: #主键类型 AUTO:"数据库ID自增" INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型 #唯一ID)", UUID:"全局唯一ID UUID"; #配置了这个实体类可以省略注解@TableId id-type: AUTO #数据库表的前缀,配置后实体类可省略注解@TableName table-prefix: tb_ #字段策略 IGNORED:"忽略判断" NOT_NULL:"非 NULL 判断" NOT_EMPTY:"非空判断" field-strategy: NOT_EMPTY #数据库类型 db-type: MYSQL configuration: #是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射 map-underscore-to-camel-case: true #返回map时true:当查询数据为空时字段返回为null,false:不加这个查询数据为空时,字段 #将被隐藏 call-setters-on-nulls: true #这个配置会将执行的SQL打印出来,在开发或测试时可以用 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl (4) 创建数据访问层。创建BookMapper接口,继承BaseMapper<Book>接口,关键代码如下: import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.seehope.domain.Book; public interface BookMapper extends BaseMapper<Book> { } 这里面可以不定义任何方法,因为父接口BaseMapper已经包含了基本的增、删、改、查方法,在业务层中调用即可。同样可以选择在这个接口上面添加@Mapper注解或者在启动类上添加MapperScan("接口所在包名")注解,这里选择后者。 (5) 创建业务逻辑层。创建BookService接口和BookServiceImpl实现类,其中BookServiceImpl的关键代码如下: //第5章/mybatisplus1/BookServiceImpl.java @Service public class BookServiceImpl implements BookService { @Autowired private BookMapper bookMapper; @Override public List<Book> findAllBooks() { return bookMapper.selectList(null); } @Override public Book findBookById(int id) { return bookMapper.selectById(id); } @Override public void addBook(Book book) { bookMapper.insert(book); } @Override public void updateBook(Book book) { bookMapper.updateById(book); } @Override public void deleteBook(int id) { bookMapper.deleteById(id); } } (6) 创建控制器BookController,代码同4.1节案例,具体代码参考本书配套资源。 (7) 创建视图。同4.1节案例。 (8) 运行代码进行测试。效果同4.1节案例。 总结: MyBatisPlus非常省事,数据访问层几乎不用写代码就可以使用。 5.2条件查询 在BaseMapper接口提供的CRUD方法中,有些方法提供了Wrapper类型的参数,用于设置查询条件,Wrapper类型的参数既可以使用子类QueryWrapper对象,也可以使用子类LambdaQueryWrapper对象。 5.2.1使用QueryWrapper封装查询条件 调用BaseMapper与select有关的方法时,使用Wrapper子类的QueryWrapper对象做参数,调用BaseMapper的delete和update有关的方法时,使用Wrapper子类的UpdateWrapper对象做参数。 Wrapper是一个抽象类,AbstractWrapper是Wrapper的子类,QueryWrapper和UpdateWrapper又是AbstractWrapper的子类。AbstractWrapper提供了众多的方法,用于设置查询条件。常用的方法见表51。 条件查询的步骤,先创建QueryWrapper对象或UpdateWrapper对象,然后调用表51中的方法构造查询条件,最后将QueryWrapper对象或UpdateWrapper对象放入Dao层接口的有关方法中作为参数即可。 表51AbstractWrapper常用的方法 方法语法说明示例 eqeq(R column, Object val) eq(boolean condition, R column, Object val)等于 =eq("name", "张飞") 相当于name = '张飞' nene(R column, Object val) ne(boolean condition, R column, Object val)不等于 <> ne("name", "张飞") 相当于name<> '张飞' gtgt(R column, Object val) gt(boolean condition, R column, Object val)大于> gt("age", 18) 相当于age>18 gege(R column, Object val) ge(boolean condition, R column, Object val)大于或等于>=ge("age",18) 相当于age>=18 ltlt(R column,Object val) lt(boolean condition,R column,Object val)小于< lt("age",18) 相当于age<18 lele(R column,Object val) le(boolean condition,R column,Object val)小于或等于 <= le("age",18) 相当于age<=18 between between(R column,Object val1,Object val2) between(boolean condition,R column,Object val1,Object val2)BETWEEN值1 AND值2between("age",18,30) 相当于age between 18 and 30 notBetween notBetween(R column,Object val1,Object val2) notBetween(boolean condition,R column,Object val1,Object val2)NOT BETWEEN值1 AND值2notBetween("age",18,30) 相当于age not between 18 and 30 like like(R column,Object val) like(boolean condition,R column,Object val)LIKE '%值%' like("name","李") 相当于name like '%李%' notLike notLike(R column,Object val) notLike(boolean condition,R column,Object val)NOT LIKE '%值%' notLike("name","李")相当于>name not like '%李%' likeLeftlikeLeft(R column,Object val) likeLeft(boolean condition,R column,Object val)LIKE '%值' likeLeft("name","李") 相当于name like '%李' likeRightlikeRight(R column,Object val) likeRight(boolean condition,R column,Object val)LIKE '值%' likeRight("name","李") 相当于name like '李%' isNullisNull(R column) isNull(boolean condition,R column)字段IS NULL isNull("name") 相当于name is null isNotNullisNotNull(R column) isNotNull(boolean condition,R column)字段IS NOT NULLisNotNull("name") 相当于name is not null in in(R column,Collection<?> value) in(boolean condition, R column, Collection<?> value)字段IN (value.get(0), value.get(1), …)in("age",{1,2,3}) 相当于age in (1,2,3) in(R column, Object… values) in(boolean condition, R column, Object… values)字段IN (v0, v1, …)in("age", 1, 2, 3) 相当于age in (1,2,3) notIn notIn(R column, Collection<?> value) notIn(boolean condition, R column, Collection<?> value)字段NOT IN (value.get(0), value.get(1),…)notIn("age",{1,2,3}) 相当于age not in (1,2,3) notIn(R column, Object… values) notIn(boolean condition, R column, Object… values)字段NOT IN (v0, v1, …) notIn("age", 1, 2, 3) 相当于age not in (1,2,3) groupBy groupBy(R… columns) groupBy(boolean condition, R… columns)分组: GROUP BY 字段, … groupBy("id", "name") 相当于group by id,name orderByAscorderByAsc(R… columns) orderByAsc(boolean condition, R… columns) 排序: ORDER BY字段, … ASC orderByAsc("id", "name") 相当于order by id ASC,name ASC orderByDescorderByDesc(R… columns) orderByDesc(boolean condition, R… columns) 排序: ORDER BY 字段, … DESC orderByDesc("id", "name") 相当于order by id DESC,name DESC orderByorderBy(boolean condition, boolean isAsc, R… columns) 排序: ORDER BY 字段, … orderBy(true, true, "id", "name") 相当于order by id ASC,name ASC having having(String sqlHaving,Object… params) having(boolean condition, String sqlHaving, Object… params) HAVING (SQL语句) having("sum(age)>10")相当于having sum(age)>10 having("sum(age)>{0}",11)相当于having sum(age)>11 or or() or(boolean condition) 拼接OR 注意事项: 主动调用or表示紧接着下一种方法不是用and连接!(不调用or则默认为使用and连接) eq("id",1).or().eq("name","李白")相当于 id = 1 or name = '李白' andand(Consumer<Param> consumer) and(boolean condition, Consumer <Param> consumer) AND 嵌套 and(i > i.eq("name", "张飞").ne("status", "活着"))相当于and (name = '张飞' and status <> '运动') 【例52】动态查询图书信息,任意输入不同条件组合均可查询到相关的图书信息。 (1) 接着上一个案例,在项目的BookService接口和BookServiceImpl中均添加方法List<Book> searchBooks(Book book),其中BookServiceImpl中的方法的代码如下: //第5章/mybatisplus1/BookServiceImpl.java @Override public List<Book> searchBooks(Book book) { QueryWrapper<Book> queryWrapper=new QueryWrapper<>(); queryWrapper.like(book.getName()!=""&&book.getName()!=null,"name",book.getName()); queryWrapper.eq(book.getCategory()!=""&&book.getCategory()!=null,"category",book.getCategory()); queryWrapper.eq(book.getAuthor()!=""&&book.getAuthor()!=null,"author",book.getAuthor()); return bookMapper.selectList(queryWrapper); } 这里使用了AbstractMapper的like与eq方法,like方法的第1个参数是条件,即当book对象的name属性不为空时才使用这条查询。上述多个queryWrapper语句之间默认为AND的关系。 (2) 控制器方法与视图都同第4章案例,具体代码可参考本书配套资源,运行代码进行测试,效果也相同。 5.2.2使用LambdaQueryWrapper封装查询条件 上面使用QueryWrapper封装查询条件时各个属性是手工输入的字符串,容易出错,而使用LambdaQueryWrapper封装查询条件时可以使用Lambda表达式,可以调用各个属性,从而避免出现错误。 【例53】使用LambdaQueryWrapper封装上述搜索条件。 (1) 将业务层BookServiceImpl中的searchBooks原有方法代码注释掉,然后添加代码,添加的代码如下: //第5章/mybatisplus1/BookServiceImpl.java public List<Book> searchBooks(Book book) { LambdaQueryWrapper<Book> queryWrapper=new LambdaQueryWrapper<>(); queryWrapper.like(book.getName()!=""&&book.getName()!=null,Book::getName,book.getName()); queryWrapper.eq(book.getCategory()!=""&&book.getCategory()!=null,Book::getCategory,book.getCategory()); queryWrapper.eq(book.getAuthor()!=""&&book.getAuthor()!=null,Book::getAuthor,book.getAuthor()); return bookMapper.selectList(queryWrapper); } (2) 运行代码进行测试,结果相同。 5.3分页查询 MyBatisPlus提供了Page类封装分页信息,Page包括以下常用分页属性,代码如下: //当前页的记录集合 private List<T> records; //总记录数 private long total; //每页显示的记录数 private long size; //当前页码 private long current; Page类的带参构造方法为public Page(long current, long size),其中参数current表示当前页,size表示每页显示的记录数。 BaseMapper有一个selectPage方法,完整语法如下: <E extends IPage<T>> E selectPage(E page, @Param("ew") Wrapper<T> queryWrapper); 该方法的第1个参数是Page对象,需用上述提到的构造方法进行构造,第2个参数是查询条件,可以为null,如果是null就查询泛型T代表的数据库表的所有记录。返回的IPage对象即为封装好的分页信息。 此外,还需要做分页配置类。具体见下面的案例。 【例54】在上面案例项目的基础上分页查询图书信息。 (1) 创建分页配置类。其实就是一个拦截器,代码如下: //第5章/mybatisplus1/MybatisPlusConfig.java import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor paginationInterceptor() { MybatisPlusInterceptor mybatisPlusInterceptor=new MybatisPlusInterceptor(); mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return mybatisPlusInterceptor; } } (2) 向业务层BookService接口和BookServiceImpl实现类添加getPage(int pageNum,int size)方法。实现类BookServiceImpl中的方法,代码如下: //第5章/mybatisplus1/BookServiceImpl.java @Override public IPage<Book> getPage(int pageNum,int size){ //参数一是当前页,参数二是每页的个数 IPage<Book> bookPage = new Page<>(pageNum, size); bookPage = bookMapper.selectPage(bookPage, null); return bookPage; } 注: Page类是IPage接口的实现类。 (3) 在控制器BookController中添加方法,代码如下: //第5章/mybatisplus1/BookController.java @GetMapping("/booksPage") //默认查询第1页,每页显示3条 public ModelAndView booksPage(@RequestParam(value="start",defaultValue = "1")int start, @RequestParam(value="size",defaultValue = "3") int size){ //表示起始页为start,每页显示size条记录,根据id升序排序进行分页 IPage<Book> page=bookService.getPage(start,size); ModelAndView mv=new ModelAndView(); mv.addObject("page",page); mv.setViewName("booksPage"); return mv; } (4) 创建视图。booksPage.html页面关键代码如下。 遍历当前页的商品: //第5章/mybatisplus1/booksPage.html <tr th:each="book:${page.records}"> <td th:text="${book.id}"></td> <td th:text="${book.name}"></td> <td th:text="${book.pnum}"></td> <td th:text="${book.price}"></td> <td th:text="${book.category}"></td> <td th:text="${book.description}"></td> <td th:text="${book.imgurl}"></td> <td th:text="${book.author}"></td> <td th:text="${book.sales}"></td> </tr> 分页控件: //第5章/mybatisplus1/booksPage.html <div> <a th:if="${page.current>1}" th:href="@{/booksPage(start=${page.current-1})}">上一页</a> 总页数: <span th:text="${page.getPages()}"></span> 当前页: <span th:text="${page.current}"></span> 总记录数: <span th:text="${page.total}"></span> <a th:if="${page.current<page.getPages()}" th:href="@{/booksPage(start=${page.current+1})}">下一页</a> </div> (5) 运行代码进行测试,效果同第4章案例。 5.4业务逻辑层快速开发 MyBatisPlus不但提供了数据访问层的快速开发,同样针对业务逻辑层也提供了快速开发技术。详情见下面的案例。 【例55】在上面案例的基础上,使用MyBatisPlus业务层快速开发技术实现查询所有图书和根据id查询一本图书的功能。 (1) 创建业务层接口。接口名称为IBookService,注意要继承MyBatisPlus提供的IService<Book>接口,代码如下: public interface IBookService extends IService<Book> { } (2) 创建业务层实现类。实现类的名称为BookServiceImpl2,注意要实现上述IBookService接口,并且要继承MyBatisPlus提供的ServiceImpl类,代码如下: @Service public class BookServiceImpl2 extends ServiceImpl<BookMapper,Book> implements IBookService{ } (3) 在控制器BookController中添加方法,代码如下: //第5章/mybatisplus1/BookController.java @Autowired private IBookService iBookService; @GetMapping("/books2") public ModelAndView findAllBooks2(){ List<Book> books=iBookService.list(); ModelAndView mv=new ModelAndView(); mv.addObject("books",books); mv.setViewName("books"); return mv; } @GetMapping("/books2/{id}") public ModelAndView findBooksById2(@PathVariable int id){ Book book=iBookService.getById(id); ModelAndView mv=new ModelAndView(); mv.addObject("book",book); mv.setViewName("book"); return mv; } (4) 运行代码进行测试。 通过浏览器中访问http://localhost:8080/books2和http://localhost:8080/books2/1,将分别看到所有图书的列表和1号图书的信息。 本章小结 本章学习了Spring Boot整合MyBatisPlus基本CRUD查询,条件查询,分页查询,以及如何整合业务层进行快速开发。