MybatisPlus expansion

Tombstone

  • The operation of logical deletion is to add a field to indicate the state of this data. If a piece of data needs to be deleted, we can achieve it by changing the state of this data, which can not only indicate that this piece of data is deleted, but also retain the data for future statistics. .
  1. Add a column of fields to the table to indicate whether to delete or not. The field type used here is int type. 1 indicates that the data is available, and 0 indicates that the data is not available.
    insert image description here
  2. The entity class adds a field as Integer, which is used to correspond to the fields in the table
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User extends Model<User> {
          
          
        private Long id;
        private String name;
        private Integer age;
    	private String email;
        @TableLogic(value = "1",delval = "0")
        private Integer status;
    }
    
  3. Test tombstone effect
@Test
void logicDelete(){
    
    
    userMapper.deleteById(7L);
}

insert image description here
insert image description here

  • The effect of tombstone deletion can also be achieved through global configuration
    insert image description here

Generic enumeration

  • When you want to represent a set of information, this set of information can only be selected from some fixed values ​​and cannot be written at will. In this scenario, enumeration is very suitable.
  1. Add a field to the table to represent gender, and use int to describe, because the int type can represent two different genders through the two values ​​​​of 0 and 1
    insert image description here
  2. Write an enumeration class
public enum GenderEnum {
    
    

    MAN(0,"男"),
    WOMAN(1,"女");
    
	@EnumValue
    private Integer gender;
    private String genderName;

    GenderEnum(Integer gender, String genderName) {
    
    
        this.gender = gender;
        this.genderName = genderName;
    }
}
  1. Entity class to add related fields
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User extends Model<User> {
    
    
    private Long id;
    private String name;
    private Integer age;
    private String email;
    private GenderEnum gender;
    private Integer status;
}
  1. adding data
@Test
void enumTest(){
    
    
    User user = new User();
    user.setName("liu");
    user.setAge(29);
    user.setEmail("[email protected]");
    user.setGenderEnum(GenderEnum.MAN);
    user.setStatus(1);
    userMapper.insert(user);
}

insert image description here

field type handler

  • In some scenarios, in the entity class, the Map collection is used as an attribute to receive the data passed by the front end, but when these data are stored in the database, they are stored in json format. json is essentially a string, that is varchar type. Then how to convert between the Map type of the entity class and the varchar type of the database, here we need to use the field type processor to complete.
  1. Entity class
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User extends Model<User> {
          
          
        private Long id;
        private String name;
        private Integer age;
        private String email;
        private GenderEnum gender;
        private Integer status;
        private Map<String,String> contact;//联系方式
    }
    
  2. Add a field in the database, which is of type varchar
    insert image description here
  3. Add corresponding annotations to entity classes to implement different types of data conversion using field type processors
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @TableName(autoResultMap = true)//查询时将json字符串封装为Map集合
    public class User extends Model<User> {
          
          
        private Long id;
        private String name;
        private Integer age;
        private String email;
        private GenderEnum gender;
        private Integer status;
        @TableField(typeHandler = FastjsonTypeHandler.class)//指定字段类型处理器
        private Map<String,String> contact;//联系方式
    }
    
  4. The introduction of field type processors depends on Fastjson
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.76</version>
    </dependency>
    

autofill

  • There are some properties in the project, if we don't want to be filled every time, we can set it to auto-fill, such as common time, creation time and update time can be set to auto-fill.
  1. In the entity class, add the corresponding fields, and specify the filling timing for the attributes that need to be automatically filled
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName(autoResultMap = true)
public class User extends Model<User> {
    
    
    @TableId
    private Long id;
    private String name;
    private Integer age;
    private String email;
    private Integer status;
    private GenderEnum gender;
    @TableField(typeHandler = FastjsonTypeHandler.class)
    private Map<String,String> contact;
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
}
  1. Write an autofill processor, specify the fill strategy
    @Component
    public class MyMetaHandler implements MetaObjectHandler {
          
          
        @Override
        public void insertFill(MetaObject metaObject) {
          
          
            setFieldValByName("createTime",new Date(),metaObject);
            setFieldValByName("updateTime",new Date(),metaObject);
        }
    
        @Override
        public void updateFill(MetaObject metaObject) {
          
          
            setFieldValByName("updateTime",new Date(),metaObject);
        }
    }
    
  2. Set the mysql time zone and update the yml connection configuration
set GLOBAL time_zone='+8:00'
select NOW();

insert image description here

Anti-full table update and delete plugin

  • In actual development, full table update and deletion are very dangerous operations. In MybatisPlus, plug-ins are provided to prevent such dangerous operations from happening.
  • Implementation steps:
  1. Inject the MybatisPlusInterceptor class and configure the BlockAttackInnerInterceptor interceptor
@Configuration
public class MybatisPlusConfig {
    
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
    
    
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
        return interceptor;
    }
}

  1. When testing the update of the whole table, an exception will be thrown, which prevents the update of the whole table
@SpringBootTest
public class QueryTest {
    
    

    @Autowired
    private UserService userService;

	@Test
	void allUpdate(){
    
    
	    User user = new User();
	    user.setId(999L);
	    user.setName("wang");
	    user.setEmail("[email protected]");
	    userService.saveOrUpdate(user,null);
	}
}

insert image description here

MybatisX rapid development plug-in

plugin installation

  • MybatisX is a plug-in provided by IDEA, which is designed to simplify the Mybatis and MybatisPlus frameworks for us.
  • Install the plugin in IDEA
  1. choose firstFile -> Settings->Plugins
    insert image description here
  2. Search for MybatisX and click Install
    insert image description here
  3. Restart IDEA to make the plugin take effect, so far the MybatisX plugin is installed

  • After the plug-in is installed, let's take a look at the function of the plug-in
  1. Jump function of Mapper interface and mapping file
    insert image description here
    insert image description here

Reverse Engineering

  • Reverse engineering is to reversely generate the structure of the Java project through the database table structure, including the following points:
  1. Entity class
  2. Mapper interface
  3. Mapper mapping file
  4. Service interface
  5. Service implementation class
  • Implementation steps:
  1. First use IDEA to connect to mysql, fill in the connection information, and test the connection to pass
    insert image description here
    insert image description here
  2. Right click on the table and select the reverse engineering option of the plug-in
    insert image description here
  3. Write reverse engineering configuration information
    insert image description here
  4. Write build information
    insert image description here

Common Requirements Code Generation

  • Although the Mapper interface provides some common methods, we can directly use these common methods to complete sql operations, but it is still not enough for various complex operation requirements in actual scenarios, so MybatisX provides more method, and the corresponding sql statement can be directly generated according to these methods, which makes the development easier.
  • Common operations can be associated with the name
@Mapper
public interface UserMapper extends BaseMapper<User> {
    
    
         //添加操作
        int insertSelective(User user);

        //删除操作
        int deleteByNameAndAge(@Param("name") String name, @Param("age") Integer age);

        //修改操作
        int updateNameByAge(@Param("name") String name, @Param("age") Integer age);

        //查询操作
        List<User> selectAllByAgeBetween(@Param("beginAge") Integer beginAge, @Param("endAge") Integer endAge);
}
  • In the mapping configuration file, the corresponding sql will be generated, and we do not need to write
<insert id="insertSelective">
    insert into powershop_user
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="id != null">id,</if>
        <if test="name != null">name,</if>
        <if test="age != null">age,</if>
        <if test="email != null">email,</if>
    </trim>
    values
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="id != null">#{id,jdbcType=BIGINT},</if>
        <if test="name != null">#{name,jdbcType=VARCHAR},</if>
        <if test="age != null">#{age,jdbcType=INTEGER},</if>
        <if test="email != null">#{email,jdbcType=VARCHAR},</if>
    </trim>
</insert>

<delete id="deleteByNameAndAge">
    delete
    from powershop_user
    where name = #{name,jdbcType=VARCHAR}
      AND age = #{age,jdbcType=NUMERIC}
</delete>

<update id="updateNameByAge">
    update powershop_user
    set name = #{name,jdbcType=VARCHAR}
    where age = #{age,jdbcType=NUMERIC}
</update>

<select id="selectAllByAgeBetween" resultMap="BaseResultMap">
    select
    <include refid="Base_Column_List"/>
    from powershop_user
    where
    age between #{beginAge,jdbcType=INTEGER} and #{endAge,jdbcType=INTEGER}
</select>

optimistic lock

problem introduction

  • Concurrent requests are multiple requests at the same time requesting server resources at the same time. If it is to obtain information, there is no problem, but if it is to modify the information, then there will be problems.
  • For example, there is only 1 item left in the inventory of the product. At this time, multiple users want to buy this product, and they all initiate a request to purchase the product. So it is definitely not possible to allow these multiple users to buy it. Because multiple users have bought this product, there will be an oversold problem, and the product cannot be shipped if the stock is not enough. Therefore, it is necessary to solve this overselling problem during development.
    insert image description here

Core problem: During the execution of a request, other requests cannot change the data. If it is a complete request, and other requests do not modify the data during the request process, then the request can modify the data normally. If other requests have already changed the data in the process of changing the data, the request will not change the data
insert image description here

  • To solve this kind of problem, the most common idea is to add a lock. The lock can be used to verify whether any data has changed during the execution of the request.

  • There are two common types of database locks, pessimistic locks and optimistic locks. The one-time modification operation is to first query the data and then modify the data.

    • Pessimistic lock: Pessimistic lock is to lock the data when querying, and the lock will not be released until the request is completed. After this request is completed, the lock is released. After the lock is released, other requests can complete reading and writing of this data.
    • The advantages and disadvantages of pessimistic locking: it can ensure that the read information is the current information, ensuring the correctness of the information, but the concurrency efficiency is very low.
    • There are very few scenarios where pessimistic locks are used in actual development, because we need to ensure efficiency during concurrency.
    • Optimistic locking: Optimistic locking is designed through table fields. The core idea is that the data is not locked when reading, and other requests can still read the data. When modifying, it is judged whether a data has been modified. If If it has been modified, the modification operation requested this time will be invalid.
  • Specifically through sql is to achieve

Updateset 字段 = 新值,version = version + 1 where version = 1
  • The operation in this way will not affect the data reading, and the efficiency of concurrency is high. However, the data currently seen may not be real information data, it is before it is modified, but it is tolerable in many scenarios and does not have a great impact.

The use of optimistic locking

  1. Add a field version in the database table, indicating the version, the default value is 1
    insert image description here
  2. Find the entity class, add the corresponding attribute, and use @Version to mark it as an optimistic lock field information
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    
    
    private Long id;
    private String name;
    private Integer age;
    private String email;
    @Version
    private Integer version;
}

  1. Through the configuration of the interceptor, the function of version control is added to each modified SQL statement when it is executed.
@Configuration
public class MybatisPlusConfig {
    
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
    
    
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}

Effect test

  • Next, simulate whether the effect of optimistic locking can be achieved when there are multiple modification requests.
  • The effect of optimistic locking is that a request is allowed to be queried by another request during the modification process, but the modification will be determined by whether the version number changes. If the version number changes, it proves that there has been a request to modify the data , then this modification will not take effect. If the version number has not changed, then complete the modification.

@Test
void updateTest2(){
    
    
    //模拟操作1的查询操作
    User user1 = userMapper.selectById(6L);

    //模拟操作2的查询操作
    User user2 = userMapper.selectById(6L);

    //模拟操作2的修改操作
    user2.setName("lisi");
    userMapper.updateById(user2);

    //模拟操作1的修改操作
    user1.setName("zhangsan");
    userMapper.updateById(user1);
}

  • code execution process
    1. Query for operation 1: the version is 2 at this time
      insert image description here
    2. Query for operation 2: the version is 2 at this time
      insert image description here
    3. Modification of operation 2: Check the version at this time, the version has not changed, so complete the modification and change the version to 3
      insert image description here
    4. Modification of operation 1: Check the version at this time, the version has changed from the originally obtained version information, so it is forbidden to modify
      insert image description here

Code generator

  • The difference between code generators and reverse engineering is that code generators can generate more structures, more content, and allow us to configure more options for generation.
  1. Introduce dependencies
    <!--代码生成器依赖-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-generator</artifactId>
        <version>3.5.3</version>
    </dependency>
    
    <!--freemarker模板依赖-->
    <dependency>
        <groupId>org.freemarker</groupId>
        <artifactId>freemarker</artifactId>
        <version>2.3.31</version>
    </dependency>
    
  2. Write code generator code
    • Example one:
    public class CodeGenerator {
          
          
    	/*
    	SELECT table_name
          FROM information_schema.tables
          WHERE table_schema = 'xxx'
          ORDER BY table_name DESC;
    	*/
    
        public static String scanner(String tip) {
          
          
            Scanner scanner = new Scanner(System.in);
            StringBuilder help = new StringBuilder();
            help.append("请输入" + tip + ":");
            System.out.println(help.toString());
            if (scanner.hasNext()) {
          
          
                String ipt = scanner.next();
                if (!ipt.equals("")) {
          
          
                    return ipt;
                }
            }
            throw new MybatisPlusException("请输入正确的" + tip + "!");
        }
    
        public static void main(String[] args) {
          
          
            // 代码生成器
            AutoGenerator mpg = new AutoGenerator();
    
            // 全局配置
            GlobalConfig gc = new GlobalConfig();
            String projectPath = System.getProperty("user.dir");
            gc.setOutputDir(projectPath + "/src/main/java");//设置代码生成路径
            gc.setFileOverride(true);//是否覆盖以前文件
            gc.setOpen(false);//是否打开生成目录
            gc.setAuthor("xxx");//设置项目作者名称
            gc.setIdType(IdType.AUTO);//设置主键策略
            gc.setBaseResultMap(true);//生成基本ResultMap
            gc.setBaseColumnList(true);//生成基本ColumnList
            gc.setServiceName("%sService");//去掉服务默认前缀
            gc.setDateType(DateType.ONLY_DATE);//设置时间类型
            mpg.setGlobalConfig(gc);
    
            // 数据源配置
            DataSourceConfig dsc = new DataSourceConfig();
            dsc.setUrl("jdbc:mysql://localhost:3306/care_home?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8");
            dsc.setDriverName("com.mysql.cj.jdbc.Driver");
            dsc.setUsername("root");
            dsc.setPassword("xxx");
            mpg.setDataSource(dsc);
    
            // 包配置
            PackageConfig pc = new PackageConfig();
            pc.setParent("com.test");
            pc.setMapper("com/test/mapper");
            pc.setXml("mapper.xml");
            pc.setEntity("com/test/pojo");
            pc.setService("com/test/service");
            pc.setServiceImpl("service.impl");
            pc.setController("com/test/controller");
            mpg.setPackageInfo(pc);
    
            // 策略配置
            StrategyConfig sc = new StrategyConfig();
            sc.setNaming(NamingStrategy.underline_to_camel);
            sc.setColumnNaming(NamingStrategy.underline_to_camel);
            sc.setEntityLombokModel(true); //自动lombok
            sc.setRestControllerStyle(true);
            sc.setControllerMappingHyphenStyle(true);
    
            sc.setLogicDeleteFieldName("deleted");//设置逻辑删除
    
            //设置自动填充配置
            TableFill gmt_create = new TableFill("create_time", FieldFill.INSERT);
            TableFill gmt_modified = new TableFill("update_time", FieldFill.INSERT_UPDATE);
            ArrayList<TableFill> tableFills=new ArrayList<>();
            tableFills.add(gmt_create);
            tableFills.add(gmt_modified);
            sc.setTableFillList(tableFills);
    
            //乐观锁
            sc.setVersionFieldName("version");
            sc.setRestControllerStyle(true);//驼峰命名
    
            //  sc.setTablePrefix("tbl_"); 设置表名前缀
            sc.setInclude(scanner("表名,多个英文逗号分割").split(","));
            mpg.setStrategy(sc);
    
            // 生成代码
            mpg.execute();
        }
    
    }
    
    • Example two:
/**
 * @author 缘友一世
 * date 2023/7/19-16:09
 */
public class CodeGenerator {
    
    
    public static void main(String[] args) {
    
    
        String projectPath = System.getProperty("user.dir"); // 获取当前项目的绝对路径
        String MainPath=projectPath+"/src/main/java";
        String url="jdbc:mysql://localhost:3306/db2?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false";
        String username="root";
        String password="xxx";
        String author="yang";
        String moduleName="system";
        String mapperLocation=projectPath+"/src/main/resources/mapper/"+moduleName;
        String parentPackageName="com.yang";
        /*
        CREATE TABLE x_user (
            id int(11) NOT NULL AUTO_INCREMENT,
            username varchar(50) NOT NULL ,
            password varchar(100) DEFAULT NULL,
            email varchar(50) DEFAULT NULL,
            phone varchar(20) DEFAULT NULL,
            status int(1) DEFAULT NULL,
            avatar varchar(200) DEFAULT NULL,
            deleted INT(1) DEFAULT 0,
            PRIMARY KEY (id)
        )ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

        DELETE FROM x_user
        WHERE id > 20;
         */
        /*
        * SELECT table_name
          FROM information_schema.tables
          WHERE table_schema = 'xxx'
          ORDER BY table_name DESC; */
        String tables="x_user";
        FastAutoGenerator.create(url, username, password)
                .globalConfig(builder -> {
    
    
                    builder.author(author) // 设置作者
                            //.enableSwagger() // 开启 swagger 模式
                            //.fileOverride() // 覆盖已生成文件
                            .outputDir(MainPath); // 指定输出目录
                })
                .dataSourceConfig(builder -> builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {
    
    
                    int typeCode = metaInfo.getJdbcType().TYPE_CODE;
                    if (typeCode == Types.SMALLINT) {
    
    
                        // 自定义类型转换
                        return DbColumnType.INTEGER;
                    }
                    return typeRegistry.getColumnType(metaInfo);

                }))
                .packageConfig(builder -> {
    
    
                    builder.parent(parentPackageName) // 设置父包名
                            .moduleName(moduleName) // 设置父包模块名
                            .pathInfo(Collections.singletonMap(OutputFile.xml, mapperLocation)); // 设置mapperXml生成路径
                })
                .strategyConfig(builder -> {
    
    
                    builder.addInclude(tables) // 设置需要生成的表名
                            .addTablePrefix("x_"); // 设置过滤表前缀
                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();
    }

}

Execute SQL analysis print

  • You can use the SQL analysis and printing function provided by MybatisPlus to obtain the execution time of the SQL statement.
  1. Since this function depends on the p6spy component, it needs to be introduced in pom.xml first
    <dependency>
        <groupId>p6spy</groupId>
        <artifactId>p6spy</artifactId>
        <version>3.9.1</version>
    </dependency>
    
  2. Configure in application.yml
spring:
  datasource:
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver
    url: jdbc:p6spy:mysql
  1. Under resources, create a spy.properties configuration file
#3.2.1以上使用modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory

# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger

#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger

# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger

# 设置 p6spy driver 代理
deregisterdrivers=true

# 取消JDBC URL前缀
useprefix=true

# 配置记录 Log 例外,可去掉的结果集error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset

# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss

# 实际驱动可多个
#driverlist=org.h2.Driver

# 是否开启慢SQL记录
outagedetection=true

# 慢SQL记录标准 2 秒
outagedetectioninterval=2

  1. Test: Execute all operations of the query, and you can see the execution time of the sql statement
    insert image description here

multiple data sources

  • Sub-database and sub-table: When the data in the database of a project is very large, more data needs to be retrieved when completing SQL operations, and we will encounter performance problems and low SQL execution efficiency.
  • In response to this problem, our solution is to split the data in one database into multiple databases, thereby reducing the amount of data in a single database, from the two aspects of apportioning the pressure of access requests and reducing the amount of data in a single database, All improve efficiency.

  • In MybatisPlus, how to demonstrate the effect of data source switching
  1. First create a new module and copy the contents of the previous module
    insert image description here
  2. Introduce dependencies
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.1.0</version>
</dependency>
  1. Create a new database and provide a multi-data source environment
    insert image description here
    insert image description here
  2. Write a configuration file to specify multiple data source information
spring:
  datasource:
    dynamic:
      primary: master
      strict: false
      datasource:
        master:
          username: root
          password: xxx
          url: jdbc:mysql://localhost:3306/mybatisplus?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
          driver-class-name: com.mysql.cj.jdbc.Driver
        slave_1:
          username: root
          password: xxx
          url: jdbc:mysql://localhost:3306/mybatisplus2?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
          driver-class-name: com.mysql.cj.jdbc.Driver


  1. Create multiple services and use @DS annotations to describe different data source information
@Service
@DS("master")
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {
    
    
}
@Service
@DS("slave_1")
public class UserServiceImpl2 extends ServiceImpl<UserMapper, User> implements UserService{
    
    
}
  1. Test the execution results of the service multi-data source environment
@SpringBootTest
class Mp03ApplicationTests {
    
    

    @Autowired
    private UserServiceImpl userServiceImpl;

    @Autowired
    private UserServiceImpl2 userServiceImpl2;

    @Test
    public void select(){
    
    
        User user = userServiceImpl.getById(1L);
        System.out.println(user);
    }

    @Test
    public void select2(){
    
    
        User user = userServiceImpl2.getById(1L);
        System.out.println(user);
    }
}

  1. Observe the test results and find that the results can be obtained from two data sources
    insert image description here
    insert image description here

Guess you like

Origin blog.csdn.net/yang2330648064/article/details/131999067