MybatisPlus quick start

MybatisPlus

document

Official document: https://baomidou.com/

Introduction

MyBatis-Plus (opens new window) (MP for short) is an enhanced tool for MyBatis (opens new window) . On the basis of MyBatis, only enhancements are made without changes, and it was born to simplify development and improve efficiency.

characteristic

  • No intrusion : only enhancement and no change, the introduction of it will not affect the existing project, as smooth as silk
  • Low loss : the basic CURD will be automatically injected when it is started, the performance is basically lossless, and the object-oriented operation is directly performed
  • Powerful CRUD operations : Built-in general Mapper and general Service, most of the CRUD operations on a single table can be realized with only a small amount of configuration, and there is a powerful condition constructor to meet various usage needs
  • Support Lambda form call : through Lambda expressions, it is convenient to write various query conditions, no need to worry about field typos
  • Supports automatic primary key generation : supports up to 4 primary key strategies (including a distributed unique ID generator - Sequence), which can be freely configured to perfectly solve the primary key problem
  • Support ActiveRecord mode : support ActiveRecord form call, the entity class only needs to inherit the Model class to perform powerful CRUD operations
  • Support custom global general operations : support global general method injection ( Write once, use anywhere )
  • Built-in code generator : use code or Maven plug-in to quickly generate Mapper, Model, Service, Controller layer code, support template engine, and more custom configurations are waiting for you to use
  • Built-in paging plug-in : Based on MyBatis physical paging, developers don't 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 : supports 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, which can quickly find out slow queries
  • Built-in global interception plug-in : Provides intelligent analysis and blocking of delete and update operations on the entire table, and can also customize interception rules to prevent misoperations

Getting Started Case

create database

CREATE DATABASE `mybatis_plus` /*!40100 DEFAULT CHARACTER 
SET utf8mb4 */;
USE `mybatis_plus`;
CREATE TABLE `user` (
	`id` INT ( 20 ) NOT NULL COMMENT '主键ID',
	`name` VARCHAR ( 30 ) DEFAULT NULL COMMENT '姓名',
	`age` INT ( 11 ) DEFAULT NULL COMMENT '年龄',
	`email` VARCHAR ( 50 ) DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY ( `id` ) 
) ENGINE = INNODB DEFAULT CHARSET = utf8;

adding data

INSERT INTO USER ( id, NAME, age, email )
VALUES
	( 1, 'Jone', 18, '[email protected]' ),
	( 2, 'Jack', 20, '[email protected]' ),
	( 3, 'Tom', 28, '[email protected]' ),
	( 4, 'Sandy', 21, '[email protected]' ),
	( 5, 'Billie', 24, '[email protected]' );

Create a new project

8558467.jpg

Introduce dependencies

<!--简化pojo类开发-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.1</version>
</dependency>
<!--mysql驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

Install the lombok plugin

8558467.jpg

The first query case

First create a new entity class pojo.User

@Data
public class User {
    
    
    Integer id;
    String name;
    Integer age;
    String email;
}

Then add the corresponding mapper mapping file mapper.UserMapper

/**
 * BaseMapper 是 MybatisPlus 提供的一个基础接口类,有泛型
 * @author songzx
 * @create 2022-09-08 14:27
 */
public interface UserMapper extends BaseMapper<User> {
    
    

}

Add @MapperScan to the startup class to automatically scan the mapper interface

@SpringBootApplication
@MapperScan("com.szx.mybatisplusproduct.mapper")
public class MybatisPlusProductApplication {
    
    

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

}

add test file

@SpringBootTest
public class UserTest1 {
    
    
    @Autowired
    UserMapper userMapper;

    @Test
    public void test1(){
    
    
        List<User> userList = userMapper.selectList(null);
        userList.forEach(item-> System.out.println("item = " + item));
    }
}

Run the test method to view the return

8558467.jpg

Add log printing

application.ymlAdd the following configuration to the configuration file

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

After the addition is complete, the sql statement information of the query will be entered in the console

8558467.jpg

BaseMapper

BaseMapper provides many basic methods, we can use the provided methods to implement basic addition, deletion, modification and query of a table

new method

@Test
public void testInsert(){
    
    
    User user = new User();
    user.setName("张三");
    user.setAge(18);
    user.setEmail("[email protected]");
    // 返回的是受影响的行数
    int insert = userMapper.insert(user); //=> 1
    // 自动生成的id是根据雪花算法算出的
    System.out.println(user.getId());
}

View the results inserted into the table

8558467.jpg

delete method

delete by id

// 根据id删除
@Test
public void testDeleteById(){
    
    
    // 返回的是被删除的数量
    int resDelCount = userMapper.deleteById(1);
    System.out.println("resDelCount = " + resDelCount);
}

delete by condition

// 根据条件删除
@Test
public void testDeleteByMap(){
    
    
    // 将条件放在一个map中传给userMapper
    HashMap<String, Object> map = new HashMap<>();
    map.put("name","张三");
    map.put("age",18);
    int i = userMapper.deleteByMap(map);
    System.out.println("i = " + i);
}

Delete data in batches according to the id collection

// 根据id集合批量删除数据
@Test
public void testBatchDelete(){
    
    
    List<Integer> ids = Arrays.asList(2, 3);
    int i = userMapper.deleteBatchIds(ids);
    System.out.println("i = " + i);
}

Modification method

@Test
public void testUpdate(){
    
    
    User user = new User();
    user.setId(2);
    user.setName("张三");
    // 根据id修改数据
    int i = userMapper.updateById(user);
    System.out.println("i = " + i);
}

query method

Query a single piece of data by id

User user = userMapper.selectById(2);
// SELECT id,name,age,email FROM user WHERE id=?
System.out.println("user = " + user);

Batch query multiple data according to id

List<Integer> ids = Arrays.asList(2, 3, 4);
// SELECT id,name,age,email FROM user WHERE id IN ( ? , ? , ? )
List<User> users = userMapper.selectBatchIds(ids);
System.out.println("users = " + users);

Query according to map

HashMap<String, Object> searchMap = new HashMap<>();
searchMap.put("name","Jack");
searchMap.put("age",20);
// SELECT id,name,age,email FROM user WHERE name = ? AND age = ?
List<User> users1 = userMapper.selectByMap(searchMap);
System.out.println("users1 = " + users1);

Query by condition

A conditional constructor is passed in here, and a null can be passed in if there is no

List<User> userList = userMapper.selectList(null);
// SELECT id,name,age,email FROM user
userList.forEach(System.out::println);

query for a single value

The conditional constructor is also passed in here, and null is passed in if there is no

For example: the total number of query data

Long total = userMapper.selectCount(null);
// SELECT COUNT( * ) FROM user
System.out.println("total = " + total);

custom SQL method

First add a custom mapper mapping file. In MyBatisPlus, the location of the mapper mapping file has been defined for us, that is, add a mapperfolder in the configuration file directory, and then automatically read the mapping ending in xml with any name in any directory. document

So first create a new mapping file in the mapper directory, the name of the mapping file is the same as the interface name

8558467.jpg

Then add a custom query method to the interface

@Repository
public interface UserMapper extends BaseMapper<User> {
    
    
    // 自定义的查询方法,根据id查询返回一个map
    Map<String,Object> mySelectMapById(Integer id);
}

Add mapping sql

<?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.szx.mybatisplusproduct.mapper.UserMapper">

    <!--Map<String,Object> mySelectMapById(Integer id);-->
    <select id="mySelectMapById" resultType="map">
        select * from user where id = #{id}
    </select>
</mapper>

Test custom methods

@Test
public void testSelectMap(){
    
    
    Map<String, Object> userMap = userMapper.mySelectMapById(2);
    System.out.println("userMap = " + userMap);
}

Test to view the running results

8558467.jpg

IService 和 ServiceImpl

  • IService is an interface provided by mybatis-plus that encapsulates common CRUD, and ServiceImpl is its implementation class
  • To further encapsulate CRUD, use the following method as a prefix to distinguish each method and avoid confusion with the method in mapper
    • get single row query
    • remove delete
    • list query collection
    • page pagination

Create Service interface and implementation class

Create a new UserService interface and inherit from IService

/**
 * 通用server,自定义一个接口,使之继承 IService<T>
 * @author songzx
 * @create 2022-09-14 15:04
 */
public interface UserService extends IService<User> {
    
    
    
}

Then add the implementation class and inherit the ServiceImpl class

/**
 * 创建service实现类,继承mybatis-plus提供的实现类,然后实现UserService
 * @author songzx
 * @create 2022-09-14 15:06
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{
    
    

}

Query the total number of data

@Autowired
UserServiceImpl userService;

/**
 * userService.count();
 * 查询数据总数
 */
@Test
public void testGetCount(){
    
    
    long count = userService.count();
    System.out.println("count = " + count);
}

batch insert

Due to the length limit of sql statement, massive data insertion cannot be implemented in a single sql, so it is implemented in the Service interface

@Autowired
UserServiceImpl userService;

/**
 * 测试批量插入
 */
@Test
public void testBatchSave(){
    
    
    ArrayList<User> users = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
    
    
        User user = new User();
        user.setName("abc" + i);
        user.setAge(20 + i);
        users.add(user);
    }
    // 返回是否批量插入成功
    boolean b = userService.saveBatch(users);
    System.out.println("b = " + b);
}

batch deletion

@Autowired
UserServiceImpl userService;
/**
 * 测试批量删除
 */
@Test
public void testBatchRemove(){
    
    
    List<Integer> ids = Arrays.asList(6, 7, 8, 9);
    boolean b = userService.removeBatchByIds(ids);
    System.out.println("b = " + b);
}

Common Notes

@TableName

Role: specify the table name corresponding to the entity class

In the above example, we did not specify the table name of the database, but only specified a generic User when inheriting BaseMapper, so that mybatis-plus knows that we are operating the user table. If the table name and instance class name in our database Inconsistent, what will happen?

We modify the table name of the database to be t_user

8558467.jpg

Then execute the query method

@Autowired
UserServiceImpl userService;

/**
 * userService.count();
 * 查询数据总数
 */
@Test
public void testGetCount(){
    
    
    long count = userService.count();
    System.out.println("count = " + count);
}

The following error will occur

8558467.jpg

At this time, we can use it on the entity class @TableNameto indicate the name of the table to be operated

@TableName("t_user")
@Data
public class User {
    
    
    Integer id;
    String name;
    Integer age;
    String email;
}

At this time, you can query normally again

8558467.jpg

Global configuration table name prefix

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      # 配置表名的默认前缀
      table-prefix: t_

@TableId

Role: specify the field as the primary key id

In the above insert data test, mybatis-plus will take the id as the primary key by default, and automatically generate the id according to the snowflake algorithm. If the primary key in the database is not called id, is there a problem?

Now modify the id in the table to t_id

8558467.jpg

Modify the id in the entity class to t_id

@TableName("t_user")
@Data
public class User {
    
    
    Integer t_id;
    String name;
    Integer age;
    String email;
}

At this point to perform a new operation

@Test
public void testSave(){
    
    
    User user = new User();
    user.setName("Jack");
    user.setAge(18);
    user.setEmail("[email protected]");
    boolean save = userService.save(user);
    System.out.println("save = " + save);
}

The following error occurs

8558467.jpg

At this time we use @TableIdto specify an attribute as the primary key

@TableName("t_user")
@Data
public class User {
    
    
    Integer id;
    @TableId
    Integer t_id;
    String name;
    Integer age;
    String email;
}

Executing the new one again will create a new one successfully

8558467.jpg

The value attribute of @TableId

If the primary key in the table is named t_id, and the corresponding entity class is id, the following exception will be thrown, indicating that mybatis-plus still uses id as the primary key

8558467.jpg

Then use @TableId("t_id")to specify the name of the primary key

@TableName("t_user")
@Data
public class User {
    
    
    @TableId("t_id")
    Integer id;
    String name;
    Integer age;
    String email;
}

Test again to add successfully

8558467.jpg

The type attribute of @TableId

The type attribute is mainly used to define the primary key strategy

common strategy

value describe
IdType.ASSIGN_ID (default) The data id is generated based on the snowflake algorithm strategy, regardless of whether the database id is set to auto-increment
IdType.AUTO Use the auto-increment strategy of the database. Note that for this type, please ensure that the database is set with id auto-increment.

The test uses the database's primary key increment strategy

@TableName("t_user")
@Data
public class User {
    
    
    @TableId(value = "t_id",type = IdType.AUTO)
    Integer id;
    String name;
    Integer age;
    String email;
}

Now the maximum id of the database is 6

8558467.jpg

Execute the new method once

8558467.jpg

Check the database records again, the latest record id is 7

8558467.jpg

@TableField

The attribute in the defined entity class corresponds to the column in the table. For example, there is a column in my table as user_name, but the corresponding entity class is name, so will there be any problems in creating a new one in this case?

Now let's change the column name in the database table

8558467.jpg

The entity class is still like this

@TableName("t_user")
@Data
public class User {
    
    
    @TableId(value = "t_id",type = IdType.AUTO)
    Integer id;
    String name;
    Integer age;
    String email;
}

Run the new method, the following error occurs, the name column cannot be found

8558467.jpg

At this time, @TableFieldannotations can be used to declare which column this property corresponds to

@TableName("t_user")
@Data
public class User {
    
    
    @TableId(value = "t_id",type = IdType.AUTO)
    Integer id;
    @TableField("user_name")
    String name;
    Integer age;
    String email;
}

At this time, you can create a new one.

8558467.jpg

CamelCase naming convention

If the database uses an underscore to name the field, for example user_name, in the entity class userName, the new creation will not go wrong, because mybatis will match the field name according to the camel case

@TableLogic

Indicates tombstone data

  • Physical deletion: This piece of data is actually deleted from the data table and cannot be recovered
  • Logical deletion: It is not actually deleting the data from the table, it is just indicating that the data is deleted according to a state, and the query will not query the data whose state is deleted

Realize adding a new field in the table to indicate whether to delete, pay attention to set a default value of 0, 0 means not deleted, 1 means deleted

8558467.jpg

Then add the corresponding attributes to the entity class and add @TableLogicannotations

@TableName("t_user")
@Data
public class User {
    
    
    @TableId(value = "t_id",type = IdType.AUTO)
    Integer id;
    @TableField("user_name")
    String name;
    Integer age;
    String email;
    // 逻辑删除
    @TableLogic
    Integer isDeleted;
}

and then perform the delete operation

@Test
public void testRemoveById(){
    
    
    boolean b = userService.removeById(8);
    System.out.println("b = " + b);
}

Results of the

8558467.jpg

From the execution results, we can see that the actual operation of the SQL is to modify the operation. At this time, check whether the data with the id of 8 in the table is still there.

8558467.jpg

From the results, it can be seen that the data with id equal to 8 has not been deleted, but the status has been changed to 1

Then come to the query method again, whether the deleted data will be found

@Test
public void testGetUserList(){
    
    
    List<User> userList = userService.list();
    userList.forEach(System.out::println);
}

operation result

8558467.jpg

Will automatically add a query condition of is_deleted = 0

Conditional constructors and common interfaces

introduction

8558467.jpg

  • Wrapper : conditionally constructed abstract class, the topmost parent class

    • AbstractWrapper: Used for encapsulating query conditions and generating where conditions for sql

      • QueryWrapper: query condition encapsulation

      • UpdateWrapper : Update conditional wrapper

      • AbstractLambdaWrapper: use Lambda syntax

        • LambdaQueryWrapper: Query Wrapper for Lambda syntax usage

        • LambdaUpdateWrapper : Lambda update package Wrapper

QueryWrapper

Assembling query conditions

For example:

Query data whose name contains a, whose age is between 20 and 30, and whose mailbox is not empty

@Test
public void test1(){
    
    
    QueryWrapper<User> qw = new QueryWrapper<>();
    // 第一个参数填数据库中对应的列名
    qw.like("user_name","a")
            .between("age",20,30)
            .isNotNull("email");
    List<User> userList = userService.list(qw);
    userList.forEach(System.out::println);
}

SQL statements in query results

SELECT t_id AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (user_name LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)

Two pieces of data were successfully queried

8558467.jpg

Assembly Sort Conditions

Sort by user age in descending order, if the age is the same, sort by id in ascending order

@Test
public void test2(){
    
    
    QueryWrapper<User> qw = new QueryWrapper<>();
    // orderByDesc 降序,orderByAsc 升序
    qw.orderByDesc("age").orderByAsc("t_id");
    List<User> userList = userService.list(qw);
    userList.forEach(System.out::println);
}

SQL statements in query results

SELECT t_id AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 ORDER BY age DESC,t_id ASC

return result

8558467.jpg

Assembly delete condition

Delete data with empty mailboxes

@Test
public void test3(){
    
    
    QueryWrapper<User> qw = new QueryWrapper<>();
    qw.isNull("email");
    boolean remove = userService.remove(qw);
    System.out.println("remove = " + remove);
}

SQL statements in query results

UPDATE t_user SET is_deleted=1 WHERE is_deleted=0 AND (email IS NULL)

return result

8558467.jpg

Priority of conditions

Modify the data whose age is greater than 20 and whose name contains a or whose email address is empty

@Test
public void test4(){
    
    
    // 将年龄大于20并且姓名中包含有a的或者邮箱为空的数据修改
    QueryWrapper<User> qw = new QueryWrapper<>();
    qw.gt("age",20)
            .like("user_name","a")
            .or()
            .isNull("email");
    User user = new User();
    user.setAge(18);
    boolean update = userService.update(user, qw);
    System.out.println("update = " + update);
}

SQL to run

UPDATE t_user SET age=? WHERE is_deleted=0 AND (age > ? AND user_name LIKE ? OR email IS NULL)

Using lambda expressions

@Test
public void test5(){
    
    
    QueryWrapper<User> qw = new QueryWrapper<>();
    qw.like("user_name","a")
            .and(i->i.gt("age",20).or().isNull("email"));
    User user = new User();
    user.setAge(18);
    boolean update = userService.update(user, qw);
    System.out.println("update = " + update);
}

Running SQL, logical precedence operations within lambda expressions

UPDATE t_user SET age=? WHERE is_deleted=0 AND (user_name LIKE ? AND (age > ? OR email IS NULL))

Assembling the select clause

For example, I only query user_name and age

@Test
public void test6(){
    
    
    QueryWrapper<User> qw = new QueryWrapper<>();
    qw.select("user_name","age");
    // getMap 返回map集合,通常配合 select() 使用
    Map<String, Object> map = userService.getMap(qw);
    System.out.println("map = " + map);
}

SQL statement

SELECT user_name,age FROM t_user WHERE is_deleted=0

implement the subquery

Query data whose age is less than or equal to 20

@Test
public void test7(){
    
    
    String sql = "select age from t_user where age <= 20";
    QueryWrapper<User> qw = new QueryWrapper<>();
    qw.inSql("age",sql);
    List<User> list = userService.list(qw);
    list.forEach(System.out::println);
}

Generated SQL

SELECT t_id AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (age IN (select age from t_user where age <= 20))

search result

8558467.jpg

UpdateWrapper

@Test
public void test1(){
    
    
    UpdateWrapper<User> up = new UpdateWrapper<>();
    up.like("user_name","a")
            .and(i->i.gt("age",20).or().isNull("email"))
            .set("user_name","张三")
            .set("email","[email protected]");
    // 不需要再声明实体类
    boolean update = userService.update(null, up);
    System.out.println("update = " + update);
}

Generated SQL

UPDATE t_user SET user_name=?,email=? WHERE is_deleted=0 AND (user_name LIKE ? AND (age > ? OR email IS NULL))

operation result

8558467.jpg

Simulate conditional queries in development scenarios

Idea 1: Complex version

@Test
public void test2(){
    
    
    String user_name = "";
    Integer ageStart = 15;
    Integer ageEnd = 30;
    QueryWrapper<User> qw = new QueryWrapper<>();
    // 判断user_name不是null,不是空字符串,不是空白符
    if(StringUtils.isNotBlank(user_name)){
    
    
        qw.like("user_name",user_name);
    }
    if(ageStart != null){
    
    
        // ge() 大于等于
        qw.ge("age",ageStart);
    }
    if(ageEnd != null){
    
    
        // le() 小于等于
        qw.le("age",ageEnd);
    }
    List<User> list = userService.list(qw);
    list.forEach(System.out::println);
}

Generated sql statement

SELECT t_id AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (age >= ? AND age <= ?)

returned result

8558467.jpg

This method is too cumbersome, below we have a simpler way to query

Idea 2: condition

We can use overloaded methods with condition parameters to build query conditions to simplify code writing

@Test
public void test3(){
    
    
    // 查询姓名包含a,年龄大于等于15小于等于30的数据
    String user_name = "";
    Integer ageStart = 15;
    Integer ageEnd = 30;
    QueryWrapper<User> qw = new QueryWrapper<>();
    qw.like(StringUtils.isNotBlank(user_name),"user_name",user_name)
            .ge(ageStart != null,"age",ageStart)
            .le(ageEnd != null,"age",ageEnd);
    List<User> list = userService.list(qw);
    list.forEach(System.out::println);
}

LambdaQueryWrapper

Use lambda expression to represent the column name of the operation to avoid errors caused by strings when writing

@Test
public void test(){
    
    
    String user_name = "a";
    Integer ageStart = 15;
    Integer ageEnd = 30;
    LambdaQueryWrapper<User> qw = new LambdaQueryWrapper<>();
    qw.like(StringUtils.isNotBlank(user_name),User::getName,user_name)
            .ge(ageStart != null,User::getAge,ageStart)
            .le(ageEnd != null,User::getAge,ageEnd);
    List<User> list = userService.list(qw);
    list.forEach(System.out::println);
}

corresponding sql

SELECT t_id AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (user_name LIKE ? AND age >= ? AND age <= ?)

search result

8558467.jpg

LambdaUpdateWrapper

@Test
public void test(){
    
    
    // UPDATE t_user SET age=?,user_name=? WHERE is_deleted=0 AND (user_name LIKE ?)
    // 将姓名中包含 a 的年龄改成22,姓名改成 updateWrapper
    LambdaUpdateWrapper<User> up = new LambdaUpdateWrapper<>();
    up.set(User::getAge,22)
            .set(User::getName,"updateWrapper")
            .like(User::getName,"a");
    boolean update = userService.update(up);
    System.out.println("update = " + update);
}

Execute SQL

UPDATE t_user SET age=?,user_name=? WHERE is_deleted=0 AND (user_name LIKE ?)

plug-in

paging plugin

mybatis-plus has its own paging plugin, which can be configured simply

First create a new configuration class

@Configuration
public class MybatisPlusConfig {
    
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
    
    
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

Test pagination plus conditional query

@Test
public void test(){
    
    
    // 1.声明一个Page类,构造器有两个参数,第一个表示第几页,第二个表示每页显示几条数据
    Page<User> userPage = new Page<>(1, 5);
    // 2.创建查询wrapper
    LambdaQueryWrapper<User> qw = new LambdaQueryWrapper<>();
    qw.like(User::getName,"a");

    // 3.传入分页查询和查询wrapper
    Page<User> page = userService.page(userPage,qw);
    
    // getRecords() 获取表中记录
    List<User> userList = page.getRecords();
    userList.forEach(System.out::println);

    System.out.println("********分页信息********");
    System.out.println("表中总数:" + page.getTotal());
    System.out.println("是否有上一页:" + page.hasPrevious());
    System.out.println("是否有下一页:" + page.hasNext());
    System.out.println("当前第几页:" + page.getCurrent());
    System.out.println("一共有几页:" + page.getPages());
    System.out.println("每页显示的条数:" + page.getSize());
}

Generated SQL

SELECT t_id AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (user_name LIKE ?) LIMIT ?

search result

8558467.jpg

Custom pagination query method

Add new interface in UserMapper interface

@Repository
public interface UserMapper extends BaseMapper<User> {
    
    
	// 自定义分页查询方法
    Page<User> selectMyPageVo(Page<User> page,Integer age);
}

Add interface mapping in UserMapper.xml

<!--Page<User> selectMyPageVo(Page<User> page,Integer age);-->
<sql id="baseColunm">t_id,user_name,email,age</sql>

<select id="selectMyPageVo" resultType="com.szx.mybatisplusproduct.pojo.User">
    select <include refid="baseColunm"></include> from t_user where age >= #{age}
</select>

test

@Autowired
UserMapper userMapper;

@Test
public void test2(){
    
    
    Page<User> page = new Page<>(1, 5);
    Page<User> userPage = userMapper.selectMyPageVo(page, 20);
    List<User> records = userPage.getRecords();
    records.forEach(System.out::println);
}

operation result

8558467.jpg

The id and name in the figure are both null, which is caused by the inconsistency between the attributes in the user entity class and the fields in the table

optimistic lock

The existing scenario is as follows: the cost price of a commodity is 80 yuan, and the selling price is 100 yuan. The boss first notified Xiao Li that you should increase the price of the product by 50 yuan. Xiao Li was playing a game and was delayed for an hour. Exactly one hour later, the boss felt that the price of the product had increased to 150 yuan, which was too high and might affect sales. Also inform Xiao Wang that you will reduce the price of the product by 30 yuan. At this time, Xiao Li and Xiao Wang operate the commodity background system at the same time. When Xiao Li operated, the system first took out the product price of 100 yuan; Xiao Wang was also operating, and the price of the product taken out was also 100 yuan. Xiao Li added 50 yuan to the price, and stored 100+50=150 yuan in the database; Xiao Wang reduced the product by 30 yuan, and stored 100-30=70 yuan in the database. Yes, if there is no lock, Xiao Li's operation will be completely covered by Xiao Wang's. Now the commodity price is 70 yuan, which is 10 yuan lower than the cost price. A few minutes later, this product quickly sold more than 1,000 items, and the boss lost more than 10,000.

In the above story, if it is an optimistic lock, Xiao Wang will check whether the price has been modified before saving the price. If it has been modified, the revised price will be retrieved again, 150 yuan, so that he will store 120 yuan in the database. If it is a pessimistic lock, after Xiao Li takes out the data, Xiao Wang can only operate on the price after Xiao Li finishes the operation, and the final price will be guaranteed to be 120 yuan.

Simulate the conflict above

First create a new product table and insert a piece of data

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_product
-- ----------------------------
DROP TABLE IF EXISTS `t_product`;
CREATE TABLE `t_product`  (
  `id` int(0) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '商品名称',
  `price` int(0) NULL DEFAULT 0 COMMENT '价格',
  `version` int(0) NULL DEFAULT 0 COMMENT '乐观锁版本号',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_product
-- ----------------------------
INSERT INTO `t_product` VALUES (1, '外星人笔记本', 100, 0);

SET FOREIGN_KEY_CHECKS = 1;

Create a new corresponding entity class

package com.szx.mybatisplusproduct.pojo;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;

/**
 * 这里在配置文件中统一加了t_前缀,所以这里不用在声明 @TableName
 * @author songzx
 * @create 2022-09-16 13:34
 */
@Data
public class Product {
    
    
    @TableId(type = IdType.AUTO)
    Integer id;
    String name;
    Integer price;
    Integer version;
}

Add Mapper

public interface ProductMapper extends BaseMapper<Product> {
    
    
}

Simulate the above scenario

@SpringBootTest
public class ProductTest {
    
    
    @Autowired
    ProductMapper productMapper;

    @Test
    public void test(){
    
    
        // 小李从数据库中获取的商品
        Product p1 = productMapper.selectById(1);

        // 小王也在同一时间获取商品
        Product p2 = productMapper.selectById(1);

        // 小李将价格调高50元
        p1.setPrice(p1.getPrice() + 50);
        productMapper.updateById(p1);

        // 小王将价格调低30元
        p2.setPrice(p2.getPrice() - 30);
        productMapper.updateById(p2);

        // 最后的结果
        Product p3 = productMapper.selectById(1);
        System.out.println(p3);
    }
}

The final result obtained is that the product price becomes 70 yuan

8558467.jpg

Implementation process of optimistic locking

Add a version field to the database

When fetching records, get the current version

SELECT id,`name`,price,`version` FROM product WHERE id=1

When updating, version + 1, if the version in the where statement is incorrect, the update will fail

UPDATE product SET price=price+50, `version`=`version` + 1 WHERE id=1 AND `version`=1

MybatisPlus implements optimistic locking

Modify the entity class and add it to the version number attribute@Version

@Data
public class Product {
    
    
    @TableId(type = IdType.AUTO)
    Integer id;
    String name;
    Integer price;
    // 在版本号属性上添加 @Version 注解
    @Version
    Integer version;
}

Add optimistic lock plugin in configuration class

@Configuration
public class MybatisPlusConfig {
    
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
    
    
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        // 乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}

Test the price modification process again

First, Xiao Li checks the price

SELECT id,name,price,version FROM t_product WHERE id=?

The price found is 100

8558467.jpg

Then Xiao Wang inquired about the price

SELECT id,name,price,version FROM t_product WHERE id=?

The price found is also 100

8558467.jpg

Then Xiao Li modifies the price

UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?

And successfully modified, and the version number was changed from 1 to 2

8558467.jpg

Then Xiao Wang will change the version number to a price of 1

UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?

Since Xiao Li has already changed the version number to 2 above, Xiao Wang will modify the data with the version number 1 at this time, but naturally the modification is not successful

8558467.jpg

Finally, the boss will check the price

SELECT id,name,price,version FROM t_product WHERE id=?

The price is 150

8558467.jpg

Optimize the price modification process

Add a mechanism to request again after modification failure

@SpringBootTest
public class ProductTest {
    
    
    @Autowired
    ProductMapper productMapper;

    @Test
    public void test(){
    
    
        // 小李从数据库中获取的商品
        Product p1 = productMapper.selectById(1);

        // 小王也在同一时间获取商品
        Product p2 = productMapper.selectById(1);

        // 小李将价格调高50元
        p1.setPrice(p1.getPrice() + 50);
        productMapper.updateById(p1);

        // 小王将价格调低30元
        p2.setPrice(p2.getPrice() - 30);
        int i = productMapper.updateById(p2);
        if(i == 0){
    
    
            // 如果没有修改成功则再次查询再次修改
            p2 = productMapper.selectById(1);
            p2.setPrice(p2.getPrice() - 30);
            productMapper.updateById(p2);
        }

        // 最后的结果
        Product p3 = productMapper.selectById(1);
        System.out.println(p3);
    }
}

The result returned by the query is 120 yuan

8558467.jpg

Note: Restore the price in the database to 100 before each test

Generic enumeration

Some value fields in the database are fixed, such as gender, we hope that when modifying such values, we can get the value from the enumeration class and store it in the database

First add the sex column in the t_user table

8558467.jpg

Add the mapping sex attribute to the corresponding entity class

@TableName("t_user")
@Data
public class User {
    
    
    @TableId(value = "t_id",type = IdType.AUTO)
    Integer id;
    @TableField("user_name")
    String name;
    Integer age;
    String email;
    // 逻辑删除
    @TableLogic
    Integer isDeleted;
    Integer sex;
}

Add enum class

@Getter
public enum SexEnum {
    
    
    MALE(1,"男"),
    FEMALE(2,"女");

    @EnumValue // 标注了 @EnumValue 的注解会作为值保存在数据库中
    Integer sex;
    String sexName;

    SexEnum(Integer sex,String sexName){
    
    
        this.sex = sex;
        this.sexName = sexName;
    }
}

Configure Scanning Generic Enumeration

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      # 配置表名的默认前缀
      table-prefix: t_
  # 配置扫描通用枚举
  type-enums-package: com.szx.mybatisplusproduct.enums

Test modification method

@Autowired
UserServiceImpl userService;

@Test
public void test(){
    
    
    // 将姓名中包含a的数据的性别改成女
    LambdaUpdateWrapper<User> up = new LambdaUpdateWrapper<>();
    up.set(User::getSex, SexEnum.FEMALE)
            .like(User::getName,"a");
    boolean update = userService.update(up);
    System.out.println(update);
}

generated sql

UPDATE t_user SET sex=? WHERE is_deleted=0 AND (user_name LIKE ?)

operation result

8558467.jpg

multiple data sources

Applies to the case of multiple tables. We create two libraries, namely: mybatis_plus (the previous library does not move) and mybatis_plus_1 (new), and move the product table of the mybatis_plus library to the mybatis_plus_1 library, so that each library has a table and obtains user data through a test case With commodity data, if it is obtained, it means that the multi-library simulation is successful

First create a new data

8558467.jpg

Introduce dependencies

<dependency> 
    <groupId>com.baomidou</groupId> 
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId> 
    <version>3.5.0</version> 
</dependency>

To reconfigure the data source, comment out the original configuration

server:
  port: 8899
spring:
  application:
    # 当前项目的名称
    name: MyBatisPlus学习项目
  datasource:
    dynamic:
      # 设置默认的数据源或者数据源组,默认值即为master
      primary: master
      # 严格匹配数据源,默认为false,true为匹配到指定数据源是抛异常,false使用默认的数据源
      strict: false
      datasource:
        master:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&characterEncoding=utf-8
          username: root
          password: abc123
        slave_1:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/mybatis_plus_1?serverTimezone=GMT%2B8&characterEncoding=utf-8
          username: root
          password: abc123

Create UserService

@DS("master") // 指定要操作的数据源
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{
    
    

}

Create ProductService

@DS("slave_1")
@Service
public class ProductServiceImp extends ServiceImpl<ProductMapper, Product> implements ProductService {
    
    
    
}

test query

@SpringBootTest
public class TestStarter {
    
    
    @Autowired
    UserServiceImpl userService;
    @Autowired
    ProductServiceImp productService;

    @Test
    public void test(){
    
    
        // 查询用户信息
        List<User> userList = userService.list();
        userList.forEach(System.out::println);

        System.out.println("********");

        // 查询商品信息
        List<Product> productList = productService.list();
        productList.forEach(System.out::println);
    }
}

returned result

8558467.jpg

It can be seen that the data in both databases have been successfully queried

Code generator

First install the plugin

<dependency> 
    <groupId>com.baomidou</groupId> 
    <artifactId>mybatis-plus-generator</artifactId> 
    <version>3.5.1</version> 
</dependency> 
<dependency> 
    <groupId>org.freemarker</groupId>     
    <artifactId>freemarker</artifactId> 
    <version>2.3.31</version> 
</dependency>

Then run the following code directly to automatically generate code based on the table

package com.szx.mybatisplusproduct;

import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.util.Collections;

public class FastAutoGeneratorTest {
    
    
    public static void main(String[] args) {
    
    
        FastAutoGenerator.create("jdbc:mysql://127.0.0.1:3306/mybatis_plus? characterEncoding=utf-8&userSSL=false", "root", "abc123")
                .globalConfig(builder -> {
    
    
                    builder.author("szx") // 设置作者
//                            .enableSwagger() // 开启 swagger 模式
                            .fileOverride() // 覆盖已生成文件
                            .outputDir("D://0000学习文件//MyBatisPlus"); // 指定输出目录
                })
                .packageConfig(builder -> {
    
    
                    builder.parent("com.baomidou.mybatisplus.samples.generator") // 设置父包名
                            .moduleName("system") // 设置父包模块名
                            .pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://0000学习文件//MyBatisPlus")); // 设置mapperXml生成路径
                })
                .strategyConfig(builder -> {
    
    
                    builder.addInclude("t_user") // 设置需要生成的表名
                            .addTablePrefix("t_", "c_"); // 设置过滤表前缀
                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();
    }
}

operation result

8558467.jpg

MyBatisX plugin

MyBatis-Plus provides us with powerful mapper and service templates, which can greatly improve development efficiency. However, in the actual development process, MyBatis-Plus cannot solve all problems for us, such as some complex SQL, multi-table joint query, we We need to write code and SQL statements by ourselves. How can we quickly solve this problem? At this time, we can use MyBatisX plug-in MyBatisX, a rapid development plug-in based on IDEA, which is born for efficiency.

Official usage: https://baomidou.com/pages/ba5b24/

plugin installation

Search for mybatisx and install

8558467.jpg

Configure data source

8558467.jpg

8558467.jpg

Quickly generate entity classes, Mapper

8558467.jpg

8558467.jpg

8558467.jpg

automatically generated files

8558467.jpg

Generate queries quickly

8558467.jpg

Guess you like

Origin blog.csdn.net/SongZhengxing_/article/details/127123854