Detailed explanation of MyBatisPlus
1. Standard data layer development
MyBatisPlus (MP for short) is an enhanced tool developed based on the MyBatis framework, designed to simplify development and improve efficiency
The official website of MyBatisPlus is:https://mp.baomidou.com/
1.1 Standard CRUD
1.2 Added
int insert (T t)
-
T: Generic, newly added to save new data
-
int: return value, return 1 after adding successfully, return 0 if not adding successfully
Add new operations in the test class:
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSave() {
User user = new User();
user.setName("yyds");
user.setPassword("yyds");
user.setAge(12);
user.setTel("10086");
userDao.insert(user);
}
}
After the test is executed, a piece of data is added to the database table. But the primary key ID in the data is a bit long, which is determined by the primary key ID generation strategy.
1.3 delete
int deleteById (Serializable id)
-
Serializable: parameter type
-
Thinking: Why is the parameter type a serialization class?
- String and Number are subclasses of Serializable,
- Number is the parent class of Float, Double, Integer, etc.
- The data types that can be used as primary keys are already subclasses of Serializable.
- MP uses Serializable as a parameter type, just as we can use Object to receive any data type.
-
-
int: return value type, data deleted successfully returns 1, undeleted data returns 0.
Do it in the test class:
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testDelete() {
userDao.deleteById(1401856123725713409L);
}
}
1.4 Modifications
int updateById(T t);
-
T: Generic, the data content that needs to be modified, note that because it is modified according to the ID, the ID attribute value needs to be included in the incoming object
-
int: return value, return 1 after modification is successful, return 0 for unmodified data
Add new operations in the test class:
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testUpdate() {
User user = new User();
user.setId(1L);
user.setName("Tom888");
user.setPassword("tom888");
userDao.updateById(user);
}
}
Note: When modifying, only modify the fields with values in the entity object.
1.5 Query by ID
T selectById (Serializable id)
- Serializable: parameter type, the value of the primary key ID
- T: query based on ID will only return one piece of data
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetById() {
User user = userDao.selectById(2L);
System.out.println(user);
}
}
1.6 Query all
List<T> selectList(Wrapper<T> queryWrapper)
- Wrapper: The conditions used to construct conditional queries, currently we do not have a direct pass as Null
- List: Because the query is all, the returned data is a collection
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll() {
List<User> userList = userDao.selectList(null);
System.out.println(userList);
}
}
The methods we call all come from the BaseMapper class inherited from the DAO interface. There are many methods in it.
1.7 Pagination query
The method used for pagination query is:
IPage<T> selectPage(IPage<T> page, Wrapper<T> queryWrapper)
- IPage: Used to build paging query conditions
- Wrapper: The conditions used to construct conditional queries, currently we do not have a direct pass as Null
- IPage: return value, you will find that the return value of building pagination conditions and methods is IPage
IPage is an interface, we need to find its implementation class to build it, and there is an implementation class for it Page
.
Step 1: Call the method to pass in parameters to get the return value
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
//分页查询
@Test
void testSelectPage(){
//1 创建IPage分页对象,设置分页参数,1为当前页码,3为每页显示的记录数
IPage<User> page=new Page<>(1,3);
//2 执行分页查询
userDao.selectPage(page,null);
//3 获取分页结果
System.out.println("当前页码值:"+page.getCurrent());
System.out.println("每页显示数:"+page.getSize());
System.out.println("一共多少页:"+page.getPages());
System.out.println("一共多少条数据:"+page.getTotal());
System.out.println("数据:"+page.getRecords());
}
}
Step 2: Set up the pagination blocker
This interceptor MP has been provided for us, we only need to configure it as a bean object managed by Spring.
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//1 创建MybatisPlusInterceptor拦截器对象
MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
//2 添加分页拦截器
mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mpInterceptor;
}
}
Step 3: Run the test program
If you want to view the SQL statements executed by MP, you can modify the application.yml configuration file,
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印SQL日志到控制台
2. DQL programming control
2.1 Condition query
2.1.1 Classes for conditional queries
- MyBatisPlus encapsulates the writing of complex SQL query conditions, and completes the combination of query conditions in the form of programming.
We have seen this before. For example, when querying all and paging queries, we have seen a Wrapper
class. This class is used to build query conditions, as shown in the following figure:
2.1.2 Environment construction
-
Create a SpringBoot project
-
Add corresponding dependencies in pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.0</version> </parent> <groupId>com.yyds</groupId> <artifactId>mybatisplus_02_dql</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
-
Write the UserDao interface
@Mapper public interface UserDao extends BaseMapper<User> { }
-
Write model classes
@Data public class User { private Long id; private String name; private String password; private Integer age; private String tel; }
-
Write boot class
@SpringBootApplication public class Mybatisplus02DqlApplication { public static void main(String[] args) { SpringApplication.run(Mybatisplus02DqlApplication.class, args); } }
-
Write a configuration file
# dataSource spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC username: root password: root # mp日志 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
-
write test class
@SpringBootTest class Mybatisplus02DqlApplicationTests { @Autowired private UserDao userDao; @Test void testGetAll(){ List<User> userList = userDao.selectList(null); System.out.println(userList); } }
-
application.yml add the following content:
# mybatis-plus日志控制台输出 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: banner: off # 关闭mybatisplus启动图标
-
Cancel SpringBoot's log printing
application.yml add the following content:
spring: main: banner-mode: off # 关闭SpringBoot启动图标(banner)
-
2.1.3 Construct conditional query
When querying, our entry is on the Wrapper class, because it is an interface, so we need to find its corresponding implementation class, and there are many implementation classes, indicating that we have multiple ways to construct query condition objects ,
- Let's look at the first one first:QueryWrapper
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
QueryWrapper qw = new QueryWrapper();
qw.lt("age",18);
List<User> userList = userDao.selectList(qw);
System.out.println(userList);
}
}
-
lt: less than (<), the final sql statement is
SELECT id,name,password,age,tel FROM user WHERE (age < ?)
After the introduction of the first method, there is a small problem that it is easy to make mistakes when writing the conditions. For example, if the age is written incorrectly, the query will fail.
- Let's look at the second one:QueryWrapper based on using lambda
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
QueryWrapper<User> qw = new QueryWrapper<User>();
qw.lambda().lt(User::getAge, 10);//添加条件
List<User> userList = userDao.selectList(qw);
System.out.println(userList);
}
}
- User::getAget, in the lambda expression, class name::method name, the final sql statement is:
SELECT id,name,password,age,tel FROM user WHERE (age < ?)
Note: Generics cannot be saved when building LambdaQueryWrapper.
At this time, when we write the condition again, there will be no wrong name, but there is an extra layer of lambda() call behind qw
- Then look at the third:LambdaQueryWrapper
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(User::getAge, 10);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
This method solves the problems of the previous method.
2.1.4 Multi-condition construction
After learning three ways to construct query objects, each has its own characteristics, so you can use any one. Just now, there is only one condition, so how to construct it if there are multiple conditions?
Requirements: Query the database table for user information between the ages of 10 and 30
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(User::getAge, 30);
lqw.gt(User::getAge, 10);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
-
gt: greater than (>), the final SQL statement is
SELECT id,name,password,age,tel FROM user WHERE (age < ? AND age > ?)
-
When constructing multiple conditions, chain programming can be supported
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>(); lqw.lt(User::getAge, 30).gt(User::getAge, 10); List<User> userList = userDao.selectList(lqw); System.out.println(userList);
Requirement: Query the data in the database table, the age is less than 10 or the age is greater than 30
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(User::getAge, 10).or().gt(User::getAge, 30);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
-
or() is equivalent to the keyword in our sql statement
or
, if it is not added by defaultand
, the final sql statement is:SELECT id,name,password,age,tel FROM user WHERE (age < ? OR age > ?)
3.1.5 Null judgment
Requirements: Query the database table, and query the eligible records according to the input age range
When the user enters a value,
If you only enter the first box, it means you want to query users who are older than this age
If only the second box is entered, it means that users younger than the age are to be queried
If both boxes are entered, it means that users whose age is between two ranges are to be queried
Think about the first question: If the background wants to receive two data from the front end, how should it be received?
We can use two simple data types, or a model class, but there is currently only one age attribute in the User class, such as:
@Data
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
Solution 1: Add attribute age2, this method can but will affect the attribute content of the original model class
@Data
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
private Integer age2;
}
Solution 2: Create a new model class, let it inherit the User class, and add the age2 attribute to it, and UserQuery adds the age2 attribute after having the User attribute.
@Data
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
@Data
public class UserQuery extends User {
private Integer age2;
}
After the environment is ready, let's implement the requirements just now:
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
//模拟页面传递过来的查询数据
UserQuery uq = new UserQuery();
uq.setAge(10);
uq.setAge2(30);
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
if(null != uq.getAge2()){
lqw.lt(User::getAge, uq.getAge2());
}
if( null != uq.getAge()) {
lqw.gt(User::getAge, uq.getAge());
}
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
The above writing method can complete the judgment that the condition is not empty, but the problem is obvious. If there are many conditions, each condition needs to be judged, and the amount of code is relatively large. Let’s look at the simplified method provided by MP:
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
//模拟页面传递过来的查询数据
UserQuery uq = new UserQuery();
uq.setAge(10);
uq.setAge2(30);
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(null!=uq.getAge2(),User::getAge, uq.getAge2());
lqw.gt(null!=uq.getAge(),User::getAge, uq.getAge());
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
2.2 Query projection
2.2.1 Query specified fields
At present, when we query data, we do nothing and the default is to query the contents of all fields in the table. The query projection we call does not query all fields, but only queries the data of the specified content.
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.select(User::getId,User::getName,User::getAge);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
-
The select(…) method is used to set the field column of the query, and multiple fields can be set. The final sql statement is:
SELECT id,name,age FROM user
-
If you are not using lambda, you need to manually specify the field
@SpringBootTest class Mybatisplus02DqlApplicationTests { @Autowired private UserDao userDao; @Test void testGetAll(){ QueryWrapper<User> lqw = new QueryWrapper<User>(); lqw.select("id","name","age","tel"); List<User> userList = userDao.selectList(lqw); System.out.println(userList); } }
2.2.2 Aggregation query
Requirements: aggregate function query, complete the use of count, max, min, avg, sum
count: the total number of records
max: maximum value
min: minimum value
avg: average value
sum: Sum
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
QueryWrapper<User> lqw = new QueryWrapper<User>();
//lqw.select("count(*) as count");
//SELECT count(*) as count FROM user
//lqw.select("max(age) as maxAge");
//SELECT max(age) as maxAge FROM user
//lqw.select("min(age) as minAge");
//SELECT min(age) as minAge FROM user
//lqw.select("sum(age) as sumAge");
//SELECT sum(age) as sumAge FROM user
lqw.select("avg(age) as avgAge");
//SELECT avg(age) as avgAge FROM user
List<Map<String, Object>> userList = userDao.selectMaps(lqw);
System.out.println(userList);
}
}
In order to make it easier when encapsulating the results, we named the aggregation functions above to obtain the data later
2.2.3 Group query
Requirements: group query, complete group by query use
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
QueryWrapper<User> lqw = new QueryWrapper<User>();
lqw.select("count(*) as count,tel");
lqw.groupBy("tel");
List<Map<String, Object>> list = userDao.selectMaps(lqw);
System.out.println(list);
}
}
-
groupBy is grouping, and the final sql statement is
SELECT count(*) as count,tel FROM user GROUP BY tel
Notice:
- Aggregation and grouping queries cannot be done using lambda expressions
- MP is just an enhancement to MyBatis. If MP cannot be implemented, we can directly use MyBatis in the DAO interface to implement it.
2.3 Query conditions
There are many query conditions for MP:
- range matching (> , = , between)
- Fuzzy matching (like)
- Empty judgment (null)
- inclusive match (in)
- group
- order
- ……
2.3.1 Equivalent query
Requirements: Query user information based on username and password
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.eq(User::getName, "Jerry").eq(User::getPassword, "jerry");
User loginUser = userDao.selectOne(lqw);
System.out.println(loginUser);
}
}
-
eq(): Equivalent
=
, the corresponding sql statement isSELECT id,name,password,age,tel FROM user WHERE (name = ? AND password = ?)
-
selectList: the query result is multiple or single
-
selectOne: The query result is a single
2.3.2 Range query
Requirement: range query for age, use lt(), le(), gt(), ge(), between() for range query
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.between(User::getAge, 10, 30);
//SELECT id,name,password,age,tel FROM user WHERE (age BETWEEN ? AND ?)
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
- gt(): greater than (>)
- ge(): greater than or equal to (>=)
- lt(): less than (<)
- lte(): less than or equal to (<=)
- between():between ? and ?
2.3.3 Fuzzy query
J
Requirements: Query the user information whose value of the name attribute in the table starts with, use like to perform fuzzy query
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.likeLeft(User::getName, "J");
//SELECT id,name,password,age,tel FROM user WHERE (name LIKE ?J)
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
- like(): Add percent signs before and after, such as %J%
- likeLeft(): Add a percent sign in front, such as %J
- likeRight(): Add a percent sign after it, such as J%
2.3.4 Sort queries
Requirement: Query all data, and then sort by id descending
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
LambdaQueryWrapper<User> lwq = new LambdaQueryWrapper<>();
/**
* condition :条件,返回boolean,
当condition为true,进行排序,如果为false,则不排序
* isAsc:是否为升序,true为升序,false为降序
* columns:需要操作的列
*/
lwq.orderBy(true,false, User::getId);
userDao.selectList(lw
}
}
In addition to the query condition construction methods introduced above, there are many other methods, such as isNull, isNotNull, in, notIn and other methods to choose from. For details, refer to the condition constructor in the official document to learn and use. The specific URL for:
https://mp.baomidou.com/guide/wrapper.html#abstractwrapper
2.4 Mapping Compatibility
Previously, we have been able to query data from tables and encapsulate the data into model classes. This whole process involves a table and a model class:
Problem 1: Table fields are out of sync with coded attribute design
When the column names of the table and the attribute names of the model class are inconsistent, the data will not be encapsulated into the model object. At this time, one of them needs to be modified. If the premise is that neither side can be changed, how to solve it?
MP provides us with an annotation @TableField
, which can be used to realize the mapping relationship between the attribute name of the model class and the column name of the table
Problem 2: The encoding adds properties that are not defined in the database
When there is an additional field in the model class that does not exist in the database table, it will cause the generated SQL statement to query the field that does not exist in the database when selecting, and an error will be reported when the program runs. The error message is:
Unknown column 'extra field name' in 'field list'
The specific solution uses @TableField
annotations, which have a property called exist
, set whether the field exists in the database table, if it is set to false, it does not exist, and when the SQL query is generated, the field will not be queried again.
Question 3: Using the default query opens up more field viewing permissions
By querying the data of all the columns in the table, some sensitive data may be queried and returned to the front end. At this time, we need to restrict which fields are not queried by default. The solution is @TableField
an attribute of the annotation called select
, which sets whether the value of the field needs to be queried by default, true (default value) means that the field is queried by default, and false means that the field is not queried by default.
Knowledge point 1: @TableField
name | @TableField |
---|---|
type | attribute annotation |
Location | above the model class attribute definition |
effect | Set the field relationship in the database table corresponding to the current attribute |
related attributes | value (default): set the database table field name exist: set whether the attribute exists in the database table field, the default is true, this attribute cannot be combined with value use select: set whether the attribute participates in the query, this attribute is different from the select() mapping configuration conflict |
Question 4: The table name is not synchronized with the code development design
The problem is mainly that the name of the table is inconsistent with the name of the model class, causing the query to fail. At this time, the following error message is usually reported:
Table ‘databaseName.tableNaem’ doesn’t exist, the translation is that the table in the database does not exist.
The solution is to use another annotation provided by MP @TableName
to set the correspondence between tables and model classes.
Knowledge point 2: @TableName
name | @TableName |
---|---|
type | class annotation |
Location | above the model class definition |
effect | Set the current class corresponding to the database table relationship |
related attributes | value (default): set the database table name |
3. DML programming control
We have introduced the query-related operations, and then we need to explain the content of the other three, additions, deletions, and modifications. To explain one by one, the first is the content in the new (insert).
3.1 id generation policy control
- Different tables apply different id generation strategies
- log: increment(1,2,3,4,...)
- Shopping Orders: Special Rules (FQ23948AK3843)
- Takeaway order: related area date and other information (10 04 20200314 34 91)
- Relational table: id can be omitted
- ……
Different businesses should use different ID generation methods, so what primary key generation strategies are provided in MP, and how should we choose?
Here we need to use an annotation of MP called @TableId
( @TableId(type = IdType.INPUT)
)
Knowledge point 1: @TableId
name | @TableId |
---|---|
type | attribute annotation |
Location | above the definition of the property used to represent the primary key in the model class |
effect | Set the generation strategy of the primary key attribute in the current class |
related attributes | value (default): set the primary key name of the database table type: set the generation strategy of the primary key attribute, and check the value according to the enumeration value of IdType |
As can be seen from the source code, in addition to the AUTO strategy, there are several generation strategies as follows:
- NONE: No id generation strategy is set
- INPUT: The user manually enters the id
- ASSIGN_ID: Snowflake algorithm generates id (compatible with numeric and string types)
- ASSIGN_UUID: Use the UUID generation algorithm as the id generation strategy
- Several other strategies are obsolete and will be replaced by ASSIGN_ID and ASSIGN_UUID.
What is distributed ID?
- When the amount of data is large enough, one database server cannot store it. At this time, multiple database servers are needed for storage.
- For example, the order table may be stored on different servers
- If you use the auto-increment primary key of the database table, there will be a conflict because it is on two servers
- At this time, a globally unique ID is needed, which is the distributed ID.
Introduced the generation strategies of these primary key IDs, which one should we use in the future?
- NONE: Do not set the id generation strategy, MP is not automatically generated, approximately equal to INPUT, so both methods need to be manually set by the user, but the first problem with manual setting is that the same ID is prone to cause primary key conflicts, in order to ensure that the primary keys do not conflict You need to make a lot of judgments, and it is more complicated to implement.
- AUTO: The database ID is auto-incremented. This strategy is suitable for use when there is only one database server, and cannot be used as a distributed ID
- ASSIGN_UUID: It can be used in a distributed environment, and it can be guaranteed to be unique, but the generated primary key is a 32-bit string, which is too long to occupy space and cannot be sorted, and the query performance is also slow
- ASSIGN_ID: It can be used in a distributed situation. The generated numbers are of the Long type and can be sorted with high performance. However, the generated strategy is related to the server time. If the system time is modified, it may lead to duplicate primary keys.
- To sum up, each primary key strategy has its own advantages and disadvantages, and it is the most sensible choice to choose according to the actual situation of your own project business.
Simplified configuration
mybatis-plus:
global-config:
db-config:
id-type: assign_id
Once configured, the primary key ID strategy for each model class will be assign_id.
Mapping relationship between database tables and model classes
MP will use the lowercase first letter of the class name of the model class as the table name by default. If the names of the database tables start with tbl_
, then we need to add all the model classes @TableName
, such as:
The configuration is still relatively cumbersome. The simplified method is to configure the following content in the configuration file:
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
Set the prefix content of the table, so that MP will tbl_
add the first letter of the model class in lowercase, and it will just be assembled into the table name of the database.
3.2 Multi-record operation
How to implement multiple deletions, let's find the corresponding API method
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
Requirement: Delete the data in the database table according to the incoming id set.
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testDelete(){
//删除指定多条数据
List<Long> list = new ArrayList<>();
list.add(1402551342481838081L);
list.add(1402553134049501186L);
list.add(1402553619611430913L);
userDao.deleteBatchIds(list);
}
}
After the execution is successful, the data in the database table will be deleted according to the specified id.
In addition to bulk deletion according to the id set, you can also perform batch query according to the id set. Let’s take a look at the API first.
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
The method name is translated as: query (batch query based on ID), and the parameter is a collection that can store multiple id values.
Requirement: Query user information based on the incoming ID set
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetByIds(){
//查询指定多条数据
List<Long> list = new ArrayList<>();
list.add(1L);
list.add(3L);
list.add(4L);
userDao.selectBatchIds(list);
}
}
The query result will be queried according to the specified id value passed in
3.3 Tombstone
How to implement logical deletion in MP?
Step 1: Modify the database table to add deleted
columns
The field name can be arbitrary, and the content can also be customized. For example, 0
it means normal, 1
and it means delete. You can set its default value to normal when adding a column 0
.
Step 2: Add attributes to the entity class
(1) Add an attribute name corresponding to the column of the database table. The name can be arbitrary. If it does not match the column name of the data table, you can use @TableField for relationship mapping. If it is consistent, it will automatically correspond.
(2) Identify the newly added field as a tombstone field, use@TableLogic
@Data
//@TableName("tbl_user") 可以不写是因为配置了全局配置
public class User {
@TableId(type = IdType.ASSIGN_UUID)
private String id;
private String name;
@TableField(value="pwd",select=false)
private String password;
private Integer age;
private String tel;
@TableField(exist=false)
private Integer online;
@TableLogic(value="0",delval="1")
//value为正常数据的值,delval为删除数据的值
private Integer deleted;
}
Step 3: Run the delete method
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testDelete(){
userDao.deleteById(1L);
}
}
The last step of logic deletion is the update operation, which will modify the specified field to the value corresponding to the deletion status.
think
Does logical deletion affect the query?
-
Execute query operation
@SpringBootTest class Mybatisplus03DqlApplicationTests { @Autowired private UserDao userDao; @Test void testFind(){ System.out.println(userDao.selectList(null)); } }
It is conceivable that the logical deletion of MP will add an undeleted condition to all queries, that is, the deleted data should not be queried.
-
If you still want to query all the deleted data, how to achieve it?
@Mapper public interface UserDao extends BaseMapper<User> { //查询所有数据包含已经被删除的数据 @Select("select * from tbl_user") public List<User> selectAll(); }
-
@TableLogic
If each table needs to have logical deletion, then it is necessary to add annotations to the attributes of each model class , how to optimize?Add global configuration in the configuration file, as follows:
mybatis-plus: global-config: db-config: # 逻辑删除字段名 logic-delete-field: deleted # 逻辑删除字面值:未删除为0 logic-not-delete-value: 0 # 逻辑删除字面值:删除为1 logic-delete-value: 1
Knowledge point 1: @TableLogic
name | @TableLogic |
---|---|
type | attribute annotation |
Location | above the attribute definition in the model class used to represent the field to delete |
effect | Identify the field as a field for logical deletion |
related attributes | value: Logical undeleted value delval: Logical deleted value |
3.4 Optimistic locking
3.4.1 Concept
Problems caused by business concurrency:spike
- If there are 100 products or tickets on sale, in order to ensure that each product or ticket can only be purchased by one person, how to ensure that there will be no overbought or repeated sales
- For this type of problem, there are actually many solutions that can be used
- The first thing that comes to mind is the lock. Locking in one server can be solved, but if it is locked in multiple servers, there is no way to control it. For example, there are two servers selling tickets in 12306. If all locks are added, it may also cause two threads to sell tickets at the same time, or there will be concurrency problems
- The method we will introduce next is a solution for small businesses, because the performance of the database itself is a bottleneck, and if its concurrency exceeds 2000, other solutions need to be considered.
To put it simply, the main problem solved by optimistic locking is that when a record is to be updated, it is hoped that this record has not been updated by others.
3.4.2 Implementation ideas
The implementation of optimistic locking:
- Add a version column to the database table, for example, the default value is 1
- Before the first thread wants to modify the data, get the version=1 in the current database when fetching the records
- Before the second thread wants to modify the data, get the version=1 in the current database when fetching the records
- When the first thread performs an update, set version = newVersion where version = oldVersion
- newVersion = version+1 [2]
- oldVersion = version [1]
- When the second thread performs the update, set version = newVersion where version = oldVersion
- newVersion = version+1 [2]
- oldVersion = version [1]
- If both threads update data, both the first and second threads may execute first
- If the first thread performs the update first, it will change the version to 2,
- When the second thread updates again, set version = 2 where version = 1, at this time the data version of the database table is already 2, so the second thread will fail to modify
- If the second thread performs the update first, it will change the version to 2,
- When the first thread updates again, set version = 2 where version = 1. At this time, the data version of the database table is already 2, so the first thread will fail to modify
- No matter who executes first, it will ensure that only one thread can update data. This is the analysis of the implementation principle of optimistic lock provided by MP.
3.4.3 Implementation steps
After analyzing the steps, the specific implementation steps are as follows:
Step 1: Add column to database table
The column name can be arbitrary, such as using version
, set the default value for the column1
Step 2: Add corresponding attributes to the model class
According to the added field column name, add the corresponding attribute value in the model class
@Data
//@TableName("tbl_user") 可以不写是因为配置了全局配置
public class User {
@TableId(type = IdType.ASSIGN_UUID)
private String id;
private String name;
@TableField(value="pwd",select=false)
private String password;
private Integer age;
private String tel;
@TableField(exist=false)
private Integer online;
private Integer deleted;
@Version
private Integer version;
}
Step 3: Add an interceptor for optimistic locking
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mpInterceptor() {
//1.定义Mp拦截器
MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
//2.添加乐观锁拦截器
mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mpInterceptor;
}
}
Step 4: Perform update operation
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testUpdate(){
User user = new User();
user.setId(3L);
user.setName("Jock666");
userDao.updateById(user);
}
}
You will find that this modification does not update the version field, because the version data is not carried.
Add version data
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testUpdate(){
User user = new User();
user.setId(3L);
user.setName("Jock666");
user.setVersion(1);
userDao.updateById(user);
}
}
You will find that what we pass is 1, and MP will add 1 to it, and then update it back to the database table.
So in order to achieve optimistic locking, the first step should be to get the version in the table, and then use the version as a condition to add 1 to the version and update it back to the database table, so when we query, we need to query it
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testUpdate(){
//1.先通过要修改的数据id将当前数据查询出来
User user = userDao.selectById(3L);
//2.将要修改的属性逐一设置进去
user.setName("Jock888");
userDao.updateById(user);
}
}
After roughly analyzing the implementation steps of optimistic locking, let's simulate a locking situation to see if it can be realized that when multiple people modify the same data, only one person can modify it successfully.
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testUpdate(){
//1.先通过要修改的数据id将当前数据查询出来
User user = userDao.selectById(3L); //version=3
User user2 = userDao.selectById(3L); //version=3
user2.setName("Jock aaa");
userDao.updateById(user2); //version=>4
user.setName("Jock bbb");
userDao.updateById(user); //verion=3?条件还成立吗?
}
}
Official documentation:
https://mp.baomidou.com/guide/interceptor-optimistic-locker.html#optimisticlockerinnerinterceptor
4. Rapid development
4.1 Code generator
Step 1: Create a Maven project
Step 2: Import the corresponding jar package
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.1</version>
</parent>
<groupId>com.yyds</groupId>
<artifactId>mybatisplus_04_generator</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--spring webmvc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatisplus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<!--代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<!--velocity模板引擎-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Step 3: Write the bootstrap class
@SpringBootApplication
public class Mybatisplus04GeneratorApplication {
public static void main(String[] args) {
SpringApplication.run(Mybatisplus04GeneratorApplication.class, args);
}
}
Step 4: Create Code Generation Classes
public class CodeGenerator {
public static void main(String[] args) {
//1.获取代码生成器的对象
AutoGenerator autoGenerator = new AutoGenerator();
//设置数据库相关配置
DataSourceConfig dataSource = new DataSourceConfig();
dataSource.setDriverName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("root");
autoGenerator.setDataSource(dataSource);
//设置全局配置
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setOutputDir(System.getProperty("user.dir")+"/mybatisplus_04_generator/src/main/java"); //设置代码生成位置
globalConfig.setOpen(false); //设置生成完毕后是否打开生成代码所在的目录
globalConfig.setAuthor("yyds"); //设置作者
globalConfig.setFileOverride(true); //设置是否覆盖原始生成的文件
globalConfig.setMapperName("%sDao"); //设置数据层接口名,%s为占位符,指代模块名称
globalConfig.setIdType(IdType.ASSIGN_ID); //设置Id生成策略
autoGenerator.setGlobalConfig(globalConfig);
//设置包名相关配置
PackageConfig packageInfo = new PackageConfig();
packageInfo.setParent("com.aaa"); //设置生成的包名,与代码所在位置不冲突,二者叠加组成完整路径
packageInfo.setEntity("domain"); //设置实体类包名
packageInfo.setMapper("dao"); //设置数据层包名
autoGenerator.setPackageInfo(packageInfo);
//策略设置
StrategyConfig strategyConfig = new StrategyConfig();
strategyConfig.setInclude("tbl_user"); //设置当前参与生成的表名,参数为可变参数
strategyConfig.setTablePrefix("tbl_"); //设置数据库表的前缀名称,模块名 = 数据库表名 - 前缀名 例如: User = tbl_user - tbl_
strategyConfig.setRestControllerStyle(true); //设置是否启用Rest风格
strategyConfig.setVersionFieldName("version"); //设置乐观锁字段名
strategyConfig.setLogicDeleteFieldName("deleted"); //设置逻辑删除字段名
strategyConfig.setEntityLombokModel(true); //设置是否启用lombok
autoGenerator.setStrategy(strategyConfig);
//2.执行生成操作
autoGenerator.execute();
}
}
For the code content in the code generator, we can directly obtain the code from the official document for modification,
https://mp.baomidou.com/guide/generator.html
Step 5: Run the program
After running successfully, a lot of code will be generated in the current project, the code contains controller
, service
, mapper
andentity
4.2 CRUD of Service in MP
MP provides a Service interface and implementation class, respectively: IService
and ServiceImpl
, the latter is a concrete realization of the former.
In the future, the Service we wrote ourselves can be modified as follows:
public interface UserService extends IService<User>{
}
@Service
public class UserServiceImpl extends ServiceImpl<UserDao, User> implements UserService{
}
The advantage after the modification is that MP has helped us realize some basic additions, deletions, changes, and queries of the business layer, which can be used directly.
Write a test class for testing:
@SpringBootTest
class Mybatisplus04GeneratorApplicationTests {
private IUserService userService;
@Test
void testFindAll() {
List<User> list = userService.list();
System.out.println(list);
}
}
Note: The MyBatis environment is not configured in the mybatisplus_04_generator project. If you want to run it, you need to extract and improve the content in the configuration file before running it.
Thinking: What methods can be used in the Service layer encapsulated by MP?
Check out the official documentation:https://mp.baomidou.com/guide/crud-interface.html