SpringBoot Integration (2) Detailed Explanation of MyBatisPlus Technology

Detailed explanation of MyBatisPlus

1. Standard data layer development

MyBatisPlus (MP for short) is an enhanced tool developed based on the MyBatis framework, designed to simplify development and improve efficiency

The official website of MyBatisPlus is:https://mp.baomidou.com/
insert image description here

1.1 Standard CRUD

insert image description here

1.2 Added

int insert (T t)
  • T: Generic, newly added to save new data

  • int: return value, return 1 after adding successfully, return 0 if not adding successfully

Add new operations in the test class:

@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
    
    

    @Autowired
    private UserDao userDao;

    @Test
    void testSave() {
    
    
        User user = new User();
        user.setName("yyds");
        user.setPassword("yyds");
        user.setAge(12);
        user.setTel("10086");
        userDao.insert(user);
    }
}

After the test is executed, a piece of data is added to the database table. But the primary key ID in the data is a bit long, which is determined by the primary key ID generation strategy.

1.3 delete

int deleteById (Serializable id)
  • Serializable: parameter type

    • Thinking: Why is the parameter type a serialization class?

      • String and Number are subclasses of Serializable,
      • Number is the parent class of Float, Double, Integer, etc.
      • The data types that can be used as primary keys are already subclasses of Serializable.
      • MP uses Serializable as a parameter type, just as we can use Object to receive any data type.
  • int: return value type, data deleted successfully returns 1, undeleted data returns 0.

Do it in the test class:

 @SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
    
    

    @Autowired
    private UserDao userDao;

    @Test
    void testDelete() {
    
    
        userDao.deleteById(1401856123725713409L);
    }
}

1.4 Modifications

int updateById(T t);
  • T: Generic, the data content that needs to be modified, note that because it is modified according to the ID, the ID attribute value needs to be included in the incoming object

  • int: return value, return 1 after modification is successful, return 0 for unmodified data

Add new operations in the test class:

@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
    
    

    @Autowired
    private UserDao userDao;

    @Test
    void testUpdate() {
    
    
        User user = new User();
        user.setId(1L);
        user.setName("Tom888");
        user.setPassword("tom888");
        userDao.updateById(user);
    }
}

Note: When modifying, only modify the fields with values ​​in the entity object.

1.5 Query by ID

T selectById (Serializable id)
  • Serializable: parameter type, the value of the primary key ID
  • T: query based on ID will only return one piece of data
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
    
    

    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetById() {
    
    
        User user = userDao.selectById(2L);
        System.out.println(user);
    }
}

1.6 Query all

List<T> selectList(Wrapper<T> queryWrapper)
  • Wrapper: The conditions used to construct conditional queries, currently we do not have a direct pass as Null
  • List: Because the query is all, the returned data is a collection
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
    
    

    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll() {
    
    
        List<User> userList = userDao.selectList(null);
        System.out.println(userList);
    }
}

The methods we call all come from the BaseMapper class inherited from the DAO interface. There are many methods in it.

1.7 Pagination query

The method used for pagination query is:

IPage<T> selectPage(IPage<T> page, Wrapper<T> queryWrapper)
  • IPage: Used to build paging query conditions
  • Wrapper: The conditions used to construct conditional queries, currently we do not have a direct pass as Null
  • IPage: return value, you will find that the return value of building pagination conditions and methods is IPage

IPage is an interface, we need to find its implementation class to build it, and there is an implementation class for it Page.

Step 1: Call the method to pass in parameters to get the return value

@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
    
    

    @Autowired
    private UserDao userDao;
    
    //分页查询
    @Test
    void testSelectPage(){
    
    
        //1 创建IPage分页对象,设置分页参数,1为当前页码,3为每页显示的记录数
        IPage<User> page=new Page<>(1,3);
        //2 执行分页查询
        userDao.selectPage(page,null);
        //3 获取分页结果
        System.out.println("当前页码值:"+page.getCurrent());
        System.out.println("每页显示数:"+page.getSize());
        System.out.println("一共多少页:"+page.getPages());
        System.out.println("一共多少条数据:"+page.getTotal());
        System.out.println("数据:"+page.getRecords());
    }
}

Step 2: Set up the pagination blocker

This interceptor MP has been provided for us, we only need to configure it as a bean object managed by Spring.

@Configuration
public class MybatisPlusConfig {
    
    
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
    
    
        //1 创建MybatisPlusInterceptor拦截器对象
        MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
        //2 添加分页拦截器
        mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mpInterceptor;
    }
}

Step 3: Run the test program

If you want to view the SQL statements executed by MP, you can modify the application.yml configuration file,

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印SQL日志到控制台

2. DQL programming control

2.1 Condition query

2.1.1 Classes for conditional queries

  • MyBatisPlus encapsulates the writing of complex SQL query conditions, and completes the combination of query conditions in the form of programming.

We have seen this before. For example, when querying all and paging queries, we have seen a Wrapperclass. This class is used to build query conditions, as shown in the following figure:

insert image description here

2.1.2 Environment construction

  • Create a SpringBoot project

  • Add corresponding dependencies in pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.5.0</version>
        </parent>
        <groupId>com.yyds</groupId>
        <artifactId>mybatisplus_02_dql</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <properties>
            <java.version>1.8</java.version>
        </properties>
        <dependencies>
    
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.4.1</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
    
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.16</version>
            </dependency>
    
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
    
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
    
  • Write the UserDao interface

    @Mapper
    public interface UserDao extends BaseMapper<User> {
          
          
    }
    
  • Write model classes

    @Data
    public class User {
          
          
        private Long id;
        private String name;
        private String password;
        private Integer age;
        private String tel;
    }
    
  • Write boot class

    @SpringBootApplication
    public class Mybatisplus02DqlApplication {
          
          
    
        public static void main(String[] args) {
          
          
            SpringApplication.run(Mybatisplus02DqlApplication.class, args);
        }
    
    }
    
  • Write a configuration file

    # dataSource
    spring:
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC
        username: root
        password: root
    # mp日志
    mybatis-plus:
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    
  • write test class

    @SpringBootTest
    class Mybatisplus02DqlApplicationTests {
          
          
    
        @Autowired
        private UserDao userDao;
        
        @Test
        void testGetAll(){
          
          
            List<User> userList = userDao.selectList(null);
            System.out.println(userList);
        }
    }
    
  • application.yml add the following content:

    # mybatis-plus日志控制台输出
    mybatis-plus:
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
      global-config:
        banner: off # 关闭mybatisplus启动图标
    
    • Cancel SpringBoot's log printing

      application.yml add the following content:

      spring:
        main:
          banner-mode: off # 关闭SpringBoot启动图标(banner)
      

2.1.3 Construct conditional query

When querying, our entry is on the Wrapper class, because it is an interface, so we need to find its corresponding implementation class, and there are many implementation classes, indicating that we have multiple ways to construct query condition objects ,

  1. Let's look at the first one first:QueryWrapper
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
    
    

    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll(){
    
    
        QueryWrapper qw = new QueryWrapper();
        qw.lt("age",18);
        List<User> userList = userDao.selectList(qw);
        System.out.println(userList);
    }
}
  • lt: less than (<), the final sql statement is

    SELECT id,name,password,age,tel FROM user WHERE (age < ?)
    

After the introduction of the first method, there is a small problem that it is easy to make mistakes when writing the conditions. For example, if the age is written incorrectly, the query will fail.

  1. Let's look at the second one:QueryWrapper based on using lambda
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
    
    

    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll(){
    
    
        QueryWrapper<User> qw = new QueryWrapper<User>();
        qw.lambda().lt(User::getAge, 10);//添加条件
        List<User> userList = userDao.selectList(qw);
        System.out.println(userList);
    }
}
  • User::getAget, in the lambda expression, class name::method name, the final sql statement is:
SELECT id,name,password,age,tel FROM user WHERE (age < ?)

Note: Generics cannot be saved when building LambdaQueryWrapper.

At this time, when we write the condition again, there will be no wrong name, but there is an extra layer of lambda() call behind qw

  1. Then look at the third:LambdaQueryWrapper
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
    
    

    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll(){
    
    
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.lt(User::getAge, 10);
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }
}

This method solves the problems of the previous method.

2.1.4 Multi-condition construction

After learning three ways to construct query objects, each has its own characteristics, so you can use any one. Just now, there is only one condition, so how to construct it if there are multiple conditions?

Requirements: Query the database table for user information between the ages of 10 and 30

@SpringBootTest
class Mybatisplus02DqlApplicationTests {
    
    

    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll(){
    
    
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.lt(User::getAge, 30);
        lqw.gt(User::getAge, 10);
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }
}
  • gt: greater than (>), the final SQL statement is

    SELECT id,name,password,age,tel FROM user WHERE (age < ? AND age > ?)
    
  • When constructing multiple conditions, chain programming can be supported

    LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
    lqw.lt(User::getAge, 30).gt(User::getAge, 10);
    List<User> userList = userDao.selectList(lqw);
    System.out.println(userList);
    

Requirement: Query the data in the database table, the age is less than 10 or the age is greater than 30

@SpringBootTest
class Mybatisplus02DqlApplicationTests {
    
    

    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll(){
    
    
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.lt(User::getAge, 10).or().gt(User::getAge, 30);
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }
}
  • or() is equivalent to the keyword in our sql statement or, if it is not added by default and, the final sql statement is:

    SELECT id,name,password,age,tel FROM user WHERE (age < ? OR age > ?)
    

3.1.5 Null judgment

Requirements: Query the database table, and query the eligible records according to the input age range

When the user enters a value,

​ If you only enter the first box, it means you want to query users who are older than this age

​ If only the second box is entered, it means that users younger than the age are to be queried

​ If both boxes are entered, it means that users whose age is between two ranges are to be queried

Think about the first question: If the background wants to receive two data from the front end, how should it be received?

We can use two simple data types, or a model class, but there is currently only one age attribute in the User class, such as:

@Data
public class User {
    
    
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
}

Solution 1: Add attribute age2, this method can but will affect the attribute content of the original model class

@Data
public class User {
    
    
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
    private Integer age2;
}

Solution 2: Create a new model class, let it inherit the User class, and add the age2 attribute to it, and UserQuery adds the age2 attribute after having the User attribute.

@Data
public class User {
    
    
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
}

@Data
public class UserQuery extends User {
    
    
    private Integer age2;
}

After the environment is ready, let's implement the requirements just now:

@SpringBootTest
class Mybatisplus02DqlApplicationTests {
    
    

    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll(){
    
    
        //模拟页面传递过来的查询数据
        UserQuery uq = new UserQuery();
        uq.setAge(10);
        uq.setAge2(30);
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        if(null != uq.getAge2()){
    
    
            lqw.lt(User::getAge, uq.getAge2());
        }
        if( null != uq.getAge()) {
    
    
            lqw.gt(User::getAge, uq.getAge());
        }
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }
}

The above writing method can complete the judgment that the condition is not empty, but the problem is obvious. If there are many conditions, each condition needs to be judged, and the amount of code is relatively large. Let’s look at the simplified method provided by MP:

@SpringBootTest
class Mybatisplus02DqlApplicationTests {
    
    

    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll(){
    
    
        //模拟页面传递过来的查询数据
        UserQuery uq = new UserQuery();
        uq.setAge(10);
        uq.setAge2(30);
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.lt(null!=uq.getAge2(),User::getAge, uq.getAge2());
        lqw.gt(null!=uq.getAge(),User::getAge, uq.getAge());
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }
}

2.2 Query projection

2.2.1 Query specified fields

At present, when we query data, we do nothing and the default is to query the contents of all fields in the table. The query projection we call does not query all fields, but only queries the data of the specified content.

@SpringBootTest
class Mybatisplus02DqlApplicationTests {
    
    

    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll(){
    
    
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.select(User::getId,User::getName,User::getAge);
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }
}
  • The select(…) method is used to set the field column of the query, and multiple fields can be set. The final sql statement is:

    SELECT id,name,age FROM user
    
  • If you are not using lambda, you need to manually specify the field

    @SpringBootTest
    class Mybatisplus02DqlApplicationTests {
          
          
    
        @Autowired
        private UserDao userDao;
        
        @Test
        void testGetAll(){
          
          
            QueryWrapper<User> lqw = new QueryWrapper<User>();
            lqw.select("id","name","age","tel");
            List<User> userList = userDao.selectList(lqw);
            System.out.println(userList);
        }
    }
    

2.2.2 Aggregation query

Requirements: aggregate function query, complete the use of count, max, min, avg, sum

count: the total number of records

max: maximum value

min: minimum value

avg: average value

sum: Sum

@SpringBootTest
class Mybatisplus02DqlApplicationTests {
    
    

    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll(){
    
    
        QueryWrapper<User> lqw = new QueryWrapper<User>();
        //lqw.select("count(*) as count");
        //SELECT count(*) as count FROM user
        //lqw.select("max(age) as maxAge");
        //SELECT max(age) as maxAge FROM user
        //lqw.select("min(age) as minAge");
        //SELECT min(age) as minAge FROM user
        //lqw.select("sum(age) as sumAge");
        //SELECT sum(age) as sumAge FROM user
        lqw.select("avg(age) as avgAge");
        //SELECT avg(age) as avgAge FROM user
        List<Map<String, Object>> userList = userDao.selectMaps(lqw);
        System.out.println(userList);
    }
}

In order to make it easier when encapsulating the results, we named the aggregation functions above to obtain the data later

2.2.3 Group query

Requirements: group query, complete group by query use

@SpringBootTest
class Mybatisplus02DqlApplicationTests {
    
    

    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll(){
    
    
        QueryWrapper<User> lqw = new QueryWrapper<User>();
        lqw.select("count(*) as count,tel");
        lqw.groupBy("tel");
        List<Map<String, Object>> list = userDao.selectMaps(lqw);
        System.out.println(list);
    }
}
  • groupBy is grouping, and the final sql statement is

    SELECT count(*) as count,tel FROM user GROUP BY tel
    

Notice:

  • Aggregation and grouping queries cannot be done using lambda expressions
  • MP is just an enhancement to MyBatis. If MP cannot be implemented, we can directly use MyBatis in the DAO interface to implement it.

2.3 Query conditions

There are many query conditions for MP:

  • range matching (> , = , between)
  • Fuzzy matching (like)
  • Empty judgment (null)
  • inclusive match (in)
  • group
  • order
  • ……

2.3.1 Equivalent query

Requirements: Query user information based on username and password

@SpringBootTest
class Mybatisplus02DqlApplicationTests {
    
    

    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll(){
    
    
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.eq(User::getName, "Jerry").eq(User::getPassword, "jerry");
        User loginUser = userDao.selectOne(lqw);
        System.out.println(loginUser);
    }
}
  • eq(): Equivalent =, the corresponding sql statement is

    SELECT id,name,password,age,tel FROM user WHERE (name = ? AND password = ?)
    
  • selectList: the query result is multiple or single

  • selectOne: The query result is a single

2.3.2 Range query

Requirement: range query for age, use lt(), le(), gt(), ge(), between() for range query

@SpringBootTest
class Mybatisplus02DqlApplicationTests {
    
    

    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll(){
    
    
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.between(User::getAge, 10, 30);
        //SELECT id,name,password,age,tel FROM user WHERE (age BETWEEN ? AND ?)
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }
}
  • gt(): greater than (>)
  • ge(): greater than or equal to (>=)
  • lt(): less than (<)
  • lte(): less than or equal to (<=)
  • between():between ? and ?

2.3.3 Fuzzy query

JRequirements: Query the user information whose value of the name attribute in the table starts with, use like to perform fuzzy query

@SpringBootTest
class Mybatisplus02DqlApplicationTests {
    
    

    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll(){
    
    
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.likeLeft(User::getName, "J");
        //SELECT id,name,password,age,tel FROM user WHERE (name LIKE ?J)
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }
}
  • like(): Add percent signs before and after, such as %J%
  • likeLeft(): Add a percent sign in front, such as %J
  • likeRight(): Add a percent sign after it, such as J%

2.3.4 Sort queries

Requirement: Query all data, and then sort by id descending

@SpringBootTest
class Mybatisplus02DqlApplicationTests {
    
    

    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll(){
    
    
        LambdaQueryWrapper<User> lwq = new LambdaQueryWrapper<>();
        /**
         * condition :条件,返回boolean,
         		当condition为true,进行排序,如果为false,则不排序
         * isAsc:是否为升序,true为升序,false为降序
         * columns:需要操作的列
         */
        lwq.orderBy(true,false, User::getId);

        userDao.selectList(lw
    }
}

In addition to the query condition construction methods introduced above, there are many other methods, such as isNull, isNotNull, in, notIn and other methods to choose from. For details, refer to the condition constructor in the official document to learn and use. The specific URL for:

https://mp.baomidou.com/guide/wrapper.html#abstractwrapper

2.4 Mapping Compatibility

Previously, we have been able to query data from tables and encapsulate the data into model classes. This whole process involves a table and a model class:

insert image description here

Problem 1: Table fields are out of sync with coded attribute design

When the column names of the table and the attribute names of the model class are inconsistent, the data will not be encapsulated into the model object. At this time, one of them needs to be modified. If the premise is that neither side can be changed, how to solve it?

MP provides us with an annotation @TableField, which can be used to realize the mapping relationship between the attribute name of the model class and the column name of the table

insert image description here

Problem 2: The encoding adds properties that are not defined in the database

When there is an additional field in the model class that does not exist in the database table, it will cause the generated SQL statement to query the field that does not exist in the database when selecting, and an error will be reported when the program runs. The error message is:

Unknown column 'extra field name' in 'field list'

The specific solution uses @TableFieldannotations, which have a property called exist, set whether the field exists in the database table, if it is set to false, it does not exist, and when the SQL query is generated, the field will not be queried again.

insert image description here

Question 3: Using the default query opens up more field viewing permissions

By querying the data of all the columns in the table, some sensitive data may be queried and returned to the front end. At this time, we need to restrict which fields are not queried by default. The solution is @TableFieldan attribute of the annotation called select, which sets whether the value of the field needs to be queried by default, true (default value) means that the field is queried by default, and false means that the field is not queried by default.

insert image description here

Knowledge point 1: @TableField

name @TableField
type attribute annotation
Location above the model class attribute definition
effect Set the field relationship in the database table corresponding to the current attribute
related attributes value (default): set the database table field name
exist: set whether the attribute exists in the database table field, the default is true, this attribute cannot be combined with value use
select: set whether the attribute participates in the query, this attribute is different from the select() mapping configuration conflict

Question 4: The table name is not synchronized with the code development design

The problem is mainly that the name of the table is inconsistent with the name of the model class, causing the query to fail. At this time, the following error message is usually reported:

Table ‘databaseName.tableNaem’ doesn’t exist, the translation is that the table in the database does not exist.

The solution is to use another annotation provided by MP @TableNameto set the correspondence between tables and model classes.
insert image description here

Knowledge point 2: @TableName

name @TableName
type class annotation
Location above the model class definition
effect Set the current class corresponding to the database table relationship
related attributes value (default): set the database table name

3. DML programming control

We have introduced the query-related operations, and then we need to explain the content of the other three, additions, deletions, and modifications. To explain one by one, the first is the content in the new (insert).

3.1 id generation policy control

  • Different tables apply different id generation strategies
    • log: increment(1,2,3,4,...)
    • Shopping Orders: Special Rules (FQ23948AK3843)
    • Takeaway order: related area date and other information (10 04 20200314 34 91)
    • Relational table: id can be omitted
    • ……

Different businesses should use different ID generation methods, so what primary key generation strategies are provided in MP, and how should we choose?

Here we need to use an annotation of MP called @TableId( @TableId(type = IdType.INPUT))

Knowledge point 1: @TableId

name @TableId
type attribute annotation
Location above the definition of the property used to represent the primary key in the model class
effect Set the generation strategy of the primary key attribute in the current class
related attributes value (default): set the primary key name of the database table
type: set the generation strategy of the primary key attribute, and check the value according to the enumeration value of IdType

As can be seen from the source code, in addition to the AUTO strategy, there are several generation strategies as follows:

  • NONE: No id generation strategy is set
  • INPUT: The user manually enters the id
  • ASSIGN_ID: Snowflake algorithm generates id (compatible with numeric and string types)
  • ASSIGN_UUID: Use the UUID generation algorithm as the id generation strategy
  • Several other strategies are obsolete and will be replaced by ASSIGN_ID and ASSIGN_UUID.

What is distributed ID?

  • When the amount of data is large enough, one database server cannot store it. At this time, multiple database servers are needed for storage.
  • For example, the order table may be stored on different servers
  • If you use the auto-increment primary key of the database table, there will be a conflict because it is on two servers
  • At this time, a globally unique ID is needed, which is the distributed ID.

Introduced the generation strategies of these primary key IDs, which one should we use in the future?

  • NONE: Do not set the id generation strategy, MP is not automatically generated, approximately equal to INPUT, so both methods need to be manually set by the user, but the first problem with manual setting is that the same ID is prone to cause primary key conflicts, in order to ensure that the primary keys do not conflict You need to make a lot of judgments, and it is more complicated to implement.
  • AUTO: The database ID is auto-incremented. This strategy is suitable for use when there is only one database server, and cannot be used as a distributed ID
  • ASSIGN_UUID: It can be used in a distributed environment, and it can be guaranteed to be unique, but the generated primary key is a 32-bit string, which is too long to occupy space and cannot be sorted, and the query performance is also slow
  • ASSIGN_ID: It can be used in a distributed situation. The generated numbers are of the Long type and can be sorted with high performance. However, the generated strategy is related to the server time. If the system time is modified, it may lead to duplicate primary keys.
  • To sum up, each primary key strategy has its own advantages and disadvantages, and it is the most sensible choice to choose according to the actual situation of your own project business.

Simplified configuration

mybatis-plus:
  global-config:
    db-config:
    	id-type: assign_id

Once configured, the primary key ID strategy for each model class will be assign_id.

Mapping relationship between database tables and model classes

MP will use the lowercase first letter of the class name of the model class as the table name by default. If the names of the database tables start with tbl_, then we need to add all the model classes @TableName, such as:

The configuration is still relatively cumbersome. The simplified method is to configure the following content in the configuration file:

mybatis-plus:
  global-config:
    db-config:
    	table-prefix: tbl_

Set the prefix content of the table, so that MP will tbl_add the first letter of the model class in lowercase, and it will just be assembled into the table name of the database.

3.2 Multi-record operation

How to implement multiple deletions, let's find the corresponding API method

int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

Requirement: Delete the data in the database table according to the incoming id set.

@SpringBootTest
class Mybatisplus03DqlApplicationTests {
    
    

    @Autowired
    private UserDao userDao;
	
    @Test
    void testDelete(){
    
    
        //删除指定多条数据
        List<Long> list = new ArrayList<>();
        list.add(1402551342481838081L);
        list.add(1402553134049501186L);
        list.add(1402553619611430913L);
        userDao.deleteBatchIds(list);
    }
}

After the execution is successful, the data in the database table will be deleted according to the specified id.

In addition to bulk deletion according to the id set, you can also perform batch query according to the id set. Let’s take a look at the API first.

List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

The method name is translated as: query (batch query based on ID), and the parameter is a collection that can store multiple id values.

Requirement: Query user information based on the incoming ID set

@SpringBootTest
class Mybatisplus03DqlApplicationTests {
    
    

    @Autowired
    private UserDao userDao;
	
    @Test
    void testGetByIds(){
    
    
        //查询指定多条数据
        List<Long> list = new ArrayList<>();
        list.add(1L);
        list.add(3L);
        list.add(4L);
        userDao.selectBatchIds(list);
    }
}

The query result will be queried according to the specified id value passed in

3.3 Tombstone

How to implement logical deletion in MP?

Step 1: Modify the database table to add deletedcolumns

The field name can be arbitrary, and the content can also be customized. For example, 0it means normal, 1and it means delete. You can set its default value to normal when adding a column 0.

Step 2: Add attributes to the entity class

(1) Add an attribute name corresponding to the column of the database table. The name can be arbitrary. If it does not match the column name of the data table, you can use @TableField for relationship mapping. If it is consistent, it will automatically correspond.

(2) Identify the newly added field as a tombstone field, use@TableLogic

@Data
//@TableName("tbl_user") 可以不写是因为配置了全局配置
public class User {
    
    
    @TableId(type = IdType.ASSIGN_UUID)
    private String id;
    private String name;
    @TableField(value="pwd",select=false)
    private String password;
    private Integer age;
    private String tel;
    @TableField(exist=false)
    private Integer online;
    @TableLogic(value="0",delval="1")
    //value为正常数据的值,delval为删除数据的值
    private Integer deleted;
}

Step 3: Run the delete method

@SpringBootTest
class Mybatisplus03DqlApplicationTests {
    
    

    @Autowired
    private UserDao userDao;
	
    @Test
    void testDelete(){
    
    
       userDao.deleteById(1L);
    }
}

The last step of logic deletion is the update operation, which will modify the specified field to the value corresponding to the deletion status.

think

Does logical deletion affect the query?

  • Execute query operation

    @SpringBootTest
    class Mybatisplus03DqlApplicationTests {
          
          
    
        @Autowired
        private UserDao userDao;
    	
        @Test
        void testFind(){
          
          
           System.out.println(userDao.selectList(null));
        }
    }
    

    It is conceivable that the logical deletion of MP will add an undeleted condition to all queries, that is, the deleted data should not be queried.

  • If you still want to query all the deleted data, how to achieve it?

    @Mapper
    public interface UserDao extends BaseMapper<User> {
          
          
        //查询所有数据包含已经被删除的数据
        @Select("select * from tbl_user")
        public List<User> selectAll();
    }
    
  • @TableLogicIf each table needs to have logical deletion, then it is necessary to add annotations to the attributes of each model class , how to optimize?

    Add global configuration in the configuration file, as follows:

    mybatis-plus:
      global-config:
        db-config:
          # 逻辑删除字段名
          logic-delete-field: deleted
          # 逻辑删除字面值:未删除为0
          logic-not-delete-value: 0
          # 逻辑删除字面值:删除为1
          logic-delete-value: 1
    

Knowledge point 1: @TableLogic

name @TableLogic
type attribute annotation
Location above the attribute definition in the model class used to represent the field to delete
effect Identify the field as a field for logical deletion
related attributes value: Logical undeleted value
delval: Logical deleted value

3.4 Optimistic locking

3.4.1 Concept

Problems caused by business concurrency:spike

  • If there are 100 products or tickets on sale, in order to ensure that each product or ticket can only be purchased by one person, how to ensure that there will be no overbought or repeated sales
  • For this type of problem, there are actually many solutions that can be used
  • The first thing that comes to mind is the lock. Locking in one server can be solved, but if it is locked in multiple servers, there is no way to control it. For example, there are two servers selling tickets in 12306. If all locks are added, it may also cause two threads to sell tickets at the same time, or there will be concurrency problems
  • The method we will introduce next is a solution for small businesses, because the performance of the database itself is a bottleneck, and if its concurrency exceeds 2000, other solutions need to be considered.

To put it simply, the main problem solved by optimistic locking is that when a record is to be updated, it is hoped that this record has not been updated by others.

3.4.2 Implementation ideas

The implementation of optimistic locking:

  • Add a version column to the database table, for example, the default value is 1
  • Before the first thread wants to modify the data, get the version=1 in the current database when fetching the records
  • Before the second thread wants to modify the data, get the version=1 in the current database when fetching the records
  • When the first thread performs an update, set version = newVersion where version = oldVersion
    • newVersion = version+1 [2]
    • oldVersion = version [1]
  • When the second thread performs the update, set version = newVersion where version = oldVersion
    • newVersion = version+1 [2]
    • oldVersion = version [1]
  • If both threads update data, both the first and second threads may execute first
    • If the first thread performs the update first, it will change the version to 2,
    • When the second thread updates again, set version = 2 where version = 1, at this time the data version of the database table is already 2, so the second thread will fail to modify
    • If the second thread performs the update first, it will change the version to 2,
    • When the first thread updates again, set version = 2 where version = 1. At this time, the data version of the database table is already 2, so the first thread will fail to modify
    • No matter who executes first, it will ensure that only one thread can update data. This is the analysis of the implementation principle of optimistic lock provided by MP.

3.4.3 Implementation steps

After analyzing the steps, the specific implementation steps are as follows:

Step 1: Add column to database table

The column name can be arbitrary, such as using version, set the default value for the column1

Step 2: Add corresponding attributes to the model class

According to the added field column name, add the corresponding attribute value in the model class

@Data
//@TableName("tbl_user") 可以不写是因为配置了全局配置
public class User {
    
    
    @TableId(type = IdType.ASSIGN_UUID)
    private String id;
    private String name;
    @TableField(value="pwd",select=false)
    private String password;
    private Integer age;
    private String tel;
    @TableField(exist=false)
    private Integer online;
    private Integer deleted;
    @Version
    private Integer version;
}
Step 3: Add an interceptor for optimistic locking
@Configuration
public class MpConfig {
    
    
    @Bean
    public MybatisPlusInterceptor mpInterceptor() {
    
    
        //1.定义Mp拦截器
        MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
        //2.添加乐观锁拦截器
        mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mpInterceptor;
    }
}
Step 4: Perform update operation
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
    
    

    @Autowired
    private UserDao userDao;
	
    @Test
    void testUpdate(){
    
    
       User user = new User();
        user.setId(3L);
        user.setName("Jock666");
        userDao.updateById(user);
    }
}

You will find that this modification does not update the version field, because the version data is not carried.

Add version data

@SpringBootTest
class Mybatisplus03DqlApplicationTests {
    
    

    @Autowired
    private UserDao userDao;
	
    @Test
    void testUpdate(){
    
    
        User user = new User();
        user.setId(3L);
        user.setName("Jock666");
        user.setVersion(1);
        userDao.updateById(user);
    }
}

You will find that what we pass is 1, and MP will add 1 to it, and then update it back to the database table.

So in order to achieve optimistic locking, the first step should be to get the version in the table, and then use the version as a condition to add 1 to the version and update it back to the database table, so when we query, we need to query it

@SpringBootTest
class Mybatisplus03DqlApplicationTests {
    
    

    @Autowired
    private UserDao userDao;
	
    @Test
    void testUpdate(){
    
    
        //1.先通过要修改的数据id将当前数据查询出来
        User user = userDao.selectById(3L);
        //2.将要修改的属性逐一设置进去
        user.setName("Jock888");
        userDao.updateById(user);
    }
}

After roughly analyzing the implementation steps of optimistic locking, let's simulate a locking situation to see if it can be realized that when multiple people modify the same data, only one person can modify it successfully.

@SpringBootTest
class Mybatisplus03DqlApplicationTests {
    
    

    @Autowired
    private UserDao userDao;
	
    @Test
    void testUpdate(){
    
    
       //1.先通过要修改的数据id将当前数据查询出来
        User user = userDao.selectById(3L);     //version=3
        User user2 = userDao.selectById(3L);    //version=3
        user2.setName("Jock aaa");
        userDao.updateById(user2);              //version=>4
        user.setName("Jock bbb");
        userDao.updateById(user);               //verion=3?条件还成立吗?
    }
}

Official documentation:

https://mp.baomidou.com/guide/interceptor-optimistic-locker.html#optimisticlockerinnerinterceptor

4. Rapid development

4.1 Code generator

Step 1: Create a Maven project

Step 2: Import the corresponding jar package

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.1</version>
    </parent>
    <groupId>com.yyds</groupId>
    <artifactId>mybatisplus_04_generator</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!--spring webmvc-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--mybatisplus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>

        <!--druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>

        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!--test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>

        <!--代码生成器-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.4.1</version>
        </dependency>

        <!--velocity模板引擎-->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.3</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Step 3: Write the bootstrap class

@SpringBootApplication
public class Mybatisplus04GeneratorApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(Mybatisplus04GeneratorApplication.class, args);
    }

}

Step 4: Create Code Generation Classes

public class CodeGenerator {
    
    
    public static void main(String[] args) {
    
    
        //1.获取代码生成器的对象
        AutoGenerator autoGenerator = new AutoGenerator();

        //设置数据库相关配置
        DataSourceConfig dataSource = new DataSourceConfig();
        dataSource.setDriverName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        autoGenerator.setDataSource(dataSource);

        //设置全局配置
        GlobalConfig globalConfig = new GlobalConfig();
        globalConfig.setOutputDir(System.getProperty("user.dir")+"/mybatisplus_04_generator/src/main/java");    //设置代码生成位置
        globalConfig.setOpen(false);    //设置生成完毕后是否打开生成代码所在的目录
        globalConfig.setAuthor("yyds");    //设置作者
        globalConfig.setFileOverride(true);     //设置是否覆盖原始生成的文件
        globalConfig.setMapperName("%sDao");    //设置数据层接口名,%s为占位符,指代模块名称
        globalConfig.setIdType(IdType.ASSIGN_ID);   //设置Id生成策略
        autoGenerator.setGlobalConfig(globalConfig);

        //设置包名相关配置
        PackageConfig packageInfo = new PackageConfig();
        packageInfo.setParent("com.aaa");   //设置生成的包名,与代码所在位置不冲突,二者叠加组成完整路径
        packageInfo.setEntity("domain");    //设置实体类包名
        packageInfo.setMapper("dao");   //设置数据层包名
        autoGenerator.setPackageInfo(packageInfo);

        //策略设置
        StrategyConfig strategyConfig = new StrategyConfig();
        strategyConfig.setInclude("tbl_user");  //设置当前参与生成的表名,参数为可变参数
        strategyConfig.setTablePrefix("tbl_");  //设置数据库表的前缀名称,模块名 = 数据库表名 - 前缀名  例如: User = tbl_user - tbl_
        strategyConfig.setRestControllerStyle(true);    //设置是否启用Rest风格
        strategyConfig.setVersionFieldName("version");  //设置乐观锁字段名
        strategyConfig.setLogicDeleteFieldName("deleted");  //设置逻辑删除字段名
        strategyConfig.setEntityLombokModel(true);  //设置是否启用lombok
        autoGenerator.setStrategy(strategyConfig);
        //2.执行生成操作
        autoGenerator.execute();
    }
}

For the code content in the code generator, we can directly obtain the code from the official document for modification,

https://mp.baomidou.com/guide/generator.html

Step 5: Run the program

After running successfully, a lot of code will be generated in the current project, the code contains controller, service, mapperandentity

4.2 CRUD of Service in MP

MP provides a Service interface and implementation class, respectively: IServiceand ServiceImpl, the latter is a concrete realization of the former.

In the future, the Service we wrote ourselves can be modified as follows:

public interface UserService extends IService<User>{
    
    
	
}

@Service
public class UserServiceImpl extends ServiceImpl<UserDao, User> implements UserService{
    
    

}

The advantage after the modification is that MP has helped us realize some basic additions, deletions, changes, and queries of the business layer, which can be used directly.

Write a test class for testing:

@SpringBootTest
class Mybatisplus04GeneratorApplicationTests {
    
    

    private IUserService userService;

    @Test
    void testFindAll() {
    
    
        List<User> list = userService.list();
        System.out.println(list);
    }

}

Note: The MyBatis environment is not configured in the mybatisplus_04_generator project. If you want to run it, you need to extract and improve the content in the configuration file before running it.

Thinking: What methods can be used in the Service layer encapsulated by MP?

Check out the official documentation:https://mp.baomidou.com/guide/crud-interface.html

Guess you like

Origin blog.csdn.net/qq_44665283/article/details/128961816