mybatis-plus getting started instructions

1. Introduction to MyBatis-Plus

1 Introduction

MyBatis-Plus (MP for short) is an enhancement tool for MyBatis . Based on MyBatis, it only enhances without making any changes . It is created to simplify development and improve efficiency .

Vision
Our vision is to become the best partner of MyBatis, just like 1P and 2P in Contra, with friends, double the efficiency.

Insert image description here

2. Characteristics

  • Non-intrusive : only enhancements and no changes are made. Introducing it will not affect existing projects and is as smooth as silk
  • Low loss : basic CURD will be automatically injected upon startup, with basically no loss in performance and direct object-oriented operation.
  • Powerful CRUD operations : built-in general Mapper and general Service, most CRUD operations on a single table can be realized with only a small amount of configuration, and there are also powerful conditional constructors to meet various usage needs
  • Supports Lambda form calling : through Lambda expressions, you can easily write various query conditions without worrying about mistyping fields.
  • Supports automatic generation of primary keys : supports up to 4 primary key strategies (including distributed unique ID generator - Sequence), which can be freely configured to perfectly solve the primary key problem
  • Support ActiveRecord mode : Support ActiveRecord form calling, entity classes only need to inherit the Model class to perform powerful CRUD operations
  • Support custom global universal operations : support global universal method injection (Write once, use anywhere)
  • Built-in code generator : Use code or Maven plug-ins to quickly generate Mapper, Model, Service, and Controller layer codes. It supports template engines and has many custom configurations waiting 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 a variety of databases : supports MySQL, MariaDB, Oracle, DB2, H2, HSQL, SQLite, Postgre, SQLServer and other databases
  • Built-in performance analysis plug-in : can output SQL statements and their execution time. It is recommended to enable this function during development and testing to quickly identify slow queries.
  • Built-in global interception plug-in : Provides intelligent analysis and blocking of delete and update operations in the entire table, and can also customize interception rules to prevent misoperations

3. Support database

Any database that can use MyBatis for CRUD and supports standard SQL. Specific support is as follows

  • MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss ,ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb
  • Dameng database, Xugu database, Renmin University Jincang database, Nanda General (Huaku) database, Nanda General database, Shentong database, Hangao database

4. Frame structure

Insert image description here

5. Code and document address

Official address: http://mp.baomidou.com
Code release address:
Github: https://github.com/baomidou/mybatis-plus
Gitee: https://gitee.com/baomidou/mybatis-plus
Document release address:https://baomidou.com/pages/24112f

2. Getting Started Cases

1. Development environment

  • IDE: idea 2019.2
  • JDK:JDK8+
  • Build tool: maven 3.5.4
  • MySQL version: MySQL 5.7
  • Spring Boot:2.6.3
  • MyBatis-Plus:3.5.1

2. Create database and tables

a>Create table

CREATE DATABASE `mybatis_plus` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
use `mybatis_plus`;
CREATE TABLE `user` (
`id` bigint(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;

b>Add 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]');

3. Create a Spring Boot project

a>Initialization project

Use Spring Initializr to quickly initialize a Spring Boot project

Insert image description here

Insert image description here

Insert image description here

Insert image description here

b>Introduce dependencies

<?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.6.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.grey</groupId>
    <artifactId>mybatis_plus_demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>mybatis_plus_demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

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

</project>

c>Install lombok plug-in in idea

Insert image description here

4. Write code

a>Deploy application.yml

spring:
  #配置数据源信息
  datasource:
    #配置数据源类型
    type: com.zaxxer.hikari.HikariDataSource
    #配置连接数据库信息
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
    username: root
    password: root

注:【win:root】【mac:password】

  • 1. Driver class driver-class-name
    • Spring boot 2.0 (built-in jdbc5 driver), driver class use:
      driver-class-name: com.mysql.jdbc.Driver
    • Spring boot 2.1 and above (built-in jdbc8 driver), the driver class is:
      driver-class-name: com.mysql.cj.jdbc.Driver
    • Otherwise, there will be a WARN message when running the test case.
  • 2. Connection address url
    • MySQL5.7 version url:
      jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
    • MySQL8.0 database url:
      jdbc:mysql://localhost:3306/mybatis_plus?
      serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
    • Otherwise, running the test case reports the following error:
      java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more

b>Startup class

Add the @MapperScan annotation to the Spring Boot startup class to scan the mapper package

package com.grey.mybatisplus;

import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@Slf4j
@SpringBootApplication
@MapperScan("com.grey.mybatisplus.mapper")
public class MybatisPlusDemoApplication {
    
    

    public static void main(String[] args) {
    
    
        long startTime = System.currentTimeMillis();
        SpringApplication.run(MybatisPlusDemoApplication.class, args);
        long endTime = System.currentTimeMillis();
        log.info("=============== 启动成功,启动时间:{} ==============", (endTime - startTime));
    }

}

c>Add entity

package com.grey.mybatisplus.entity;

import lombok.Data;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    
    
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

The result after compiling the User class:

Insert image description here

d>Add mapper

BaseMapper is a template mapper provided by MyBatis-Plus, which contains basic CRUD methods, and generics are the entity types of operations.

package com.grey.mybatisplus.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.grey.mybatisplus.entity.User;
import org.springframework.stereotype.Repository;

@Repository //为了避免报错,可以在mapper接口上添加 @Repository 注解
public interface UserMapper extends BaseMapper<User> {
    
    
}

e>Test

Insert image description here

package com.grey.mybatisplus.controller;

import com.grey.mybatisplus.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class MybatisPlusTest {
    
    

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelectList() {
    
    
        //selectList()根据MP内置的条件构造器查询一个list集合,null表示没有条件,即查询所有
        userMapper.selectList(null).forEach(System.out::println);
    }
}

IDEA reports an error at userMapper because the injected object cannot be found because the class is dynamically created, but the program can be executed correctly.
In order to avoid errors, you can add the @Repository annotation on the mapper interface.

f>Add log

Configure log output in application.yml

#配置Mybatis日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

Insert image description here

3. Basic CRUD

1、BaseMapper

The basic CRUD in MyBatis-Plus has been implemented in the built-in BaseMapper. We can use it directly. The interface is as follows

package com.baomidou.mybatisplus.core.mapper;

public interface BaseMapper<T> extends Mapper<T> {
    
    

/ 插入 

	/**
	* 插入一条记录
	* @param entity 实体对象
	*/
	int insert(T entity);


/ 删除 

	/**
	* 根据 ID 删除
	* @param id 主键ID
	*/
	int deleteById(Serializable id);

	/**
	* 根据实体(ID)删除
	* @param entity 实体对象
	* @since 3.4.4
	*/
	int deleteById(T entity);

	/**
	* 根据 columnMap 条件,删除记录
	* @param columnMap 表字段 map 对象
	*/
	int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

	/**
	* 根据 entity 条件,删除记录
	* @param queryWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where语句)
	*/
	int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

	/**
	* 删除(根据ID 批量删除)
	* @param idList 主键ID列表(不能为 null 以及 empty)
	*/
	int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);



/ 修改 

	/**
	* 根据 ID 修改
	* @param entity 实体对象
	*/
	int updateById(@Param(Constants.ENTITY) T entity);

	/**
	* 根据 whereEntity 条件,更新记录
	* @param entity 实体对象 (set 条件值,可以为 null)
	* @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成where 语句)
	*/
	int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);




/ 查询 


	/**
	* 根据 ID 查询
	* @param id 主键ID
	*/
	T selectById(Serializable id);

	/**
	* 查询(根据ID 批量查询)
	* @param idList 主键ID列表(不能为 null 以及 empty)
	*/
	List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

	/**
	* 查询(根据 columnMap 条件)
	* @param columnMap 表字段 map 对象
	*/
	List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

	/**
	* 根据 entity 条件,查询一条记录
	* <p>查询一条记录,例如 qw.last("limit 1") 限制取一条记录, 注意:多条数据会报常</p>
	* @param queryWrapper 实体对象封装操作类(可以为 null)
	*/
	default T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper) {
    
    
		List<T> ts = this.selectList(queryWrapper);
			if (CollectionUtils.isNotEmpty(ts)) {
    
    
				if (ts.size() != 1) {
    
    
					throw ExceptionUtils.mpe("One record is expected, but the query result is multiple records");
				}
				return ts.get(0);
			}
		return null;
	}

	/**
	* 根据 Wrapper 条件,查询总记录数
	* @param queryWrapper 实体对象封装操作类(可以为 null)
	*/
	Long selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

	/**
	* 根据 entity 条件,查询全部记录
	* @param queryWrapper 实体对象封装操作类(可以为 null)
	*/
	List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

	/**
	* 根据 Wrapper 条件,查询全部记录
	* @param queryWrapper 实体对象封装操作类(可以为 null)
	*/
	List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

	/**
	* 根据 Wrapper 条件,查询全部记录
	* <p>注意: 只返回第一个字段的值</p>
	* @param queryWrapper 实体对象封装操作类(可以为 null)
	*/
	List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

	/**
	* 根据 entity 条件,查询全部记录(并翻页)
	* @param page 分页查询条件(可以为 RowBounds.DEFAULT)
	* @param queryWrapper 实体对象封装操作类(可以为 null)
	*/
	<P extends IPage<T>> P selectPage(P page, @Param(Constants.WRAPPER)Wrapper<T> queryWrapper);

	/**
	* 根据 Wrapper 条件,查询全部记录(并翻页)
	* @param page 分页查询条件
	* @param queryWrapper 实体对象封装操作类
	*/
	<P extends IPage<Map<String, Object>>> P selectMapsPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

}

2. Insert

/**
* 插入一条记录
* @param entity 实体对象
*/
int insert(T entity);
@Test
public void testInsert() {
    
    
    User user = new User(null, "张三", 23, "[email protected]");
    //INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
    int result = userMapper.insert(user);
    System.out.println("受影响行数:" + result);
    //1475754982694199298
    System.out.println("id自动获取:" + user.getId());
}

The final execution result is that the obtained id is 1475754982694199298.
This is because MyBatis-Plus will generate the id based on the snowflake algorithm strategy by default when inserting data.

3. Delete

a>Delete records by id

/**
* 根据 ID 删除
* @param id 主键ID
*/
int deleteById(Serializable id);
@Test
public void testDeleteById() {
    
    
    //通过id删除用户信息
    //DELETE FROM user WHERE id=?
    int result = userMapper.deleteById(1681606413372043266L);
    System.out.println("受影响行数:" + result);
}

b>Delete records in batches by ID

/**
* 删除(根据ID 批量删除)
* @param idList 主键ID列表(不能为 null 以及 empty)
*/
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
@Test
public void testDeleteBatchIds() {
    
    
    //通过多个id批量删除
    //DELETE FROM user WHERE id IN (?, ?, ?)
    List<Long> idList = Arrays.asList(1L, 2L, 3L);
    int result = userMapper.deleteBatchIds(idList);
    System.out.println("受影响行数:" + result);
}

c>Delete records through map conditions

/**
* 根据 columnMap 条件,删除记录
* @param columnMap 表字段 map 对象
*/
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
@Test
public void testDeleteByMap() {
    
    
    //根据map集合中所设置的条件删除记录
    //DELETE FROM user WHERE name = ? AND age = ?
    Map<String, Object> map = new HashMap<>();
    map.put("age", 23);
    map.put("name", "张三");
    int result = userMapper.deleteByMap(map);
    System.out.println("受影响行数:" + result);
}

4. Modify

/**
* 根据 ID 修改
* @param entity 实体对象
*/
int updateById(@Param(Constants.ENTITY) T entity);
@Test
public void testUpdateById() {
    
    
    User user = new User(4L, "admin", 22, null);
    //UPDATE user SET name=?, age=? WHERE id=?
    int result = userMapper.updateById(user);
    System.out.println("受影响行数:" + result);
}

5. Query

a>Query user information based on id

/**
* 根据 ID 查询
* @param id 主键ID
*/
T selectById(Serializable id);
@Test
public void testSelectById() {
    
    
    //根据id查询用户信息
    //SELECT id,name,age,email FROM user WHERE id=?
    User user = userMapper.selectById(4L);
    System.out.println(user);
}

b>Query multiple user information based on multiple IDs

/**
* 查询(根据ID 批量查询)
* @param idList 主键ID列表(不能为 null 以及 empty)
*/
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
@Test
public void testSelectBatchIds() {
    
    
    //根据多个id查询多个用户信息
    //SELECT id,name,age,email FROM user WHERE id IN (?,?)
    List<Long> idList = Arrays.asList(4L, 5L);
    List<User> list = userMapper.selectBatchIds(idList);
    list.forEach(System.out::println);
}

c> Query user information through map conditions

/**
* 查询(根据 columnMap 条件)
* @param columnMap 表字段 map 对象
*/
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
@Test
public void testSelectByMap() {
    
    
    //通过map条件查询用户信息
    //SELECT id,name,age,email FROM user WHERE name = ? AND age = ?
    Map<String, Object> map = new HashMap<>();
    map.put("age", 22);
    map.put("name", "admin");
    List<User> list = userMapper.selectByMap(map);
    list.forEach(System.out::println);
}

d>Query all data

/**
* 根据 entity 条件,查询全部记录
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
@Test
public void testSelectList() {
    
    
    //查询所有用户信息
    //SELECT id,name,age,email FROM user
    List<User> list = userMapper.selectList(null);
    list.forEach(System.out::println);
}

By observing the methods in BaseMapper, most methods have formal parameters of Wrapper type. This is a conditional constructor that can set different conditions for SQL statements. If there are no conditions, you can assign null to the formal parameter, that is, query/ Delete/modify all data

6. General Service

illustrate:

  • General Service CRUD encapsulates the IService interface and further encapsulates CRUD. getQuery single row, removedelete, listquery collection page, and paging prefix naming method to distinguish Mapperlayers to avoid confusion.
  • Generic Tfor any entity object
  • It is recommended that if there is a possibility to customize the general Service method, please create your own IBaseServiceinheritance Mybatis-Plusprovided by the base class
  • Official website address:https://baomidou.com/pages/49cc81/#servicecrud%E6%8E%A5%E5%8F%A3

a>IService

There is an interface IService and its implementation class ServiceImpl in MyBatis-Plus, which encapsulate common business layer logic. For details, view the source code IService and ServiceImpl.

b>Create Service interface and implementation class

package com.grey.mybatisplus.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.grey.mybatisplus.entity.User;

/**
 * UserService继承IService模板提供的基础功能
 */
public interface UserService extends IService<User> {
    
    
}
package com.grey.mybatisplus.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.grey.mybatisplus.entity.User;
import com.grey.mybatisplus.mapper.UserMapper;
import com.grey.mybatisplus.service.UserService;
import org.springframework.stereotype.Service;

/**
 * ServiceImpl实现了IService,提供了IService中基础功能的实现
 * 若ServiceImpl无法满足业务需求,则可以使用自定义的UserService定义方法,并在实现类中实现
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    
    
}

c>Test query record number

@Autowired
private UserService userService;

@Test
public void testGetCount(){
    
    
	long count = userService.count();
	System.out.println("总记录数:" + count);
}

d>Test batch insert

@Test
public void testSaveBatch() {
    
    
    //SQL长度有限制,海量数据插入单条SQL无法实行
    //因此MP将批量插入在了通用Service中实现,而不是通用Mapper
    List<User> users = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
    
    
        User user = new User();
        user.setName("ybc" + i);
        user.setAge(20 + i);
        users.add(user);
    }
    //SQL:INSERT INTO t_user (username, age) VALUES (?, ?)
    userService.saveBatch(users);
}

4. Commonly used annotations

1. @TableName

  • After the above tests, when using MyBatis-Plus to implement basic CRUD, we did not specify the table to be operated. We only set the generic User when the Mapper interface inherited BaseMapper, and the table to be operated was the user table.
  • It can be concluded that when MyBatis-Plus determines the table to be operated on, it is determined by the generic type of BaseMapper, that is, the entity type, and the default table name of the operation is consistent with the class name of the entity type.

a>Problem

  • What problems will occur if the class name of the entity class type is inconsistent with the table name of the table to be operated?
  • We renamed the table user to t_user to test the query function
  • RENAME TABLE user TO t_user;
  • The program throws an exception, Table 'mybatis_plus.user' doesn't exist, because the current table name is t_user, and the default operation table name is consistent with the class name of the entity type, that is, the user table

Insert image description here

b>Solve the problem via @TableName

Add @TableName("t_user") to the entity class type to identify the table corresponding to the entity class, and the SQL statement can be successfully executed.

package com.grey.mybatisplus.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

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

c>Solve the problem through global configuration

  • During the development process, we often encounter the above problem, that is, the tables corresponding to entity classes have fixed prefixes, such as t_ or tbl_
  • At this time, you can use the global configuration provided by MyBatis-Plus to set a default prefix for the table name corresponding to the entity class. Then there is no need to identify the table corresponding to the entity class through @TableName on each entity class.
#配置Mybatis日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      #配置MyBatis-Plus操作表的默认前缀
      table-prefix: t_

2、@TableId

After the above test, MyBatis-Plus will use id as the primary key column by default when implementing CRUD, and when inserting data, it will generate id based on the snowflake algorithm strategy by default.

a>Problem

  • If the primary key in the entity class and table is not id, but other fields, such as uid, will MyBatis-Plus automatically recognize uid as the primary key column?
  • Change the attribute id in our entity class to uid, change the field id in the table to uid, and test the added function
  • ALTER TABLE t_user CHANGE id uid bigint;
  • The program throws an exception, Field 'uid' doesn't have a default value, indicating that MyBatis-Plus does not assign uid as the primary key.

Insert image description here
b>Solve the problem via @TableId

Mark it as the primary key through @TableId on the uid attribute in the entity class, and the SQL statement can be successfully executed.

package com.grey.mybatisplus.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    
    
    @TableId
    private Long uid;
    private String name;
    private Integer age;
    private String email;
}

c>value attribute of @TableId

  • If the attribute corresponding to the primary key in the entity class is id, and the field representing the primary key in the table is uid, if only the annotation @TableId is added to the attribute id, the exception Unknown column 'id' in 'field list' will be thrown, that is MyBatis-Plus will still operate id as the primary key of the table, and the field uid represents the primary key in the table
  • At this time, you need to specify the primary key field in the table through the value attribute of the @TableId annotation, @TableId("uid") or @TableId(value="uid")

Insert image description here

package com.grey.mybatisplus.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    
    
    @TableId(value = "uid")
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

d>type attribute of @TableId

The type attribute is used to define the primary key strategy

Commonly used primary key strategies:

value describe
IdType.ASSIGN_ID (default) The strategy based on the snowflake algorithm generates data ID, 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 has set the id auto-increment, otherwise it will be invalid.
IdType.INPUT Note: For this ID generation strategy, you need to delete the auto-increment strategy of the table and add data to manually set the ID. If the value of the primary key ID is not set, an error will be reported. The error message is that the primary key ID has not been given a value. If the primary key ID is set, , then the data can be added successfully
IdType.NONE Stateless, this type has no primary key set (the annotation is equivalent to following the global, and the global Rio is approximately equal to INPUT)

Configure the global primary key policy:

#配置Mybatis日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      #配置MyBatis-Plus操作表的默认前缀
      table-prefix: t_
      #配置MyBatis-Plus的主键策略
      id-type: auto

e>Snowflake algorithm

  • background

It is necessary to choose an appropriate solution to cope with the growth of data scale and to cope with the increasing access pressure and data volume.
The main expansion methods of the database include: business sub-database, master-slave replication, and database sub-table.

  • Database table

Scattered storage of different business data in different database servers can support businesses with millions or even tens of millions of users. However, if the business continues to develop, the single table data of the same business will also reach the processing bottleneck of a single database server. For example, if Taobao's hundreds of millions of user data are all stored in one table on a database server, it will definitely not be able to meet the performance requirements. In this case, the single table data needs to be split.

There are two ways to split single table data: vertical table splitting and horizontal table splitting. The schematic diagram is as follows:

Insert image description here

  • Vertical table

Vertical table splitting is suitable for splitting out some columns in the table that are not commonly used and occupy a large amount of space.

For example, the nickname and description fields in the previous diagram, assuming we are a dating website, when users filter other users, they mainly use the age and sex fields to query, while the nickname and description fields are mainly used for display. Generally not used in business inquiries. description itself is relatively long, so we can separate these two fields into another table, which can bring certain performance improvements when querying age and sex.

  • Level score table

Horizontal table sharding is suitable for tables with particularly large rows. Some companies require table sharding if the number of rows in a single table exceeds 50 million. This number can be used as a reference, but it is not an absolute standard. The key is to look at the access performance of the table. For some more complex tables, it may be necessary to split the tables if the number exceeds 10 million; for some simple tables, even if the data stored exceeds 100 million rows, it does not need to be split into tables.

But no matter what, when you see the amount of data in the table reaching tens of millions, you, as an architect, should be alert, because this is likely to be a performance bottleneck or hidden danger of the architecture.

Horizontal table sharding will introduce more complexity than vertical table sharding, such as how to handle globally unique data IDs.

Primary key auto-increment

  • Taking the most common user ID as an example, it can be segmented according to the range size of 1000000. 1 ~ 999999 is placed in Table 1, 1000000 ~ 1999999 is placed in Table 2, and so on.
  • Complex point: Selection of segment size. Too small a segment will lead to too many sub-tables after segmentation, increasing maintenance complexity; too large a segment may cause performance problems in a single table. It is generally recommended that the segment size be between 1 million and 20 million, depending on specific needs. Choose an appropriate segment size based on your business.
  • Advantages: New tables can be expanded smoothly as data increases. For example, if the current number of users is 1 million, if the number increases to 10 million, you only need to add a new table, and the original data does not need to be moved.
  • ④Disadvantages: Uneven distribution. If the table is divided according to 10 million, it is possible that the actual amount of data stored in a certain segment is only 1, while the actual amount of data stored in another segment is 10 million.

model

  • Taking user ID as an example, if we plan 10 database tables from the beginning, we can simply use the value of user_id % 10 to represent the database table number to which the data belongs. The user with ID 985 is placed in the subtable numbered 5. , the user with ID 10086 is placed in the subtable numbered 6.
  • Complexity: Determination of the initial number of tables. Too many tables are troublesome to maintain, while too few tables may cause performance problems with a single table.
  • Advantages: The table distribution is relatively even.
  • Disadvantages: Expanding new tables is troublesome, and all data must be redistributed.

snowflake algorithm

The snowflake algorithm is a distributed primary key generation algorithm announced by Twitter. It can ensure the non-repetition of primary keys in different tables and the orderliness of primary keys in the same table.

①Core idea:

The length is 64 bits in total (a long type).

The first is a sign bit, a 1-bit identifier. Since the long basic type is signed in Java, the highest bit is the sign bit, a positive number is 0, and a negative number is 1, so the id is generally a positive number, and the highest bit is 0.

The 41-bit time cutoff (millisecond level) stores the difference in time cutoff (current time cutoff - starting time cutoff), and the result is approximately equal to 69.73 years.

10 bits are used as the machine ID (5 bits are the data center, 5 bits are the machine ID, and can be deployed on 1024 nodes).

12 bits are used as serial numbers within milliseconds (meaning each node can generate 4096 IDs per millisecond).

Insert image description here

② Advantages : The overall ordering is based on increasing time, and there will be no ID collisions in the entire distributed system, and the efficiency is high.

3、@TableField

  • After the above tests, we can find that when MyBatis-Plus executes SQL statements, it must ensure that the attribute names in the entity class are consistent with the field names in the table.
  • What problems will occur if the attribute names and field names in the entity class are inconsistent?

a>Case 1

If the attributes in the entity class use the camel case naming style, and the fields in the table use the underline naming style,
for example, the entity class attribute userName, and the field user_name in the table,
MyBatis-Plus will automatically convert the underline naming style into the camel case naming style.
Equivalent to configuring in MyBatis

b>Case 2

If the attributes in the entity class and the fields in the table do not meet the conditions 1,
for example, the entity class attribute name and the field username in the table
need to use @TableField("username") on the entity class attribute to set the field name corresponding to the attribute
ALTER TABLE t_user CHANGE name username varchar(30);

package com.grey.mybatisplus.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    
    
    @TableId(value = "uid")
    private Long id;
    @TableField("username")
    private String name;
    private Integer age;
    private String email;
}

4、@TableLogic

a>logical delete

  • Physical deletion: real deletion, the corresponding data is deleted from the database, and the deleted data cannot be queried later.
  • Logical deletion: Fake deletion, changing the status of the field representing whether it has been deleted in the corresponding data to "deleted status", and this data record can still be seen in the database later.
  • Usage scenario: Data recovery can be performed

b>Implement logical deletion

step1: Create a tombstone status column in the database and set the default value to 0

Insert image description here

step2: Add logical deletion attributes to the entity class

package com.grey.mybatisplus.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    
    
    @TableId(value = "uid")
    private Long id;
    @TableField("username")
    private String name;
    private Integer age;
    private String email;
    @TableLogic
    private Integer isDeleted;
}

step3: test

  • Test the deletion function. What is actually executed is to modify
    UPDATE t_user SET is_deleted=1 WHERE id=? AND is_deleted=0
  • Test the query function. Logically deleted data will not be queried by default.
    SELECT id, username AS name, age, email, is_deleted FROM t_user WHERE is_deleted=0

5、@Version

Optimistic lock configuration
Note that after adding annotations, you need to add optimistic lock config configuration

  • SELECT id,name,price,version FROM t_product WHERE id=?
    Parameters: 1(Long)
    Row: 1, Alien Notebook, 100, 2
  • UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?
    Alien Notebook(String), 150(Integer), 3(Integer), 1(Long), 2(Integer)
    Updates : 1
  • Please see the Optimistic Locking chapter for details.

6、@KeySequence

  • SEQUENCE is a sequence number generator that can automatically generate sequence numbers for rows in the table and generate a set of equally spaced values ​​(type is number). Does not take up disk space, takes up memory.
  • Its main use is to generate the primary key value of a table, which can be referenced in an insert statement, or used to check the current value through a query, or to increment the sequence to the next value.
  • Note: primary key auto-increment exists in mysql, but does not exist in oracle.
  • Note: The primary key generation strategy must use INPUT

@KeySequence()

  • value = “SEQ_ORACLE_STRING_KEY” sequence usageSEQ_ORACLE_STRING_KEY
  • clazz = String.class type is string type
  • dbType = DbType.ORACLE The database type is ORACLE

5. Conditional constructors and common interfaces

1. Introduction to wapper

Insert image description here

  • Wrapper: conditionally constructed abstract class, the top parent class
    • AbstractWrapper: used to encapsulate query conditions and generate SQL where conditions
      • QueryWrapper: query condition encapsulation
      • UpdateWrapper: Update conditional encapsulation
      • AbstractLambdaWrapper: Using Lambda syntax
        • LambdaQueryWrapper: Query Wrapper for Lambda syntax usage
        • LambdaUpdateWrapper: Lambda update package Wrapper

2、QueryWrapper

a>Example 1: Assembling query conditions

@Test
public void test01() {
    
    
    //查询用户包含a,年龄在20到30之间,并且邮箱不为null的用户信息
    //SELECT id,username AS name,age,email,is_deleted FROM t_user
    // WHERE is_deleted=0 AND (username LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.like("username", "a")
            .between("age", 20, 30)
            .isNotNull("email");
    List<User> list = userMapper.selectList(queryWrapper);
    list.forEach(System.out::println);
}

b>Example 2: Assembly sorting conditions

@Test
public void test02() {
    
    
    //按年龄降序查询用户,如果年龄相同则按照id升序排列
    //SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE
    //is_deleted=0 ORDER BY age,id ASC
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.orderByDesc("age")
            .orderByAsc("id");
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}

c>Example 3: Assembling deletion conditions

@Test
public void test03() {
    
    
    //删除email为空的用户
    //DELETE FROM t_user WHERE (email IS NULL)
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.isNull("email");
    //条件构造器也可以构建删除语句的条件
    int result = userMapper.delete(queryWrapper);
    System.out.println("受影响的行数:" + result);
}

d>Example 4: Priority of conditions

@Test
public void test04() {
    
    
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    //将(年龄大于20并且用户名中包含有a)或邮箱为null的用户信息修改为年龄为:18
    //邮箱为:[email protected]
    //UPDATE t_user SET age=?,email=? WHERE (username LIKE ? AND age > ? OR email IS NULL)
    queryWrapper.like("username", "a")
            .gt("age", 20)
            .or()
            .isNull("email");
    User user = new User();
    user.setAge(18);
    user.setEmail("[email protected]");
    int result = userMapper.update(user, queryWrapper);
    System.out.println("受影响的行数:" + result);
}
@Test
public void test04() {
    
    
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    //将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改为:18
    //邮箱为:[email protected]
    //UPDATE t_user SET age=?,email=? WHERE (username LIKE ? AND (age > ? OR email IS NULL))
    //lambda表达式内的逻辑优先运算
    queryWrapper.like("username", "a")
            .and(i -> i.gt("age", 20).or().isNull("email"));
    User user = new User();
    user.setAge(18);
    user.setEmail("[email protected]");
    int result = userMapper.update(user, queryWrapper);
    System.out.println("受影响的行数:" + result);
}

e>Example 5: Assembling the select clause

@Test
public void test05() {
    
    
    //查询用户信息的username和age字段
    //SELECT username,age FROM t_user
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.select("username", "age");
    //selectMaps()返回Map集合列表,通常配合select()使用,避免User对象中没有被查询到的列值为null
    List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
    maps.forEach(System.out::println);
}

f>Example 6: Implementing subquery

@Test
public void test06() {
    
    
    //查询id小于等于3的用户信息
    //SELECT uid,username AS name,age,email,is_deleted FROM t_user WHERE
    //(id IN (select uid from t_user where uid <= 3))
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.inSql("uid", "select uid from t_user where uid <= 3");
    List<User> list = userMapper.selectList(queryWrapper);
    list.forEach(System.out::println);
}

3、UpdateWrapper

@Test
public void test07() {
    
    
    //将(年龄大于20或邮箱为null)并且用户名中包含a的用户信息修改
    //组装set子句以及修改条件
    UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
    //lambda表达式内的逻辑优先运算
    updateWrapper.set("age", 18)
            .set("email", "[email protected]")
            .like("username", "a")
            .and(i -> i.gt("age", 20).or().isNull("email"));
    //这里必须要创建User对象,否则无法应用自动填充。如果没有自动填充,可以设置为null
    //UPDATE t_user SET username=?,age=?,email=? WHERE
    // (username LIKE ? AND (age > ? OR email IS NULL))
    //User user = new User();
    //user.setName("张三");
    //int result = username.update(user,updateWrapper);
    //UPDATE t_user SET age=?,email=? WHERE (username LIKE ? AND (age > ? OR email IS NULL))
    int result = userMapper.update(null, updateWrapper);
    System.out.println(result);
}

4、condition

In the actual development process, assembly conditions are a common function, and these condition data come from user input and are optional. Therefore, when assembling these conditions, we must first determine whether the user has selected these conditions. If so, Assemble this condition. If there is no selection, it must not be assembled to avoid affecting the results of SQL execution.

Idea one:

@Test
public void test08() {
    
    
    //定义查询条件,有可能为null(用户未输入或未选择)
    String username = null;
    Integer ageBegin = 10;
    Integer ageEnd = 24;
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    //StringUtils.isNotBlank()判断某字符串是否不为空
    // 且长度不为0且不由空白符(whitespace)构成
    if (StringUtils.isNotBlank(username)) {
    
    
        queryWrapper.like("username", "a");
    }
    if (ageBegin != null) {
    
    
        queryWrapper.ge("age", ageBegin);
    }
    if (ageEnd != null) {
    
    
        queryWrapper.le("age", ageBegin);
    }
    //SELECT id,username AS name,age,email,is_deleted
    // FROM t_user WHERE (age >= ? AND age <= ?)
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}

Idea two:

There is no problem with the above implementation, but the code is more complicated. We can use the overloaded method with condition parameters to build query conditions and simplify code writing.

@Test
public void test08useCondition() {
    
    
   //定义查询条件,有可能为null(用户未输入或未选择)
   String username = null;
   Integer ageBegin = 10;
   Integer ageEnd = 24;
   QueryWrapper<User> queryWrapper = new QueryWrapper<>();
   //StringUtils.isNotBlank()判断某字符串是否不为
   // 空且长度不为0且不由空白符(whitespace)构成
   queryWrapper.like(StringUtils.isNotBlank(username), "username", "a")
           .ge(ageBegin != null, "age", ageBegin)
           .le(ageEnd != null, "age", ageEnd);
   //SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE (age >=
   //? AND age <= ?)
   List<User> users = userMapper.selectList(queryWrapper);
   users.forEach(System.out::println);
}

5、LambdaQueryWrapper

@Test
    public void test09() {
    
    
        //定义查询条件,有可能为null(用户未输入)
        String username = "a";
        Integer ageBegin = 10;
        Integer ageEnd = 24;
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        //避免使用字符串表示字段,防止运行是错误
        queryWrapper.like(StringUtils.isNotBlank(username), User::getName, username)
                .ge(ageBegin != null, User::getAge, ageBegin)
                .le(ageEnd != null, User::getAge, ageEnd);
        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }

6、LambdaUpdateWrapper

@Test
public void test10() {
    
    
    //组装set子句
    LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
    updateWrapper.set(User::getAge, 18)
            .set(User::getEmail, "[email protected]")
            .like(User::getName, "a")
            .and(i -> i.lt(User::getAge, 24).or().isNull(User::getEmail));
    //lambda表达式的逻辑优先运算
    User user = new User();
    int result = userMapper.update(user, updateWrapper);
    System.out.println("受影响的行数:" + result);
}

6. Plug-ins

1. Pagination plug-in

MyBatis Plus comes with a paging plug-in, which can realize the paging function with simple configuration.

a>Add configuration class

package com.grey.mybatisplus.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan("com.gery.mybatisplus.mapper") //可以将主类中的注解移到此处
public class MybatisPlusConfig {
    
    

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

b>Test

@Test
public void testPage() {
    
    
    //设置分页参数
    Page<User> page = new Page<>(1, 5);
    userMapper.selectPage(page, null);
    //获取分页数据
    List<User> list = page.getRecords();
    list.forEach(System.out::println);
    System.out.println("当前页:" + page.getCurrent());
    System.out.println("每页显示的条数:" + page.getSize());
    System.out.println("总记录数:" + page.getTotal());
    System.out.println("总页数:" + page.getPages());
    System.out.println("是否有上一页:" + page.hasPrevious());
    System.out.println("是否有下一页:" + page.hasNext());
}

Test results:
User(id=1, name=Jone, age=18, [email protected], isDeleted=0) User(id=2,name=Jack, age=20, [email protected] , isDeleted=0) User(id=3, name=Tom,age=28, [email protected], isDeleted=0) User(id=4, name=Sandy, age=21,email=test4@baomidou .com, isDeleted=0) User(id=5, name=Billie, age=24, [email protected], isDeleted=0) Current page: 1 Number of items displayed on each page: 5 Total number of records: 17 Total number of pages: 4 Whether there is a previous page: false Whether there is a next page: true

2. xml custom paging

a>Interface method defined in UserMapper

package com.grey.mybatisplus.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.grey.mybatisplus.entity.User;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

@Repository
public interface UserMapper extends BaseMapper<User> {
    
    

    /**
     * 根据年龄查询用户列表,分页显示
     * @param page 分页对象,xml中可以从里面进行取值,传递参数Page即自动分页,必须放在第一位
     * @param age 年龄
     * @return return
     */
    Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);

}

b>Write SQL in UserMapper.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.grey.mybatisplus.mapper.UserMapper">

    <!-- SQL片段,记录基础字段 -->
    <sql id="BaseColumns">uid,username,age,email</sql>

    <!--Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);-->
    <select id="selectPageVo" resultType="com.grey.mybatisplus.entity.User">
        SELECT
        <include refid="BaseColumns"/>
        FROM t_user WHERE age > #{age}
    </select>

</mapper>

c>Test

@Test
public void testSelectPageVo() {
    
    
    //设置分页参数
    Page<User> page = new Page<>(1, 5);
    userMapper.selectPageVo(page, 20);
    //获取分页数据
    List<User> list = page.getRecords();
    list.forEach(System.out::println);
    System.out.println("当前页:" + page.getCurrent());
    System.out.println("每页显示的条数:" + page.getSize());
    System.out.println("总记录数:" + page.getTotal());
    System.out.println("总页数:" + page.getPages());
    System.out.println("是否有上一页:" + page.hasPrevious());
    System.out.println("是否有下一页:" + page.hasNext());
}

Result:
User(id=3, name=Tom, age=28, [email protected], isDeleted=null) User(id=4,name=Sandy, age=21, [email protected], isDeleted=null) User(id=5, name=Billie,age=24, [email protected], isDeleted=null) User(id=8, name=ybc1, age=21,email=null, isDeleted= null) User(id=9, name=ybc2, age=22, email=null, isDeleted=null) Current page: 1 Number of items displayed on each page: 5 Total number of records: 12 Total number of pages: 3 Is there a previous page? Page: false Whether there is a next page: true

3. Optimistic locking

a>Scene

  • A product has a cost price of 80 yuan and a selling price of 100 yuan. The boss first informed Xiao Li that he should increase the price of the goods by 50 yuan. Xiao Li was playing games and was delayed for an hour. Exactly an 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 product backend system at the same time. When Xiao Li is operating, the system first takes out the product price of 100 yuan; Xiao Wang is also operating, and the product price taken out is 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.
  • The current product price is 70 yuan, which is 10 yuan lower than the cost price. A few minutes later, more than 1,000 items of this product were quickly sold, and the boss lost more than 10,000 yuan.

b> Optimistic locking and pessimistic locking

  • In the above story, if it is an optimistic lock, Xiao Wang will check whether the price has been modified before saving it. If it has been modified, the modified price of 150 yuan will be taken out again, 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 completes the operation, and the final price will be guaranteed to be 120 yuan.

c>Simulate modification conflicts

Add product table to database

CREATE TABLE t_product
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
NAME VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称',
price INT(11) DEFAULT 0 COMMENT '价格',
VERSION INT(11) DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id)
);

adding data

INSERT INTO t_product (id, NAME, price) VALUES (1, '外星人笔记本', 100);

Add entity

package com.grey.mybatisplus.entity;

import lombok.Data;

@Data
public class Product {
    
    
    private Long id;
    private String name;
    private Integer price;
    private Integer version;
}

Add mapper

package com.grey.mybatisplus.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.grey.mybatisplus.entity.Product;

public interface ProductMapper extends BaseMapper<Product> {
    
    
}

test

@Test
public void testConcurrentUpdate() {
    
    
    //1、小李
    Product p1 = productMapper.selectById(1L);
    System.out.println("小李取出的价格:" + p1.getPrice());

    //2、小王
    Product p2 = productMapper.selectById(1L);
    System.out.println("小王取出的价格:" + p2.getPrice());

    //3、小李将价格加了50元,存入数据库
    p1.setPrice(p1.getPrice() + 50);
    int result1 = productMapper.updateById(p1);
    System.out.println("小李修改结果:" + result1);

    //4、小王将商品减少了30元,存入了数据库
    p2.setPrice(p2.getPrice() - 30);
    int result2 = productMapper.updateById(p2);
    System.out.println("小王修改结果:" + result2);

    //最后的结果
    Product p3 = productMapper.selectById(1L);
    //价格覆盖,最后的结果:70
    System.out.println("最后的结果:" + p3.getPrice());

}

d>Optimistic lock implementation process

  • Add version field to database
  • When fetching records, get the current version
    SELECT id, name,price, versionFROM product WHERE id=1
  • When updating, version + 1. If the version in the where statement is incorrect, the update fails.
    UPDATE product SET price=price+50, version= version+ 1 WHERE id=1 AND version=1

e>Mybatis-Plus implements optimistic locking

Modify entity class

package com.grey.mybatisplus.entity;

import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;

@Data
public class Product {
    
    
    private Long id;
    private String name;
    private Integer price;
    @Version
    private Integer version;
}

Add optimistic lock plug-in configuration

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

Test modification conflicts

  • Xiao Li queries product information:
    SELECT id,name,price,version FROM t_product WHERE id=?
  • Xiao Wang queries product information:
    SELECT id,name,price,version FROM t_product WHERE id=?
  • Xiao Li modifies the product price and automatically adds version+1
    UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?Parameters: Alien Notebook (String), 150 (Integer), 1( Integer), 1(Long), 0(Integer)
  • Xiao Wang modifies the product price. At this time, the version has been updated. The condition is not met and the modification fails.
    UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?Parameters: Alien Notebook (String), 70 (Integer), 1(Integer), 1(Long), 0(Integer)
  • In the end, Xiao Wang failed to modify and query the price: 150
    SELECT id,name,price,version FROM t_product WHERE id=?

Optimize process

@Test
public void testConcurrentUpdate() {
    
    
    //1、小李
    Product p1 = productMapper.selectById(1L);
    System.out.println("小李取出的价格:" + p1.getPrice());

    //2、小王
    Product p2 = productMapper.selectById(1L);
    System.out.println("小王取出的价格:" + p2.getPrice());

    //3、小李将价格加了50元,存入数据库
    p1.setPrice(p1.getPrice() + 50);
    int result1 = productMapper.updateById(p1);
    System.out.println("小李修改结果:" + result1);

    //4、小王将商品减少了30元,存入了数据库
    p2.setPrice(p2.getPrice() - 30);
    int result2 = productMapper.updateById(p2);
    System.out.println("小王修改结果:" + result2);
    if (result2 == 0) {
    
    
        //失败重试,重新获取version并更新
        p2 = productMapper.selectById(1L);
        p2.setPrice(p2.getPrice() - 30);
        result2 = productMapper.updateById(p2);
    }
    System.out.println("小王修改重试的结果:" + result2);

    //最后的结果
    Product p3 = productMapper.selectById(1L);
    //价格覆盖,最后的结果:70
    System.out.println("最后的结果:" + p3.getPrice());
}

7. General enumeration

Some field values ​​in the table are fixed, such as gender (male or female). At this time, we can use MyBatis-Plus's general enumeration to achieve this.

a>Add field sex to database table

Insert image description here
b>Create a generic enumeration type

package com.grey.mybatisplus.enums;

import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;

@Getter
public enum SexEnum {
    
    

    MALE(1, "男"),
    FEMALE(2, "女");

    @EnumValue
    private Integer sex;
    private String sexName;

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

}

c>Configure scanning common enumeration

#配置Mybatis日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      #配置MyBatis-Plus操作表的默认前缀
      table-prefix: t_
      #配置MyBatis-Plus的主键策略
      id-type: auto
  #配置扫描通用枚举
  type-enums-package: com.grey.mybatisplus.enums

d>Test

@Test
public void testSexEnum() {
    
    
    User user = new User();
    user.setName("Enum");
    user.setAge(20);
    //设置性别信息为枚举项,会将@EnumValue注解所标识的属性值存储到数据库
    user.setSex(SexEnum.MALE.getSex());
    //INSERT INTO t_user ( uid, username, age, sex ) VALUES ( ?, ?, ?, ? )
    //Parameters: 1682288144970371073(Long), Enum(String), 20(Integer), 1(Integer)
    //Updates: 1
    userMapper.insert(user);
}

8. Code Generator

1. Introduce dependencies

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

2. Rapid generation

package com.grey.mybatisplus.generator;

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

import java.util.Collections;

/**
 * @Description: MybatisPlus代码生成器
 * @author: lz
 * @since: 2023/7/21
 */
public class MybatisPlusGenerator {
    
    

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

}

Insert image description here

9. Multiple data sources

  • Applicable to a variety of scenarios: pure multi-library, read-write separation, one master and multiple slaves, mixed mode, etc.
    Currently we will simulate a pure multi-library scenario, other scenarios are similar
  • Scenario description:
    We create two libraries, namely: mybatis_plus (the previous library is unchanged) and mybatis_plus_1 (newly created), and move the t_product table of the mybatis_plus library to the mybatis_plus_1 library, so that each library has one table and one test case. Obtain user data and product data. If obtained, the multi-database simulation is successful.

1. Create database and tables

Create database mybatis_plus_1 and table t_product

CREATE DATABASE `mybatis_plus_1`;
use `mybatis_plus_1`;
CREATE TABLE t_product
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
NAME VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称',
price INT(11) DEFAULT 0 COMMENT '价格',
VERSION INT(11) DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id)
);

Add test data

INSERT INTO t_product(id, NAME, price) VALUES (1, '外星人笔记本', 100);

Delete the mybatis_plus library t_product table

use mybatis_plus;
DROP TABLE IF EXISTS t_product;

2. Introduce dependencies

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

3. Configure multiple data sources

Description: Comment out the previous database connection and add new configuration

spring:
  #配置数据源信息
  datasource:
    dynamic:
      #设置默认的数据源或数据源组,默认值即为master
      primary: master
      #严格匹配数据源,默认false,true未匹配到指定数据时抛出异常,false使用默认数据源
      strict: false
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: root
        slave_1:
          url: jdbc:mysql://localhost:3306/mybatis_plus_1?characterEncoding=utf-8&useSSL=false
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: root

4. Create user service

package com.grey.mybatisplus.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.grey.mybatisplus.entity.User;

/**
 * UserService继承IService模板提供的基础功能
 */
public interface UserService extends IService<User> {
    
    
}
package com.grey.mybatisplus.service.impl;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.grey.mybatisplus.entity.User;
import com.grey.mybatisplus.mapper.UserMapper;
import com.grey.mybatisplus.service.UserService;
import org.springframework.stereotype.Service;

/**
 * ServiceImpl实现了IService,提供了IService中基础功能的实现
 * 若ServiceImpl无法满足业务需求,则可以使用自定义的UserService定义方法,并在实现类中实现
 */
@Service
@DS("master")//指定所操作的数据源
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    
    
}

5. Create product service

package com.grey.mybatisplus.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.grey.mybatisplus.entity.Product;

public interface ProductService extends IService<Product> {
    
    
}
package com.grey.mybatisplus.service.impl;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.grey.mybatisplus.entity.Product;
import com.grey.mybatisplus.mapper.ProductMapper;
import com.grey.mybatisplus.service.ProductService;
import org.springframework.stereotype.Service;

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

6. Test

@Autowired
private ProductMapper productMapper;

@Autowired
private ProductService productService;

@Test
public void testDynamicDataSource(){
    
    
    System.out.println(userService.getById(4L));
    System.out.println(productService.getById(1L));
}

Results:
1. If the object can be obtained successfully, the test is successful.
2. If we realize the separation of reading and writing, add the write operation method to the main database data source, and the read operation method to add the slave database data source, and automatically switch, will it be possible? Achieve reading and writing separation?

10. MyBatisX plug-in

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 and multi-table joint queries. We need to write code and SQL statements ourselves. How can we solve this problem quickly? At this time, we can use the MyBatisX plug-in.
MyBatisX is a rapid development plug-in based on IDEA and is born for efficiency.

MyBatisX plug-in usage:https://baomidou.com/pages/ba5b24/

Guess you like

Origin blog.csdn.net/weixin_45888036/article/details/131808869