SpringBoot项目拥抱Mybatis-Plus持久层框架实践
本文目录
前言
自从 Mybatis-Plus推出以来,越来越多的公司在自己的项目中选择Mybatis-Plus框架替换了持久层框架Mybatis。因为Mybatis-Plus用起来既有Mybatis的手写复杂sql语句的灵活性,又兼具了Spring Data Jpa自动提供了单表CRUD操作的通用框架方法,只需要自定义一个Mapper并继承BaseMapper即可,为开发人员使用持久层框架节约了很多工作量。同时Mybatis-Plus还提供了链式查询和分页查询等诸多通用API方法,开发人员可直接使用。本文的目的是指导新手如何在自己的spring-boot项目中集成mybatis-plus持久层框架完成数据的增删改查功能。
1 Mybatis-Plus简介
MyBatis-Plus (简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
愿景: 我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。
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 的数据库,具体支持情况如下:
- mysql,oracle,db2,h2,hsql,sqlite,postgresql,sqlserver,Phoenix,Gauss ,clickhouse,Sybase,OceanBase,Firebird,cubrid,goldilocks,csiidb
- 达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库
1.3 框架结构
2 快速开始
2.1 新建spring-boot项目
打开IDEA,依次点击File->New->Project->选择Spring Initializr,为节省接下来项目下载依赖包需要花费太长的时间,首先点击下图中第一个红色方框中最右侧的设置图标按钮,将ServerUrl参数值改为阿里的地址:http://start.aliyun.com(默认地址为https://spring.io/)
然后填写好项目名以及项目在本地电脑上的存储位置(下图中的Name和Location右侧输入框中的内容),同时命名好项目的GroupId和artifactId(下图中Group和Artifact右侧输入框中的内容),Java版本选择8
然后点击Next按钮进入选择依赖控制界面,我们选择项目需要的依赖,这里笔者选择了Lombok、Spring Web、Mybatis Plus Framework、 MySQL Driver、Apache Commons Lang 和Fastjson等项目依赖模块
然后点击Finish按钮生产项目骨架,程序帮我们选择了Spring Boot 2.3.7.RELEASE版本,Mybatis-Plus版本自动选择了3.4.2版本。但是笔者在实践的过程中发现项目启动时报了一系列Spring Boot 2.3.7.RELEASE版本中内置的springframeword-XX-5.2.12.RELEASE.jar无法打开的错误问题,于是我把Spring Boot版本换成了2.2.7.RELEASE版本,它内置的springframwork版本使用了5.2.6.RELEASE版本,这个版本的jar包在项目启动时没有出现打不开的错误。然后我在Spring Boot项目的启动类时添加@MapperScan注解时始终无法找到这个注解,笔者估计是3.4.2版本的Mybatis-Plus与2.2.7.RELEASE版本的Spring Boot不兼容,于是把Mybatis-Plus的版本也换成了3.1.0版本的,换了之后发现可以找到@MapperScan这个注解了。
使用spring-boot-2.3.7.RELEASE版本启动报错日志:
java: 读取D:\mavenRepository\.m2\repository\org\springframework\spring-jdbc\5.2.12.RELEASE\spring-jdbc-5.2.12.RELEASE.jar时出错; error in opening zip filejava: 读取D:\mavenRepository\.m2\repository\org\springframework\spring-tx\5.2.12.RELEASE\spring-tx-5.2.12.RELEASE.jar时出错; error in opening zip file
在创建项目之后,笔者通过IDEA开发工具右侧的Maven生命周期和依赖管理发现存在很多重复引用jar包的问题,于是使用标签将重复的依赖去除,只是注意要把一些模块内要用到但是却重复引用的依赖单独放到标签下,以免造成项目中依赖的jar包缺失导致性项目启动失败。实际上项目中存在重复引用依赖jar包只还要不存在jar包冲突的情况是不会造成项目启动失败错误的,只是会造成项目的依赖管理变得臃肿。
下面的pom.xml文件是我经过替换spring-boot 2.2.7.RELEASE版本,并排除大部分重复依赖且项目可以成功启动后的pom.xml文件内容
pom.xml
2.2 完成项目启动配置
在项目的src/main/resources目录下的application.properties环境配置文件中添加spring、tomcat服务器和mybatis-pus的配置项,spring-boot项目在启动时会去根据自动配置类在初始化相关beans时会去加载这些参数
application.propertis
# 应用名称
spring.application.name=mybatis-plus
# 应用服务 WEB 访问端口
server.port=8080
# 应用上下文路径
server.servlet.context-path=/mybatis-plus
# 激活dev环境
spring.profiles.active=dev
# 设置时区,防止json序列化对象日期参数时比真是日期早了8小时
spring.jackson.time-zone=GMT+8# mybatis-plus配置
mybatis-plus.mapper-locations=classpath:com/example/mybatisplus/mapper/*Mapper.xml
mybatis-plus.configuration.map-underscore-to-camel-case=true
# 这个配置可以查看sql执行的详细日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
另外新建application-dev.properties配置文件,对应开发环境的配置参数,在这个属性文件中我们配置开发环境的数据源参数,将来有测试环境和生产环境时还可以继续添加application-test.properties文件和application-prod.properties文件配置测试环境和生产环境对应的环境配置参数
application-dev.properties
# database driver
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# datasource name
spring.datasource.name=hikariDataSource
# connection URL
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
# login user
spring.datasource.username=root
# login password
spring.datasource.password=<你的数据库root用户连接密码>
注意上面的数据库连接URL对应的spring.datasource.url配置项中必须加上characterEncoding=UTF-8和serverTimezone=Asia/Shanghai两个参数,前者是为了防止数据库中文乱码,后者是为了数据库中保存的日期字段时间准确,默认的时间会比我们中国北京时区早8个小时。
项目的启动类上加上@MapperScan
注解,basePackages属性中填写数据库访问接口Mapper类所在的包名
package com.example.mybatisplus;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
@MapperScan(basePackages = {"com.example.mybatisplus.mapper"})
publicclass MybatisPlusApplication {public static void main(String[] args) {SpringApplication.run(MybatisPlusApplication.class, args);}}
package com.example.mybatisplus.configuration;import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
publicclass MybatisPlusConfig {@Beanpublic PaginationInterceptor paginationInterceptor() {PaginationInterceptor paginationInterceptor = new PaginationInterceptor();paginationInterceptor.setOverflow(true);paginationInterceptor.setDialectClazz("com.baomidou.mybatisplus.extension.plugins.pagination.dialects.MySqlDialect");paginationInterceptor.setSqlParser(new JsqlParserCountOptimize());return paginationInterceptor;}
}
这里的配置与mybatis-plus官网的配置稍有不同,官网用的是3.4.2版本,而我用的3.1.0版本。主要是new了一个分页拦截器类PaginationInterceptor
,然后设置它的数据库方言类和sqlParser属性。如果用的不是Mysql数据库,读者可使用com.baomidou.mybatisplus.extension.plugins.pagination.dialects
包下面与自己数据库对应的方言类
2.3 浅析Mybatis-Plus自动配置类源码
解读mybatis-plus自动配置类的源码的目的是为了帮助我们跟更好的理解Mybatis-Plus的工作原理和指导我们如何正确的配置mybatis-plus的属性参数
mybatis-plus的自动配置类为MybatisPlusAutoConfiguration
类,该类位于mybatis-plus-boot-starter-3.1.0.jar包中的com.baomidou.mybatisplus.autoconfigure
包下
进入MybatisPlusAutoConfiguration
类中我们可以看到下面的两个@bean注解标注的方法sqlSessionFactory(DataSource dataSource)
和sqlSessionTemplate(SqlSessionFactory sqlSessionFactory)
帮我们配置了Spring集成Mybatis时必须要用到的连个bean:SqlSessionTemplate
和SqlSessionTemplate
。这两个bean都会在开发人员没有自定义配置这两个类的bean的条件下自动初始化并注入到Spring IOC容器中
我们可以看到MybatisPlusAutoConfiguration
类上加上了@EnableConfigurationProperties({MybatisPlusProperties.class})
和@AutoConfigureAfter({DataSourceAutoConfiguration.class})
两个注解,前者表示项目中具有·MybatisPlusProperties·这个属性配置类时初始化·MybatisPlusAutoConfiguration·配置类及其下面配置的bean并添加到Spring IOC容器中;后者表示在完成数据源自动配置类DataSourceAutoConfiguration
初始化之后进行。
进入MybatisPlusProperties
类中我们发现它要求所有与mybatis-plus相关的属性参数都以mybatis-plus为前缀
这个类会去解析环境配置文件中以mybatis-plus开头的键值对并在初始化时填充到它的属性值中
更多源码读者可自己通过IDEA打开项目后查看。
3 使用Mybatis-Plus完成数据库CRUD功能
这里我为了减少文章篇幅,仅演示单表的CRUD操作,主要涉及单条和多条数据的添加、修改、查询和分页查询功能的实现,使用Mybatis-Plus实现同时查询多张表的连表查询与在Mybatis中通过自定义mapper.xml实现一致。网上的案例也比较多,我在本文就不再延伸演示了
3.1 建表
这里我在mysql的test数据库中新建了一张stock_info表,代表商品信息表,也是为了方便学习分布式电商项目的需要。建表sql脚本如下:
DROPTABLEIFEXISTS`stock_info`;
CREATETABLE`stock_info` (`id`bigint(20) NOTNULL AUTO_INCREMENT COMMENT'主键',`good_code`varchar(30) NOTNULLCOMMENT'商品编码',`good_name`varchar(100) DEFAULTNULLCOMMENT'商品名称',`count`int(11) DEFAULT'0'COMMENT'商品数量',`created_date` datetime NOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',`created_by`varchar(30) NOTNULLDEFAULT'system'COMMENT'创建人',`last_updated_date` datetime NOTNULLDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPCOMMENT'最后更新时间',`last_updated_by`varchar(30) NOTNULLDEFAULT'system'COMMENT'最后更新人',`unit_price`int(11) DEFAULT'0'COMMENT'单价,单位分',PRIMARY KEY (`id`),UNIQUEKEY`uk_good_code` (`good_code`)
) ENGINE=InnoDB AUTO_INCREMENT=22DEFAULTCHARSET=utf8mb4;
读者也可以使用可视化的数据库客户端工具如Navicat实现新建一张表,然后把建表sql脚本dump下来保存到项目中存储脚本的文件夹下。
编码部分
项目的编码我们按照分层模式进行,分为控制器层(controller包下的XXController类)、服务层(service包下的XXService类)和数据库访问层(mapper包下的XXMapper类)
3.2 新建与表对应的实体类
在com.example.mybatisplus.pojo
包下新建一个基础类BaseEntity,主要包含一张表中通用的创建人、创建时间、最后修改人和最后修改时间等字段
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;import java.io.Serializable;
import java.util.Date;@Data
publicclass BaseEntity implements Serializable {/*** 创建人*/@TableField(value = "created_by", fill = FieldFill.INSERT)private String createdBy;/*** 创建日期(带时间)*/@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")@TableField(value = "created_date", fill = FieldFill.INSERT)private Date createdDate;/*** 修改人用户ID*/@TableField(value = "last_updated_by", fill = FieldFill.INSERT_UPDATE)private String lastUpdatedBy;/*** 修改日期(带时间)*/@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")@TableField(value = "last_updated_date", fill = FieldFill.INSERT_UPDATE)private Date lastUpdatedDate;}
日期字段加上日期格式化注解@JsonFormat注解,在pattern属性中指定日期格式
同样在com.example.mybatisplus.pojo
下新建与stock_info表对应的实体类StockInfo类并继承上面的BaseEntity类
package com.example.mybatisplus.pojo;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;@Data
@TableName("stock_info")
publicclass StockInfo extends BaseEntity {/*** 主键ID*/@TableId(type = IdType.AUTO)private Long id;/*** 商品代码*/@TableField(value = "good_code")private String goodCode;/*** 商品名称*/@TableField(value = "good_name")private String goodName;/*** 库存数量*/@TableField(value = "count")private Integer count;/*** 商品单价,单位:分*/@TableField(value = "unit_price")private Long unitPrice;}
关于@TableName
、@TableId
和@TableField
等注解的用法及详细介绍,读者可以通过阅读官方文档注解部分掌握,我在这里就不作解读了。本项目源码已提交到gitee个人代码仓库,其他接口出参通用类如ResponseVo、form表单查询参数封装类StockParam类和BaseParam类可通过文末给出的项目gitee地址查看。
3.3 数据库访问层编码
新建StockMapper
接口并继承BaseMapper
接口类,并自定义两个方法
package com.example.mybatisplus.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mybatisplus.pojo.StockInfo;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;@Repository
publicinterface StockMapper extends BaseMapper<StockInfo> {/*** 根据商品码查找库存数量* @param goodCode* @return*/Integer findCountByGoodCode(String goodCode);/*** 更新商品库存数量* @param id* @param count* @return*/Integer updateStockById(@Param("id") Long id, @Param("count") Integer count);
}
我们按住Ctrl键,点击BaseMapper
进入BaseMapper
类可以看到该类定义了单表的常用CRUD方法,为开发人员节省了自定义CRUD方法的时间
package com.baomidou.mybatisplus.core.mapper;import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Param;
// BaseMapper中的泛型参数T为表对应的实体类
publicinterface BaseMapper<T> {// 添加条记录方法int insert(T entity);// 通过ID删除方法int deleteById(Serializable id);// 通过列值匹配删除方法int deleteByMap(@Param("cm") Map<String, Object> columnMap);// 通过查询条件删除int delete(@Param("ew") Wrapper<T> wrapper);// 根据ID集合批量删除int deleteBatchIds(@Param("coll") Collection<? extends Serializable> idList);// 根据ID更新记录int updateById(@Param("et") T entity);// 根据查询条件更新int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);// 根据ID查询单条记录T selectById(Serializable id);// 根据ID集合批量查询List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);// 根据列值匹配查询,返回多条记录List<T> selectByMap(@Param("cm") Map<String, Object> columnMap);// 根据查询条件查询单条记录T selectOne(@Param("ew") Wrapper<T> queryWrapper);// 根据条件查询满足条件的数量Integer selectCount(@Param("ew") Wrapper<T> queryWrapper);//根据查询条件查询多条记录,返回实体对象集合List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);//根据查询条件查询多条记录,返回Map集合List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> queryWrapper);// 根据查询条件查询多条记录,返回对象集合List<Object> selectObjs(@Param("ew") Wrapper<T> queryWrapper);// 带查询参数的分页查询,返回带实体对象集合的分页对象IPage<T> selectPage(IPage<T> page, @Param("ew") Wrapper<T> queryWrapper);// 带查询参数的分页查询,返回带Map集合的分页对象IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param("ew") Wrapper<T> queryWrapper);
}
在src/main/resources
目录下我们逐层新建至com/example/muybatisplus/mapper
文件夹,然后在mapper文件夹下新建StockMapper.xml
完成StockMapper.java
中自定义方法的实现。这一步与以往的Mybatis作为持久层框架手写Mapper.xml
一样
StockMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mybatisplus.mapper.StockMapper"><select id="findCountByGoodCode" parameterType="java.lang.String" resultType="java.lang.Integer">select `count` from stock_infowhere good_code = #{goodCode, jdbcType=VARCHAR}</select><update id="updateStockById">update stock_info set `count` = #{count, jdbcType=INTEGER}where id = #{id, jdbcType=BIGINT}</update>
</mapper>
3.4 Service层编码
新建StockService
接口类并集成IService
接口类
package com.example.mybatisplus.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.example.mybatisplus.params.StockParam;
import com.example.mybatisplus.pojo.ResponseVo;
import com.example.mybatisplus.pojo.StockInfo;import java.util.List;publicinterface StockService extends IService<StockInfo> {/*** 单个添加保存** @param stockInfo* @return*/ResponseVo saveOne(StockInfo stockInfo);/*** 批量添加保存** @param stockInfoList* @return*/ResponseVo saveBatch(List<StockInfo> stockInfoList);/*** 修改单个** @param stockInfo* @return*/ResponseVo updateOne(StockInfo stockInfo);/*** 批量修改* @param stockInfoList* @return*/ResponseVo updateBatch(List<StockInfo> stockInfoList);/*** 带个条件分页查询* @param stockParam* @param pageNo* @param pageSize* @return*/ResponseVo findPageByCondition(StockParam stockParam, int pageNo, int pageSize);/*** 自定义查找商品库存* @param goodCode* @return*/ResponseVo findCountByGoodCode(String goodCode);/*** 自定义更商品库存* @param id* @param count* @return*/ResponseVo updateStockCountById(Long id, Integer count);
}
在StockService接口类中,我定义了一些访问数据库的CRUD抽象方法,方法返回类型统一为ResponseVo
然后新建StockServiceImpl
类继承ServiceImpl
类并实现StockService
接口类
package com.example.mybatisplus.service.impl;import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.example.mybatisplus.mapper.StockMapper;
import com.example.mybatisplus.params.StockParam;
import com.example.mybatisplus.pojo.ResponseVo;
import com.example.mybatisplus.pojo.StockInfo;
import com.example.mybatisplus.service.StockService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;import java.util.List;@Service
@Slf4j
publicclass StockServiceImpl extends ServiceImpl<StockMapper, StockInfo> implements StockService {privatestaticfinal String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";@Overridepublic ResponseVo saveOne(StockInfo stockInfo) {log.info("invoke StockServiceImpl#saveOne method start --------");log.info("stockInfo={}", JSON.toJSON(stockInfo));setCreatedByAndCreatedByDate(stockInfo);setLastUpdatedByAndLastUpdatedDate(stockInfo);boolean result = super.save(stockInfo);ResponseVo responseVo;if (result) {responseVo = ResponseVo.success(result);} else {responseVo = ResponseVo.error("保存失败");}return responseVo;}@Overridepublic ResponseVo saveBatch(List<StockInfo> stockInfoList) {log.info("invoke StockServiceImpl#saveBatch method start --------");log.info("stockInfoList={}", JSON.toJSON(stockInfoList));for (StockInfo stockInfo : stockInfoList) {setCreatedByAndCreatedByDate(stockInfo);setLastUpdatedByAndLastUpdatedDate(stockInfo);}boolean result = super.saveBatch(stockInfoList);ResponseVo responseVo;if (result) {responseVo = ResponseVo.success(result);} else {responseVo = ResponseVo.error("批量保存失败");}return responseVo;}@Overridepublic ResponseVo updateOne(StockInfo stockInfo) {log.info("invoke StockServiceImpl#updateOne method start --------");log.info("stockInfo={}", JSON.toJSON(stockInfo));setLastUpdatedByAndLastUpdatedDate(stockInfo);boolean result = super.updateById(stockInfo);ResponseVo responseVo;if (result) {responseVo = ResponseVo.success(result);} else {responseVo = ResponseVo.error("更新失败");}return responseVo;}@Overridepublic ResponseVo updateBatch(List<StockInfo> stockInfoList) {log.info("invoke StockServiceImpl#updateBatch method start --------");log.info("stockInfoList={}", JSON.toJSON(stockInfoList));for (StockInfo stockInfo : stockInfoList) {setLastUpdatedByAndLastUpdatedDate(stockInfo);}boolean result = super.updateBatchById(stockInfoList);ResponseVo responseVo;if (result) {responseVo = ResponseVo.success(result);} else {responseVo = ResponseVo.error("批量更新失败");}return responseVo;}@Overridepublic ResponseVo findPageByCondition(StockParam stockParam, int pageNo, int pageSize) {log.info("invoke StockServiceImpl#findPageByCondition method start --------");log.info("pageNo={}, pageSize={}", pageNo, pageSize);if (pageNo <= 0) {pageNo = 1;}if (pageSize <= 10) {pageSize = 10;}if (pageSize > 500) {pageSize = 500;}IPage<StockInfo> pageParam = new Page<>(pageNo, pageSize);QueryWrapper<StockInfo> queryWrapper = getQueryWrapperByParam(stockParam);IPage<StockInfo> pageData = super.page(pageParam, queryWrapper);ResponseVo responseVo = ResponseVo.success(pageData);return responseVo;}@Overridepublic ResponseVo findCountByGoodCode(String goodCode) {log.info("invoke StockServiceImpl#findCountByGoodCode method start --------");log.info("goodCode={}", goodCode);ResponseVo responseVo;if (StringUtils.isEmpty(goodCode)) {responseVo = ResponseVo.error(HttpStatus.BAD_REQUEST.value(), "goodCode cannot be null");return responseVo;}Integer count = this.baseMapper.findCountByGoodCode(goodCode);if (count == 1) {responseVo = ResponseVo.success(count);} else {responseVo = ResponseVo.error("更新库存失败");}return responseVo;}@Overridepublic ResponseVo updateStockCountById(Long id, Integer count) {log.info("invoke StockServiceImpl#updateStockCountById method start --------");ResponseVo responseVo;if (id == null || id < 1) {responseVo = ResponseVo.error(HttpStatus.BAD_REQUEST.value(), "invalid request param of id");return responseVo;}if (count == null || count < 1) {responseVo = ResponseVo.error(HttpStatus.BAD_REQUEST.value(), "invalid request param of count");return responseVo;}Integer resultCount = this.baseMapper.updateStockById(id, count);if (resultCount == 1) {responseVo = ResponseVo.success(resultCount);} else {responseVo = ResponseVo.error("更新商品库存失败");}return responseVo;}/*** 设置创建人与创建时间** @param stockInfo*/private void setCreatedByAndCreatedByDate(StockInfo stockInfo) {if (StringUtils.isEmpty(stockInfo.getCreatedBy())){stockInfo.setCreatedBy("system");}if (stockInfo.getCreatedDate() == null) {stockInfo.setCreatedDate(DateUtil.date(System.currentTimeMillis()));}}/*** 设置最后修改人与最后修改时间** @param stockInfo*/private void setLastUpdatedByAndLastUpdatedDate(StockInfo stockInfo) {if (StringUtils.isEmpty(stockInfo.getLastUpdatedBy())){stockInfo.setLastUpdatedBy("system");}if (stockInfo.getLastUpdatedDate() == null) {stockInfo.setLastUpdatedDate(DateUtil.date(System.currentTimeMillis()));}}/*** 处理动态查询** @param stockParam* @return queryWrapper*/private QueryWrapper<StockInfo> getQueryWrapperByParam(StockParam stockParam) {QueryWrapper<StockInfo> queryWrapper = new QueryWrapper<>();queryWrapper.select("id", "good_code", "good_name", "unit_price", "count","created_date", "created_by", "last_updated_by", "last_updated_date");// 注意加了唯一索引的列使用like查询会导致查不出结果queryWrapper.eq(!StringUtils.isEmpty(stockParam.getGoodCode()), "good_code", stockParam.getGoodCode());queryWrapper.likeRight(!StringUtils.isEmpty(stockParam.getGoodName()), "good_name", stockParam.getGoodName());queryWrapper.eq(stockParam.getCount() != null, "count", stockParam.getCount());queryWrapper.ge(stockParam.getQueryMinUnitPrice() != null,"unit_price", stockParam.getQueryMinUnitPrice());queryWrapper.le(stockParam.getQueryMaxUnitPrice() != null,"unit_price", stockParam.getQueryMaxUnitPrice());queryWrapper.eq(StringUtils.isNotBlank(stockParam.getCreatedBy()), "created_by", stockParam.getCreatedBy());if (!StringUtils.isEmpty(stockParam.getQueryMinCreatedDate())) {DateTime queryMinCreatedDate = DateUtil.parse(stockParam.getQueryMinCreatedDate(), DATE_TIME_FORMAT);queryWrapper.ge("created_date", queryMinCreatedDate);}if (!StringUtils.isEmpty(stockParam.getQueryMaxCreatedDate())) {DateTime queryMaxCreatedDate = DateUtil.parse(stockParam.getQueryMaxCreatedDate(), DATE_TIME_FORMAT);queryWrapper.le("created_date", queryMaxCreatedDate);}if (!StringUtils.isEmpty(stockParam.getLastUpdatedBy())) {queryWrapper.eq(StringUtils.isNotBlank(stockParam.getLastUpdatedBy()), "last_updated_by", stockParam.getLastUpdatedBy());}if (!StringUtils.isEmpty(stockParam.getQueryMinUpdateDate())) {DateTime queryMinUpdateDate = DateUtil.parse(stockParam.getQueryMinUpdateDate(), DATE_TIME_FORMAT);queryWrapper.ge("last_updated_date", queryMinUpdateDate);}if (!StringUtils.isEmpty(stockParam.getQueryMaxUpdateDate())) {DateTime queryMaxUpdateDate = DateUtil.parse(stockParam.getQueryMaxUpdateDate(), DATE_TIME_FORMAT);queryWrapper.le("last_updated_date", queryMaxUpdateDate);}queryWrapper.orderByAsc("id");return queryWrapper;}
}
进入ServiceImpl
类中我们可以看到,它实现了IService
接口类,实现了大部分BaseMapper
中的抽象方法
ServiceImpl
类中的第一泛型参数为继承自BaseMapper
的自定义Mapper
类,第二个泛型参数则是与不表对应的实体类,对应本演示项目中的StockMapper
和StockInfo
两个类。这样自定义Mapper类无需在自定义ServiceImpl
中注入,而是通过this.baseMapper
拿到数据库访问代理对象。
3.5 Controller层编码
这一层编码就非常简单了,直接调用注入的StockService
服务类完成操作即可, 只是需要在类和方法上加上一些spring-mvc的注解
package com.example.mybatisplus.controller;import com.example.mybatisplus.params.StockParam;
import com.example.mybatisplus.pojo.ResponseVo;
import com.example.mybatisplus.pojo.StockInfo;
import com.example.mybatisplus.service.StockService;
import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;
import java.util.List;@RestController
@RequestMapping("/stock")
publicclass StockController {@Resourceprivate StockService stockService;/*** 保存单个** @param stockInfo* @return*/@PostMapping("/saveOne")public ResponseVo saveOne(@RequestBody StockInfo stockInfo) {return stockService.saveOne(stockInfo);}/*** 批量保存* @param stockInfoList* @return*/@PostMapping("/saveBatch")public ResponseVo saveBatch(@RequestBody List<StockInfo> stockInfoList) {return stockService.saveBatch(stockInfoList);}/*** 更新单个* @param stockInfo* @return*/@PostMapping("/updateOne")public ResponseVo updateOne(@RequestBody StockInfo stockInfo) {return stockService.updateOne(stockInfo);}/*** 批量更新** @param stockInfoList* @return*/@PostMapping("/updateBatch")public ResponseVo updateBatch(@RequestBody List<StockInfo> stockInfoList) {return stockService.updateBatch(stockInfoList);}/*** 分页查找** @param pageNo 当前页* @param pageSize 每页记录数* @param stockParam 库存查询参数封装对象* @return*/@PostMapping("/page/list/{pageNo}/{pageSize}")public ResponseVo pageListByCondition(@PathVariable("pageNo") int pageNo, @PathVariable("pageSize") int pageSize, @RequestBody StockParam stockParam) {return stockService.findPageByCondition(stockParam, pageNo, pageSize);}/*** 根据商品码查找商品库存数量** @param goodCount* @return*/@GetMapping("/goodCount")public ResponseVo findGoodCount(@RequestParam("goodCount") String goodCount) {return stockService.findCountByGoodCode(goodCount);}/*** 修改商品库存数量** @param id* @param count* @return*/@PostMapping("/update/count")public ResponseVo updateStockCountById(Long id, Integer count) {return stockService.updateStockCountById(id, count);}}
4 效果体验
4.1 启动项目
编码完成,我们在本地启动Mysql服务的前提下开始启动项目,控制台中出现如下日志信息表示启动成功:
021-12-04 15:55:19.483 INFO 16832 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2021-12-04 15:55:19.494 INFO 16832 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-12-04 15:55:19.494 INFO 16832 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.34]
2021-12-04 15:55:19.723 INFO 16832 --- [ main] o.a.c.c.C.[.[localhost].[/mybatis-plus] : Initializing Spring embedded WebApplicationContext
2021-12-04 15:55:19.723 INFO 16832 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 2050 ms
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter._ _ |_ _ _|_. ___ _ | _
| | |\/|_)(_| | |_\ |_)||_|_\ / | 3.1.0
Registered plugin: 'AbstractSqlParserHandler(sqlParserList=null, sqlParserFilter=null)'
2021-12-04 15:55:20.020 INFO 16832 --- [ main] com.zaxxer.hikari.HikariDataSource : hikariDataSource - Starting...
2021-12-04 15:55:20.195 INFO 16832 --- [ main] com.zaxxer.hikari.HikariDataSource : hikariDataSource - Start completed.
Parsed mapper file: 'file [D:\Mybatis\mybatisplus\target\classes\com\example\mybatisplus\mapper\StockMapper.xml]'
2021-12-04 15:55:20.766 INFO 16832 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2021-12-04 15:55:21.116 INFO 16832 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '/mybatis-plus'
2021-12-04 15:55:21.120 INFO 16832 --- [ main] c.e.mybatisplus.MybatisPlusApplication : Started MybatisPlusApplication in 4.34 seconds (JVM running for 9.892)
4.2 测试接口
项目启动成功后,我们就可以打开postman对接口进行测试了
4.2.1 测试添加单条数据
响应结果返回转态码200表示接口调用成功, 同时我们可以看到后台控制台打印了如下sql执行语句
==> Preparing: INSERT INTO stock_info ( good_code, good_name, count, unit_price, created_by, created_date, last_updated_by, last_updated_date ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ? )
==> Parameters: GalaxyNote20(String), 三星Noto20(String), 500(Integer), 589900(Long), system(String), 2021-12-04 16:22:31.653(Timestamp), system(String), 2021-12-04 16:22:31.693(Timestamp)
<== Updates: 1
4.2.2 测试批量添加接口
我们调用批量插入接口一次性插入5条数据,接口返回状态码200表示添加数据成功
然后我们通过客户端Navicat查询数据库也可以看到通过调用单个添加和批量添加接口添加的数据入库了,一些数据是我之前调用添加接口写入到数据库中的。
4.2.3 测试分页查询接口
最后我们测试下分页查询效果:
1)首先不带查询参数进行分页查询,此时查的是表中全部数据
接口响应信息里显示共查出了27条数据,每页显示10条,共3页
2)最后带上查询参数进行分页查询
截图时我折叠了records字段中的值,records字段中的数据如下所示:
[{"createdBy": "system","createdDate": "2021-12-04 16:36:50","lastUpdatedBy": "system","lastUpdatedDate": "2021-12-04 16:36:50","id": 23,"goodCode": "GalaxyNote3","goodName": "三星Note3","count": 500,"unitPrice": 280000},{"createdBy": "system","createdDate": "2021-12-04 16:36:50","lastUpdatedBy": "system","lastUpdatedDate": "2021-12-04 16:36:50","id": 24,"goodCode": "GalaxyNote4","goodName": "三星Note4","count": 500,"unitPrice": 300000},{"createdBy": "system","createdDate": "2021-12-04 16:36:50","lastUpdatedBy": "system","lastUpdatedDate": "2021-12-04 16:36:50","id": 25,"goodCode": "GalaxyNote5","goodName": "三星Note4","count": 500,"unitPrice": 330000},{"createdBy": "system","createdDate": "2021-12-04 16:36:50","lastUpdatedBy": "system","lastUpdatedDate": "2021-12-04 16:36:50","id": 26,"goodCode": "GalaxyNote6","goodName": "三星Note6","count": 500,"unitPrice": 350000},{"createdBy": "system","createdDate": "2021-12-04 16:36:50","lastUpdatedBy": "system","lastUpdatedDate": "2021-12-04 16:36:50","id": 27,"goodCode": "GalaxyNote7","goodName": "三星Note7","count": 500,"unitPrice": 380000}]
限于文章篇幅,其他接口的测试效果就不一一在此列举了。感兴趣的读者可以把这一项目克隆下来并测试使用mybatis-plus实现更多操作数据库功能。
5 参考链接
【1】mybatis-plus快速开始
【2】mybatis-plus安装
【3】spring-boot集成mybatis-plus
【4】mybatis-plus实现数据库CRUD
【5】mybatis-plus分页插件
本文项目gitee地址:mybatisplus
本文首发个人微信公众号,喜欢我的文章的读者朋友希望能扫码下面的二维码加个微信关注,谢谢!
SpringBoot项目拥抱Mybatis-Plus持久层框架实践相关推荐
- mybatis(java持久层框架)
mybatis java持久层框架 (对JDBC进行封装,并自动完成ORM操作) ORM框架是对象关系映射,一个对象与表中的一行数据一一对应,把对象持久化到数据库中. 我将会一步一步详细的创建一个完整 ...
- 一起来学SpringBoot(七)持久层框架
springboot具有非常棒的持久层框架支持,下面我将介绍我用过的三种持久层框架进行简述使用. 由于这里操作的都是一张表,这里贴出通用的yml和建表语句 切记这里使用的是mysql8 ,5.8之前的 ...
- Java持久层框架之mybatis使用
一.什么是框架,框架从何而来,为什么使用框架? 框架(framework): 1.是一系列jar包,其本质是对JDK功能的拓展.(jar包,jar:class文件的压缩包) 2.框架是一组程序的集合, ...
- SSM持久层框架MyBatis,看这一篇就够了
前言: 此篇仅为个人初期学习笔记,如有错误,请xd们指正 你这么好看,还.... MyBatis 前言 一.MyBatis简介 二.搭建MyBatis项目 1. 开发环境 2. 创建maven工程 3 ...
- 持久层框架之MyBatis
1.mybatis框架介绍: MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并 ...
- Java数据持久层框架 MyBatis之背景知识一
对于MyBatis的学习而言,最好去MyBatis的官方文档:http://www.mybatis.org/mybatis-3/zh/index.html 对于语言的学习而言,马上上手去编程,多多练习 ...
- java持久层用文件_Java持久层框架MyBatis简单实例
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis .本文 ...
- 优秀的持久层框架-Mybatis(上)
文章目录 前言 一.MyBatis概述 1.1传统JDBC编程 1.2 mybatis的历史 1.3 mybatis是什么? 1.4如何使用? 1.5Mybatis架构 二. MyBatis环境搭建 ...
- 【狂神说:秦疆】Mybatis持久层框架笔记
目录 Mybatis 1.简介 1.1.什么是Mybatis 1.2.如何获取Mybatis 1.3.持久化 1.4.持久层 1.5.为什么使用Mybatis 2.第一个Mybatis程序 2.1.搭 ...
最新文章
- 有史以来最精彩的自问自答:OpenAI 转方块的机械手
- 网站文章要求图文并茂,图片要怎样做好优化工作呢?
- linux内核网络协议栈--接收流程及函数(九)
- 创建弹出窗口的图片展示
- 重磅:服务器基础知识全解终极版(145页PPT)
- dubbo源码解析(二)springBoot+dubbo案例整合
- 机器视觉可以应用到哪些领域,你都知道吗?
- 告别奇虎360、依图科技,再谋他途!依图CTO颜水成被曝离职!
- ubuntu16.04 安装显卡驱动
- 【unity 保卫星城】--- 开发笔记(Demo演示篇)
- 不用任何软件,批量转化图片格式
- html5建站软件工具有哪些
- 怎么仿制html文件,简单仿制HTML网页
- Codeforces Round #700 (Div. 2)(B,C,D1,D2详细题解)
- CornerStone 破解 最简单的破解方法
- 猜数字 随机生成一个1-100之间的数字,玩家进行猜测,如果猜错,提示玩家数字过大或者过小,如果猜对恭喜玩家胜利,并且退出游戏。
- 网络爬虫框架Scrapy简介
- 几何画板手机版_数学几何画板手机版
- 房屋租赁出售系统的设计与实现
- linux重置网络协议,linux网络配置、管理