SpringBoot project embraces the practice of mybatis plus persistence layer framework

Keywords: Java Spring Boot mybatis-plus

Contents of this article

preface

Since the launch of Mybatis plus, more and more companies have chosen the Mybatis plus framework to replace the persistence layer framework Mybatis in their own projects. Because Mybatis plus not only has the flexibility of handwritten complex sql statements of Mybatis, but also has the general framework method that Spring Data Jpa automatically provides single table CRUD operation. You only need to customize a Mapper and inherit BaseMapper, which saves a lot of workload for developers to use the persistence layer framework. At the same time, Mybatis plus also provides many general API methods such as chain query and paging query, which can be directly used by developers. The purpose of this paper is to guide novices how to integrate Mybatis plus persistence layer framework in their spring boot project to complete the function of data addition, deletion, modification and query.

1 Introduction to mybatis plus

MyBatis plus (MP for short) is an enhancement tool for MyBatis. On the basis of MyBatis, it only makes enhancements and does not change. It is born to simplify development and improve efficiency.

Vision: our vision is to become the best partner of MyBatis, just like 1P and 2P in soul duel. With the combination of friends and friends, the efficiency will be doubled.

1.1 characteristics

  • No invasion: it is only enhanced without change, and its introduction will not affect the existing project, which is as smooth as silk
  • Low loss: the basic CURD will be injected automatically upon startup, with basically no loss of performance and direct object-oriented operation
  • Powerful crud operation: built in general Mapper and general Service, most CRUD operations of a single table can be realized only through a small number of configurations, and there is a powerful condition constructor to meet various use requirements
  • Support Lambda formal call: it is convenient to write various query conditions through Lambda expression, and there is no need to worry about wrong fields
  • Support automatic generation of primary key: support up to 4 primary key strategies (including distributed unique ID generator - Sequence), which can be configured freely to perfectly solve the primary key problem
  • Support ActiveRecord mode: support ActiveRecord formal calls. Entity classes only need to inherit Model classes to perform powerful CRUD operations
  • Support custom global general operations: support global general method injection (Write once, use anywhere)
  • Built in code generator: code or Maven plug-in can be used to quickly generate Mapper, Model, Service and Controller layer code, support template engine, and more custom configurations for you to use
  • Built in paging plug-in: Based on MyBatis physical paging, developers do not need to care about specific operations. After configuring the plug-in, writing paging is equivalent to ordinary List query
  • The paging plug-in supports multiple databases: MySQL, MariaDB, Oracle, DB2, H2, HSQL, SQLite, Postgre, SQLServer and other databases
  • Built in performance analysis plug-in: it can output SQL statements and their execution time. It is recommended to enable this function during development and testing to quickly find out slow queries
  • Built in global interception plug-in: it provides intelligent analysis and blocking of full table delete and update operations, and can also customize interception rules to prevent misoperation

1.2 support database

Any database that can use mybatis for CRUD and supports standard SQL. The specific support is as follows:

  • mysql,oracle,db2,h2,hsql,sqlite,postgresql,sqlserver,Phoenix,Gauss ,clickhouse,Sybase,OceanBase,Firebird,cubrid,goldilocks,csiidb
  • Dameng database, virtual Valley database, NPC Jincang database, NTU general (Huaku) database, NTU general database, Shentong database, Hangao database

1.3 frame structure

2 quick start

2.1 create a new spring boot project

Open the IDEA, click file - > New - > Project - > and select Spring Initializr. To save too long time for the next project to download dependent packages, first click the setting icon button on the right in the first red box in the figure below to change the ServerUrl parameter value to Ali's address: http://start.aliyun.com (the default address is https://spring.io/ )

Then fill in the project Name and the storage Location of the project on the local computer (the contents in the input boxes on the right of Name and Location in the figure below), and Name the GroupId and artifactId of the project (the contents in the input boxes on the right of Group and Artifact in the figure below). Select 8 for Java version


Then click the Next button to enter the dependency selection control interface. We select the dependencies required by the project. Here, the author selects project dependency modules such as Lombok, Spring Web, Mybatis Plus Framework, MySQL Driver, Apache common Lang and Fastjson

Then click Finish to produce the project skeleton. The program helps us select Spring Boot version 2.3.7.release, and mybatis plus automatically selects version 3.4.2. However, in the process of practice, the author found that the project startup reported a series of errors that the built-in springframeword-XX-5.2.12.RELEASE.jar in Spring Boot 2.3.7.RELEASE could not be opened, so I changed the Spring Boot version to 2.2.7.RELEASE, and the built-in springframework version used 5.2.6.RELEASE, This version of the jar package does not have an error that cannot be opened when the project is started. Then, when I added the @ MapperScan annotation to the startup class of the Spring Boot project, I couldn't find this annotation. I estimated that version 3.4.2 of mybatis plus was incompatible with version 2.2.7.RELEASE of Spring Boot, so I changed the version of mybatis plus to version 3.1.0. After changing, I found that @ MapperScan annotation could be found.

Use spring-boot-2.3.7.RELEASE to start the error log:

java: read D:\mavenRepository\.m2\repository\org\springframework\spring-jdbc\5.2.12.RELEASE\spring-jdbc-5.2.12.RELEASE.jar Error occurred; error in opening zip file

java: read D:\mavenRepository\.m2\repository\org\springframework\spring-tx\5.2.12.RELEASE\spring-tx-5.2.12.RELEASE.jar Error occurred; error in opening zip file

After the project was created, the author found that there were many problems of repeated references to jar packages through Maven life cycle and dependency management on the right side of the IDEA development tool, so the label was used to remove the repeated dependencies, but attention should be paid to putting the repeatedly referenced dependencies in some modules under the label separately, In order to avoid the lack of dependent jar packages in the project and the failure of project startup. In fact, if there are repeated references to dependent jar packages in the project, but there is no jar package conflict, the project startup failure error will not be caused, but the dependency management of the project will become bloated.

The following pom.xml file is the content of the pom.xml file after I replaced the spring boot version 2.2.7.release, excluded most of the repeated dependencies, and the project can be started successfully

pom.xml

2.2 complete project startup configuration

Add the configuration items of spring, tomcat server and mybatis pus in the application.properties environment configuration file under the src/main/resources directory of the project. The spring boot project will load these parameters when initializing related beans according to the automatic configuration class during startup

application.propertis

# apply name
spring.application.name=mybatis-plus
# Application service WEB access port
server.port=8080
# Application context path
server.servlet.context-path=/mybatis-plus
# Activate dev environment
spring.profiles.active=dev
# Set the time zone to prevent json from serializing the object date parameter 8 hours earlier than the true date
spring.jackson.time-zone=GMT+8

# Mybatis plus configuration
mybatis-plus.mapper-locations=classpath:com/example/mybatisplus/mapper/*Mapper.xml
mybatis-plus.configuration.map-underscore-to-camel-case=true
# This configuration allows you to view the detailed log of sql execution
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

In addition, create a new application-dev.properties configuration file, which corresponds to the configuration parameters of the development environment. In this property file, we configure the data source parameters of the development environment. In the future, when there is a test environment and production environment, we can continue to add application-test.properties files and application-prod.properties files to configure the environment configuration parameters corresponding to the test environment and production environment

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=<Your database root User connection password>

Note that two parameters, characterEncoding=UTF-8 and serverTimezone=Asia/Shanghai, must be added to the spring.datasource.url configuration item corresponding to the database connection URL above. The former is to prevent Chinese garbled code in the database, and the latter is to ensure that the time of the date field saved in the database is accurate. The default time will be 8 hours earlier than our Beijing time zone.

Add the @ MapperScan annotation to the startup class of the project, and fill in the package name of the Mapper class of the database provider in the basePackages attribute

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 {

     @Bean
     public 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;
     }
}

The configuration here is slightly different from that of the mybatis plus official website. The official website uses version 3.4.2, while I use version 3.1.0. It mainly introduces a PaginationInterceptor class, PaginationInterceptor, and then sets its database dialect class and sqlParser attribute. If the Mysql database is not used, readers can use the dialect class corresponding to their own database under the com.baidu.mybatisplus.extension.plugins.pagination.conversations package

2.3 analysis of mybatis plus automatic configuration class source code

The purpose of interpreting the source code of mybatis plus automatic configuration class is to help us better understand the working principle of mybatis plus and guide us how to correctly configure the attribute parameters of mybatis plus

The automatic configuration class of mybatis plus is MybatisPlusAutoConfiguration class, which is located in the com.baidu.mybatisplus.autoconfigure package in mybatis plus-boot-starter-3.1.0.jar package

Entering the MybatisPlusAutoConfiguration class, we can see that the methods sqlsessionfactory (datasource, datasource) and sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) marked by the following @ bean annotation help us configure the connected beans that Spring must use when integrating Mybatis: SqlSessionTemplate and SqlSessionTemplate. Both beans will be automatically initialized and injected into the Spring IOC container when the developer does not customize the beans of these two classes


We can see that @ EnableConfigurationProperties({MybatisPlusProperties.class}) and @ autoconfiguraeafter ({DataSourceAutoConfiguration. Class}) annotations are added to the MybatisPlusAutoConfiguration class, The former means that when the project has the property of · MybatisPlusProperties · configuration class, initialize the · MybatisPlusAutoConfiguration · configuration class and the bean s configured below and add them to the Spring IOC container; The latter indicates that the initialization of the data source automatic configuration class DataSourceAutoConfiguration is completed.

Entering the MybatisPlusProperties class, we found that it requires all property parameters related to mybatis plus to be prefixed with mybatis plus

This class parses the key value pair starting with mybatis plus in the environment configuration file and populates its attribute value during initialization

More source code readers can open the project through IDEA and view it.

3. Use mybatis plus to complete the database CRUD function

In order to reduce the length of the article, I only demonstrate the CRUD operation of a single table, which mainly involves the addition, modification, query and paging query of single and multiple data. The continuous table query of querying multiple tables at the same time using Mybatis plus is consistent with the implementation of user-defined mapper.xml in Mybatis. There are many cases on the Internet, so I won't extend the demonstration in this article

3.1 table building

Here I create a new stock in the mysql test database_ The info table represents the commodity information table, which is also needed to facilitate the learning of distributed e-commerce projects. The sql script for creating tables is as follows:

DROPTABLEIFEXISTS`stock_info`;
CREATETABLE`stock_info` (
                              `id`bigint(20) NOTNULL AUTO_INCREMENT COMMENT'Primary key',
                              `good_code`varchar(30) NOTNULLCOMMENT'Commodity code',
                              `good_name`varchar(100) DEFAULTNULLCOMMENT'Trade name',
                              `count`int(11) DEFAULT'0'COMMENT'Quantity of goods',
                              `created_date` datetime NOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'Creation time',
                              `created_by`varchar(30) NOTNULLDEFAULT'system'COMMENT'Creator',
                              `last_updated_date` datetime NOTNULLDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPCOMMENT'Last update time',
                              `last_updated_by`varchar(30) NOTNULLDEFAULT'system'COMMENT'Last updated by',
                              `unit_price`int(11) DEFAULT'0'COMMENT'Unit price',
                              PRIMARY KEY (`id`),
                              UNIQUEKEY`uk_good_code` (`good_code`)
) ENGINE=InnoDB AUTO_INCREMENT=22DEFAULTCHARSET=utf8mb4;

Readers can also use visual database client tools such as Navicat to create a new table, and then dump the table creation sql script and save it to the folder where the script is stored in the project.

Coding part

The coding of the project is carried out according to the hierarchical mode, which is divided into controller layer (XXController class under controller package), service layer (XXService class under service package) and database access layer (XXMapper class under mapper package)

3.2 create an entity class corresponding to the table

Create a new basic class BaseEntity under the com.example.mybatisplus.pojo package, which mainly contains the fields of creator, creation time, last modified by and last modified time in a table

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

@Data
publicclass BaseEntity implements Serializable {

    /**
     * Creator
     */
    @TableField(value = "created_by", fill = FieldFill.INSERT)
    private String createdBy;

    /**
     * Creation date (with time)
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(value = "created_date", fill = FieldFill.INSERT)
    private Date createdDate;

    /**
     * Modified by user ID
     */
    @TableField(value = "last_updated_by", fill = FieldFill.INSERT_UPDATE)
    private String lastUpdatedBy;

    /**
     * Modification date (with time)
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(value = "last_updated_date", fill = FieldFill.INSERT_UPDATE)
    private Date lastUpdatedDate;

}

Add the date format annotation @ JsonFormat annotation to the date field, and specify the date format in the pattern attribute

Also, create and stock under com.example.mybatisplus.pojo_ The entity class corresponding to the info table is StockInfo class and inherits the BaseEntity class above

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 {
    /**
     * Primary key ID
     */
    @TableId(type = IdType.AUTO)
    private Long id;
    /**
     * Commodity code
     */
    @TableField(value = "good_code")
    private String goodCode;

    /**
     * Trade name
     */
    @TableField(value = "good_name")
    private String goodName;

    /**
     * Inventory quantity
     */
    @TableField(value = "count")
    private Integer count;

    /**
     * Unit price of goods, unit: minute
     */
    @TableField(value = "unit_price")
    private Long unitPrice;

}

For the usage and detailed introduction of @ TableName, @ TableId and @ TableField annotations, readers can read the official documents Annotation section Master, I won't interpret it here. The source code of the project has been submitted to the gitee personal code warehouse. Other interface reference general classes such as ResponseVo, form query parameter encapsulation class, StockParam class and BaseParam class can be viewed through the project gitee address given at the end of the text.

3.3 database access layer code

Create a new StockMapper interface, inherit the BaseMapper interface class, and customize two methods

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> {
    /**
     * Find inventory quantity by commodity code
     * @param goodCode
     * @return
     */
    Integer findCountByGoodCode(String goodCode);

    /**
     * Update item inventory quantity
     * @param id
     * @param count
     * @return
     */
    Integer updateStockById(@Param("id") Long id, @Param("count") Integer count);
}

Hold down Ctrl and click BaseMapper to enter the BaseMapper class. You can see that this class defines common CRUD methods for single tables, saving developers time to customize CRUD methods

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;
// The generic parameter T in BaseMapper is the entity class corresponding to the table
publicinterface BaseMapper<T> {
    // Add record method
    int insert(T entity);
    // Delete method by ID
    int deleteById(Serializable id);
    // Delete method by column value matching
    int deleteByMap(@Param("cm") Map<String, Object> columnMap);
    // Delete by query criteria
    int delete(@Param("ew") Wrapper<T> wrapper);
    // Batch delete based on ID set
    int deleteBatchIds(@Param("coll") Collection<? extends Serializable> idList);
    // Update record by ID
    int updateById(@Param("et") T entity);
    // Update according to query criteria
    int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);
    // Query a single record by ID
    T selectById(Serializable id);
    // Batch query by ID set
    List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);
    // Multiple records are returned according to the column value matching query
    List<T> selectByMap(@Param("cm") Map<String, Object> columnMap);
    // Query a single record according to query criteria
    T selectOne(@Param("ew") Wrapper<T> queryWrapper);
    // Query the quantity that meets the criteria according to the criteria
    Integer selectCount(@Param("ew") Wrapper<T> queryWrapper);
    //Query multiple records according to query criteria and return entity object collection
    List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);
    //Query multiple records according to query criteria and return the Map set  
    List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> queryWrapper);
    // Query multiple records according to query criteria and return the object collection
    List<Object> selectObjs(@Param("ew") Wrapper<T> queryWrapper);
    // Paging query with query parameters returns paging objects with entity object sets
    IPage<T> selectPage(IPage<T> page, @Param("ew") Wrapper<T> queryWrapper);
    // Paging query with query parameters returns paging objects with Map sets
    IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param("ew") Wrapper<T> queryWrapper);
}

In the src/main/resources directory, we create a new layer by layer to the com/example/muybatisplus/mapper folder, and then create StockMapper.xml under the mapper folder to complete the implementation of custom methods in StockMapper.java. This step is the same as the previous Mybatis handwritten Mapper.xml as a persistence layer framework

StockMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//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_info
        where 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 layer code

Create a new StockService interface class and integrate the IService interface class

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> {
    /**
     * Single add save
     *
     * @param stockInfo
     * @return
     */
    ResponseVo saveOne(StockInfo stockInfo);

    /**
     * Batch add save
     *
     * @param stockInfoList
     * @return
     */
    ResponseVo saveBatch(List<StockInfo> stockInfoList);

    /**
     * Modify single
     *
     * @param stockInfo
     * @return
     */
    ResponseVo updateOne(StockInfo stockInfo);

    /**
     * Batch modification
     * @param stockInfoList
     * @return
     */
    ResponseVo updateBatch(List<StockInfo> stockInfoList);

    /**
     * Paging query with multiple conditions
     * @param stockParam
     * @param pageNo
     * @param pageSize
     * @return
     */
    ResponseVo findPageByCondition(StockParam stockParam, int pageNo, int pageSize);
    /**
     * Custom find item inventory
     * @param goodCode
     * @return
     */
    ResponseVo findCountByGoodCode(String goodCode);

    /**
     * Custom inventory
     * @param id
     * @param count
     * @return
     */
    ResponseVo updateStockCountById(Long id, Integer count);
}

In the StockService interface class, I define some CRUD abstract methods to access the database, and the return type of the method is uniformly ResponseVo

Then create a new StockServiceImpl class, inherit the ServiceImpl class and implement the StockService interface class

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";

    @Override
    public 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("Save failed");
        }
        return responseVo;
    }

    @Override
    public 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("Batch saving failed");
        }
        return responseVo;
    }

    @Override
    public 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("Update failed");
        }
        return responseVo;
    }

    @Override
    public 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("Batch update failed");
        }
        return responseVo;
    }

    @Override
    public 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;
    }

    @Override
    public 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("Failed to update inventory");
        }
        return responseVo;
    }

    @Override
    public 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("Failed to update commodity inventory");
        }
        return responseVo;
    }

    /**
     * Set creator and creation time
     *
     * @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()));
        }
    }

    /**
     * Set last modified by and last modified time
     *
     * @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()));
        }
    }

    /**
     * Processing dynamic queries
     *
     * @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");
        // Note that using the like query for columns with unique indexes will result in no results
        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;
    }
}

Entering the ServiceImpl class, we can see that it implements the IService interface class and implements most of the abstract methods in BaseMapper

The first generic parameter in the ServiceImpl class is a custom Mapper class inherited from BaseMapper, and the second generic parameter is an entity class that does not correspond to a table, corresponding to the StockMapper and StockInfo classes in this demonstration project. In this way, the custom Mapper class does not need to be injected into the custom ServiceImpl, but gets the database access proxy object through this.baseMapper.

3.5 Controller layer code

This layer of coding is very simple. You can directly call the injected StockService service class to complete the operation, but you need to add some spring MVC annotations to the classes and methods

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 {

    @Resource
    private StockService stockService;
    /**
     * Save single
     *
     * @param stockInfo
     * @return
     */
    @PostMapping("/saveOne")
    public ResponseVo saveOne(@RequestBody StockInfo stockInfo) {
        return stockService.saveOne(stockInfo);
    }

    /**
     * Batch save
     * @param stockInfoList
     * @return
     */
    @PostMapping("/saveBatch")
    public ResponseVo saveBatch(@RequestBody List<StockInfo> stockInfoList) {
        return stockService.saveBatch(stockInfoList);
    }

    /**
     * Update single
     * @param stockInfo
     * @return
     */
    @PostMapping("/updateOne")
    public ResponseVo updateOne(@RequestBody StockInfo stockInfo) {
        return stockService.updateOne(stockInfo);
    }

    /**
     * Batch update
     *
     * @param stockInfoList
     * @return
     */
    @PostMapping("/updateBatch")
    public ResponseVo updateBatch(@RequestBody List<StockInfo> stockInfoList) {
        return stockService.updateBatch(stockInfoList);
    }

    /**
     * Paging lookup
     *
     * @param pageNo Current page
     * @param pageSize Records per page
     * @param stockParam Inventory query parameter encapsulation object
     * @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);

    }

    /**
     * Find the inventory quantity of goods according to the commodity code
     *
     * @param goodCount
     * @return
     */
    @GetMapping("/goodCount")
    public ResponseVo findGoodCount(@RequestParam("goodCount") String goodCount) {
        return stockService.findCountByGoodCode(goodCount);
    }

    /**
     * Modify commodity inventory quantity
     *
     * @param id
     * @param count
     * @return
     */
    @PostMapping("/update/count")
    public ResponseVo updateStockCountById(Long id, Integer count) {
        return stockService.updateStockCountById(id, count);
    }

}

4 Effect experience

4.1 start up project

After the coding is completed, we start the project on the premise of starting the Mysql service locally. The following log information appears in the console, indicating that the startup is successful:

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 test interface

After the project is started successfully, we can open postman to test the interface

4.2.1 add a single piece of data to the test


The status code 200 returned from the response result indicates that the interface call is successful. At the same time, we can see that the following sql execution statements are printed on the background console

==>  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), Samsung 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 adding interfaces in test batch

We call the batch insert interface to insert 5 pieces of data at one time, and the interface returns a status code of 200, indicating that the data is added successfully

Then we can query the database through the client Navicat, and we can see that the data added to the database is added to the database by calling a single add and bulk add interface.

4.2.3 test paging query interface

Finally, we test the paging query effect:

1) First, perform paging query without query parameters. At this time, all the data in the table is queried

A total of 27 pieces of data are found in the interface response information, and 10 pieces of data are displayed on each page, a total of 3 pages

2) Finally, bring query parameters for paging query


During the screenshot, I collapsed the value in the records field. The data in the records field is as follows:

[
            {
                "createdBy": "system",
                "createdDate": "2021-12-04 16:36:50",
                "lastUpdatedBy": "system",
                "lastUpdatedDate": "2021-12-04 16:36:50",
                "id": 23,
                "goodCode": "GalaxyNote3",
                "goodName": "Samsung 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": "Samsung 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": "Samsung 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": "Samsung 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": "Samsung Note7",
                "count": 500,
                "unitPrice": 380000
            }
        ]

Limited to the length of the article, the test effects of other interfaces are not listed here one by one. Interested readers can clone this project and test the use of mybatis plus to achieve more database operation functions.

5 reference links

[1]Mybatis plus quick start

[2]Mybatis plus installation

[3]Spring boot integration mybatis plus

[4]Implementation of database CRUD with mybatis plus

[5]Mybatis plus paging plug-in

Project gitee address: mybatisplus

This article is the first WeChat official account. I love my articles readers' friends, I hope to scan the code below and add a WeChat concern. Thank you!

Posted by bob2006 on Sun, 05 Dec 2021 13:54:36 -0800