The notes are arranged in the crazy God's mybatis plus
Mybatis plus study notes
1. Introduction
MyBatis plus (MP for short) is an enhancement tool of MyBatis. On the basis of MyBatis, it only makes enhancement without change, and is born to simplify development and improve efficiency.
1.1 characteristics
- No invasion: it is only enhanced without change, and its introduction will not affect the existing project, which is as smooth as silk
- Low loss: the basic CURD will be injected automatically upon startup, with basically no loss of performance and direct object-oriented operation
- Powerful crud operation: built-in general Mapper and general Service. Most CRUD operations of a single table can be realized only through a small number of configurations. There is also a powerful condition constructor to meet various use needs
- Support Lambda form call: through Lambda expression, it is convenient to write various query conditions without worrying about wrong fields
- Support automatic generation of primary key: support up to 4 primary key strategies (including distributed unique ID generator - Sequence), which can be configured freely to perfectly solve the problem of primary key
- Support ActiveRecord mode: support ActiveRecord formal calls. Entity classes only need to inherit Model classes to perform powerful CRUD operations
- Support custom global general operations: support global general method injection (Write once, use anywhere)
- Built in code generator: code or Maven plug-in can be used to quickly generate Mapper, Model, Service and Controller layer code, support template engine, and have more custom configurations for you to use
- Built in paging plug-in: Based on MyBatis physical paging, developers do not need to care about specific operations. After configuring the plug-in, writing paging is equivalent to ordinary List query
- The paging plug-in supports multiple databases: MySQL, MariaDB, Oracle, DB2, H2, HSQL, SQLite, Postgre, SQLServer and other databases
- Built in performance analysis plug-in: it can output Sql statements and their execution time. It is recommended to enable this function during development and testing to quickly find out slow queries
- Built in global interception plug-in: it provides intelligent analysis and blocking of full table delete and update operations, and can also customize interception rules to prevent misoperation
1.2. Support database
Any database that can use mybatis for CRUD and supports standard sql
1.3 frame structure
2. Quick start
2.1. Create database
CREATE DATABASE mybatis_plus;
2.2. Create data table
DROP TABLE IF EXISTS user; CREATE TABLE user ( id BIGINT(20) NOT NULL COMMENT 'Primary key ID', name VARCHAR(30) NULL DEFAULT NULL COMMENT 'full name', age INT(11) NULL DEFAULT NULL COMMENT 'Age', email VARCHAR(50) NULL DEFAULT NULL COMMENT 'mailbox', PRIMARY KEY (id) ); INSERT INTO user (id, name, age, email) VALUES (1, 'Jone', 18, 'test1@baomidou.com'), (2, 'Jack', 20, 'test2@baomidou.com'), (3, 'Tom', 28, 'test3@baomidou.com'), (4, 'Sandy', 21, 'test4@baomidou.com'), (5, 'Billie', 24, 'test5@baomidou.com');
2.3. Create project
Create an empty Spring Boot project
2.4. Import dependency
<!--Database driven--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.20</version> </dependency> <!--mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.0.5</version> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
Note: we can save a lot of code by using mybatis plus. Try not to import mybatis and mybatis plus at the same time!
2.5. Connect to the database
# mysql spring.datasource.username=root spring.datasource.password=root spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?userSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
2.6 coding
Traditional method: POJO Dao (connect to Mybatis and configure mapper XML file) - service controller
After using mybatis plus
-
pojo
@Data @AllArgsConstructor @NoArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; }
-
mapper interface
// Inherit the basic interface BaseMapper on the corresponding Mapper @Repository // Represents the persistence layer public interface UserMapper extends BaseMapper<User> { // All CRUD operations have been basically completed // You don't need a lot of configuration files like before }
-
Add the @ MapperScan annotation in the Spring Boot startup class and scan the Mapper folder
@SpringBootApplication @MapperScan("com.thomas.mybatisplus.mapper") public class Application { public static void main(String[] args) { SpringApplication.run(QuickStartApplication.class, args); } }
-
use
@SpringBootTest class MybatisPlusApplicationTests { // It inherits BaseMapper, and all methods come from the parent class // We can also write our own extension methods @Autowired UserMapper userMapper; @Test void contextLoads() { // The parameter is a Wrapper and condition constructor. We don't need null here // Query all users List<User> users = userMapper.selectList(null); users.forEach(System.out::println); } }
-
console output
User(id=1, name=Jone, age=18, email=test1@baomidou.com) User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=3, name=Tom, age=28, email=test3@baomidou.com) User(id=4, name=Sandy, age=21, email=test4@baomidou.com) User(id=5, name=Billie, age=24, email=test5@baomidou.com)
Thinking questions:
1. Who wrote SQL for us? Mybatis plus is all written
2. Where did the method come from? Mybatis plus is all written
3. Configuration log
All our sql is invisible now. We want to know how it is executed, so we must look at the log!
# Configuration log (self-contained, console output) mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
4,CRUD
4.1. Insertion operation
@Test public void testInsert(){ User user = new User(); user.setName("thomas"); user.setAge(20); user.setEmail("test6@baomidou.com"); int result = userMapper.insert(user);// Help us generate id automatically System.out.println(result);// Number of rows affected System.out.println(user);// Discovery: id auto backfill }
The default value of the ID inserted in the database is the global unique ID (ID_WORKER)
4.2. Primary key generation strategy
Generation of unique id of distributed system
- Snowflake algorithm:
- snowflake is Twitter's open source distributed ID generation algorithm, and the result is a long ID. Its core idea is to use 41bit as the number of milliseconds, 10bit as the machine ID (five bits are the data center and five bit machine ID), 12bit as the serial number within milliseconds (meaning that each node can generate 4096 IDS per millisecond), and finally there is a symbol bit, which is always 0. Can guarantee almost the only one in the world!
- We need to configure primary key auto increment:
- @ TableId(type = IdType.AUTO) on entity class field
- The database field must be self incremented
Other attributes:
public enum IdType { AUTO(0),//Self increment of database ID NONE(1),//The type is not set with primary key INPUT(2),//The user enters the ID, which can be filled by registering the auto fill plug-in //The following three types are automatically filled only when the inserted object ID is empty. ID_WORKER(3),//Globally unique ID (idWorker) UUID(4),//Globally unique ID (UUID) ID_WORKER_STR(5);//String globally unique ID (string representation of idworker) }
4.3. Update operation
@Test public void testUpdate(){ User user = new User(); // Automatic splicing of dynamic sql through conditions user.setId(6L); user.setName("thomas1"); user.setAge(18); // Note: updateById, but the parameter is an object int i = userMapper.updateById(user); System.out.println(i); }
4.3. Automatic filling
Creation time and modification time! These operations are generally completed automatically. We don't want to update them manually!
Alibaba Development Manual: all database tables: gmt_create,gmt_modified almost all tables should be configured! And need automation!
Method 1: database level (database modification is not allowed during work)
-
Add a new field create in the table_ time,update_ time
- The default value of the field is set to current_ Only MySQL update 7.5 and later_ Time settings are updated automatically.
-
To test the insertion method again, we need to synchronize the entity class first
private Date createTime; private Date updateTime;
-
Just review the update results again
Method 2: code level
-
Delete default values and update operations in the database
-
Annotation needs to be added to the field attribute of the entity class
//Add padding to field @TableField(fill = FieldFill.INSERT) private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime;
public enum FieldFill { /** * Do not process by default */ DEFAULT, /** * Insert fill field */ INSERT, /** * Update fill field */ UPDATE, /** * Insert and update populated fields */ INSERT_UPDATE }
-
Just write a processor to handle this annotation
@Slf4j @Component // Don't forget to add the processor to the IOC container public class MyMetaObjectHandler implements MetaObjectHandler { // Filling strategy during insertion @Override public void insertFill(MetaObject metaObject) { log.info("start insert fill ..."); // setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject) this.setFieldValByName("createTime", new Date(), metaObject); this.setFieldValByName("updateTime", new Date(), metaObject); } // Population policy when updating @Override public void updateFill(MetaObject metaObject) { log.info("start update fill ..."); this.setFieldValByName("updateTime", new Date(), metaObject); } }
-
Test insertion
-
Test update, observation time
4.4 Le Guan lock
- Optimistic lock: hence the name Siyi is very optimistic. It always thinks that there will be no problem. No matter what you do, don't lock it! If there is a problem, update the test value again.
- Pessimistic lock: so it is very pessimistic. It always thinks that there is always a problem. No matter what you do, you will lock it and operate it again.
Optimistic lock implementation method:
- When fetching the record, get the current version.
- When updating, bring this version.
- When updating, set version = newVersion where version = oldVersion.
- If the version is wrong, the update fails.
-- Optimistic lock: 1. Query first to obtain the version number version = 1 -- A update user set name = "kuangshen", version = version + 1 where id = 2 and version = 1 -- B: The thread finishes first. At this time version = 2,Will lead to A Modification failed! update user set name = "kuangshen", version = version + 1 where id = 2 and version = 1
Optimistic lock plug-in for MyBatisPlus
-
Add the version field to the database.
-
Add the corresponding field to the entity class and mark it with @ Version
@Version // Optimistic lock Version annotation private Integer version;
-
Register components
//Scan our mapper folder @MapperScan("com.kuang.mapper") @EnableTransactionManagement @Configuration // Configuration class public class MyBatisPlusConfig { // Register optimistic lock plug-in @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor(){ return new OptimisticLockerInterceptor(); } }
-
test
// Test optimistic lock successful @Test public void testOptimisticLocker(){ // 1. Query user information User user = userMapper.selectById(1L); // 2. Modify user information user.setName("xxx"); user.setEmail("3419339201@qq.com"); userMapper.updateById(user); } // Failed to test optimistic lock, in multi-threaded environment! @Test public void testOptimisticLocker2(){ // Thread 1 User user = userMapper.selectById(1L); user.setName("user1"); user.setEmail("2439115397@qq.com"); // Simulate another thread to perform queue jumping User user2 = userMapper.selectById(1L); user2.setName("user2"); user2.setEmail("2953887629@qq.com"); userMapper.updateById(user2); userMapper.updateById(user); // If there is no optimistic lock, the value of queue jumping thread will be overwritten! }
4.5. Query operation
// Test query @Test public void testSelectById(){ User user = userMapper.selectById(1); System.out.println(user); } // Batch query @Test public void testSelectByBatchIds(){ List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3)); users.forEach(System.out::println); } // Use map according to one of the query criteria @Test public void testSelectByMap(){ HashMap<String, Object> map = new HashMap<>(); // Custom query map.put("name","thomas"); map.put("age","20"); List<User> users = userMapper.selectByMap(map); users.forEach(System.out::println); }
4.6 paging query
- Paging the original limit
- pageHelper third party plug-in
- MyBatisPlus actually has a built-in paging plug-in
Use the paging plug-in provided by MyBatisPlus
-
Configuring the paging interceptor component
@Bean public PaginationInterceptor paginationInterceptor(){ return new PaginationInterceptor(); }
-
You can directly use the Page object provided by MyBatisPlus
// Test paging plug-in @Test public void testSelectPage(){ // Parameter 1: current page // Parameter 2: page size Page<User> page = new Page<>(1,5); userMapper.selectPage(page,null); List<User> records = page.getRecords(); records.forEach(System.out::println); System.out.println(page.getTotal()); //Total records }
4.7. Delete
// Delete by id @Test public void testDeleteById(){ int result = userMapper.deleteById(1L); System.out.println(result); } // Batch deletion based on id set @Test public void testDeleteBatchIds(){ int result = userMapper.deleteBatchIds(Arrays.asList(2, 3, 4)); System.out.println(result); } // Delete according to map as condition @Test public void test(){ Map<String,Object> map = new HashMap<>(); map.put("name","thomas"); map.put("age",20); int result = userMapper.deleteByMap(map); System.out.println(result); }
4.8. Logical deletion
- Physical delete: remove directly from the database.
- Logical deletion: it is not removed in the database, but invalidated by a variable, deleted = 0 = > deleted = 1.
Logical deletion can prevent data loss, similar to the recycle bin.
test
-
Add a deleted field in the data table. The default value is 0
-
Add corresponding attributes to the entity class and use @ TableLogic
@TableLogic // Logical deletion annotation private Integer deleted;
-
Configure logical deletion
// Register logical deletion plug-in @Bean public ISqlInjector iSqlInjector(){ return new LogicSqlInjector(); }
# Configure logical deletion mybatis-plus.global-config.db-config.logic-delete-value=1 mybatis-plus.global-config.db-config.logic-not-delete-value=0
-
The test deletion is an update operation rather than a deletion operation. The record is still in the database, but the corresponding deleted value is modified to 1
-
Execute the query test and automatically filter the logically deleted fields when the query is found (the query statement will automatically splice the condition of deleted = 0)
5. Performance analysis plug-in
Function: performance analysis interceptor, which is used to output each SQL statement and its execution time
MybatisPlus also provides a performance analysis plug-in. If it exceeds this time, it will stop running
use
-
Import plug-in
// Register SQL performance analysis plug-in @Bean @Profile({"dev","test"}) // Set the dev and test environment to start to ensure the running efficiency of the program public PerformanceInterceptor performanceInterceptor(){ PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor(); performanceInterceptor .setMaxTime(100); // Unit: milliseconds. Set the maximum time of sql execution. If it exceeds, it will not be executed performanceInterceptor.setFormat(true); // Format code return performanceInterceptor; }
Configure the environment in SpringBoot as dev or test environment.
# Setting up the development environment spring.profiles.active=dev
-
test
@Test public void contextLoads() { List<User> users = userMapper.selectList(null); for (User user : users) { System.out.println(user); } }
It is found that the log output from the console has been formatted, and an exception will be thrown as long as the SQL execution time exceeds the specified time!
Using the performance analysis plug-in can improve efficiency. The disassembly has been removed in the new version of MP, and druid can be used
6. Conditional constructor
Mybatis plus conditional constructor
We can write some complex SQL and use it instead
@Test public void test(){ QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.isNotNull("name") // Name cannot be empty .isNotNull("email") // Mailbox is not empty .ge("age",20); // Age 18 or older userMapper.selectList(wrapper).forEach(System.out::println); } @Test public void test2(){ QueryWrapper<User> wrapper = new QueryWrapper(); wrapper.eq("name","thomas"); //Name is Mike // Query a piece of data and use List or Map when multiple results appear User user = userMapper.selectOne(wrapper); System.out.println(user); } @Test public void test3(){ QueryWrapper<User> wrapper = new QueryWrapper(); wrapper.between("age",20,30); //The query age range is [20,30]: including the values at both ends Integer count = userMapper.selectCount(wrapper);// Number of query results System.out.println(count); } @Test public void test4(){ QueryWrapper<User> wrapper = new QueryWrapper(); wrapper.notLike("name","K") //The name does not include K .likeRight("name","C"); //The name ends in C List<Map<String, Object>> maps = userMapper.selectMaps(wrapper); maps.forEach(System.out::println); } @Test public void test5(){ QueryWrapper<User> wrapper = new QueryWrapper(); wrapper.inSql("id","select id from user where id < 3"); // id is found in the sub query List<Object> objects = userMapper.selectObjs(wrapper); objects.forEach(System.out::println); } @Test public void test6(){ QueryWrapper<User> wrapper = new QueryWrapper(); wrapper.orderByAsc("id"); // Sort ascending by id List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); }
7. Code generator
AutoGenerator is the code generator of mybatis plus. Through AutoGenerator, you can quickly generate the code of Entity, Mapper, Mapper XML, Service, Controller and other modules, which greatly improves the development efficiency.
-
rely on
<dependencies> <!-- web rely on --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- mybatis-plus rely on--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.1.tmp</version> </dependency> <!-- mybatis-plus Code generator dependency --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.3.1.tmp</version> </dependency> <!-- freemarker rely on --> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> </dependency> <!-- mysql rely on --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> </dependencies>
-
code
// Demo example: execute the main method, input the table name of the module on the console, and press enter to automatically generate the table name in the corresponding project directory public class CodeGenerator { /** * <p> * Read console content * </p> */ public static String scanner(String tip) { Scanner scanner = new Scanner(System.in); StringBuilder help = new StringBuilder(); help.append("Please enter" + tip + ": "); System.out.println(help.toString()); if (scanner.hasNext()) { String ipt = scanner.next(); if (StringUtils.isNotBlank(ipt)) { return ipt; } } throw new MybatisPlusException("Please enter the correct" + tip + "!"); } public static void main(String[] args) { // Code generator AutoGenerator mpg = new AutoGenerator(); // Global configuration GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty("user.dir"); gc.setOutputDir(projectPath + "/src/main/java"); gc.setAuthor("jobob"); gc.setOpen(false); // gc.setSwagger2(true); Entity attribute Swagger2 annotation mpg.setGlobalConfig(gc); // Data source configuration DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://localhost:3306/ant?useUnicode=true&useSSL=false&characterEncoding=utf8"); // dsc.setSchemaName("public"); dsc.setDriverName("com.mysql.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("password"); mpg.setDataSource(dsc); // Package configuration PackageConfig pc = new PackageConfig(); pc.setModuleName(scanner("Module name")); pc.setParent("com.baomidou.ant"); mpg.setPackageInfo(pc); // Custom configuration InjectionConfig cfg = new InjectionConfig() { @Override public void initMap() { // to do nothing } }; // If the template engine is freemaker String templatePath = "/templates/mapper.xml.ftl"; // If the template engine is velocity // String templatePath = "/templates/mapper.xml.vm"; // Custom output configuration List<FileOutConfig> focList = new ArrayList<>(); // Custom configuration will be output first focList.add(new FileOutConfig(templatePath) { @Override public String outputFile(TableInfo tableInfo) { // Customize the output file name. If you set the Prefix suffix for Entity, note that the name of xml will change!! return projectPath + "/src/main/resources/mapper/" + pc.getModuleName() + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; } }); /* cfg.setFileCreate(new IFileCreate() { @Override public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) { // Determine whether custom folders need to be created checkDir("The directory created by calling the default method, and the user-defined directory is marked with ""); if (fileType == FileType.MAPPER) { // The mapper file has been generated. It is judged that it exists. If you don't want to regenerate it, false will be returned return !new File(filePath).exists(); } // Allow template files to be generated return true; } }); */ cfg.setFileOutConfigList(focList); mpg.setCfg(cfg); // Configuration template TemplateConfig templateConfig = new TemplateConfig(); // Configure custom output templates //Specify the path of custom template. Be careful not to bring it with you ftl/.vm, which will be automatically identified according to the template engine used // templateConfig.setEntity("templates/entity2.java"); // templateConfig.setService(); // templateConfig.setController(); templateConfig.setXml(null); mpg.setTemplate(templateConfig); // Policy configuration StrategyConfig strategy = new StrategyConfig(); strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); strategy.setSuperEntityClass("Your own parent entity,No, you don't need to set it!"); strategy.setEntityLombokModel(true); strategy.setRestControllerStyle(true); // Public parent class strategy.setSuperControllerClass("Your own parent controller,No, you don't need to set it!"); // Public fields written in the parent class strategy.setSuperEntityColumns("id"); strategy.setInclude(scanner("Table name, separated by multiple English commas").split(",")); strategy.setControllerMappingHyphenStyle(true); strategy.setTablePrefix(pc.getModuleName() + "_"); mpg.setStrategy(strategy); mpg.setTemplateEngine(new FreemarkerTemplateEngine()); mpg.execute(); } }
Modify the content according to your own project