SpringBoot系列(二十九)MyBatisPlus高级功能

https://juejin.cn/post/6961721367846715428#heading-10

一、自动填充

1.1修改Mapper

在实体类中的某些字段上,通过@TableField设置自动填充

public class User2 {
	private Long id;
	private String name;
	private Integer age;
	private String email;
	private Long managerId;
	@TableField(fill = FieldFill.INSERT) // 插入时自动填充
	private LocalDateTime createTime;
	@TableField(fill = FieldFill.UPDATE) // 更新时自动填充
	private LocalDateTime updateTime;
	private Integer version;
	private Integer deleted;
}

1.2 实现自动填充处理器

在common/mybatis中创建类

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;

@Component //需要注册到Spring容器中
public class MyMetaObjectHandler implements MetaObjectHandler {

	@Override
	public void insertFill(MetaObject metaObject) {
        // 插入时自动填充
        // 注意第二个参数要填写实体类中的字段名称,而不是表的列名称
		strictFillStrategy(metaObject, "createTime", LocalDateTime::now);
	}

	@Override
	public void updateFill(MetaObject metaObject) {
        // 更新时自动填充
		strictFillStrategy(metaObject, "updateTime", LocalDateTime::now);
	}
}

1.3测试

注意,自动填充仅在该字段为空时会生效,若该字段不为空,则直接使用已有的值。如下

	@Test
	public void test() {
		User2 user = new User2();
		user.setId(8L);
		user.setName("王一蛋");
		user.setAge(29);
		user.setEmail("yd@baomidou.com");
		user.setManagerId(2L);
		user.setCreateTime(LocalDateTime.of(2000,1,1,8,0,0));
		mapper.insert(user);
	}

二、代码生成器

mp提供一个生成器,可快速生成Entity实体类,Mapper接口,Service,Controller等全套代码

2.1在SpringBoot单元测试中,创建一个类CodeGenerator


public class CodeGenerator {

    public static void main(String[] args) {
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 全局配置
        GlobalConfig globalConfig = new GlobalConfig();
        globalConfig
                .setAuthor("generator@TaleLin")
                .setOpen(false)
                .setFileOverride(false)
                .setIdType(IdType.AUTO)
                .setBaseResultMap(true)
                .setEntityName("%sDO")
                .setServiceName("%sService");
        mpg.setGlobalConfig(globalConfig);

        // 数据源配置
        DataSourceConfig dataSourceConfig = new DataSourceConfig();
        dataSourceConfig
                .setUrl("jdbc:mysql://localhost:3306/lin-cms?allowPublicKeyRetrieval=true&useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai")
                .setDriverName("com.mysql.cj.jdbc.Driver")
                .setUsername("root")
                .setPassword("123456");
        mpg.setDataSource(dataSourceConfig);

        // 包名配置
        PackageConfig packageConfig = new PackageConfig();
        packageConfig
                .setParent("io.github.talelin.latticy")
                .setPathInfo(getPathInfo())
                .setEntity("model")
                .setController("controller.v1")
                .setXml("xml");
        mpg.setPackageInfo(packageConfig);

        // 模板配置
        TemplateConfig templateConfig = new TemplateConfig();
        templateConfig
                .setEntity("/mpg/templates/entity.java")
                .setXml("/mpg/templates/mapper.xml")
                .setController("/mpg/templates/controller.java");
        mpg.setTemplate(templateConfig);

        // 策略配置
        StrategyConfig strategyConfig = new StrategyConfig();
        strategyConfig
                .setNaming(NamingStrategy.underline_to_camel)
                .setSuperEntityClass("io.github.talelin.latticy.model.BaseModel")
                .setTablePrefix("lin_")
                .setEntitySerialVersionUID(false)
                .setEntityLombokModel(true)
                .setRestControllerStyle(true)
                .setSuperEntityColumns("id", "create_time", "update_time", "delete_time")
                .setInclude(scanner("表名,多个英文逗号分割").split(","))
                .setControllerMappingHyphenStyle(true);
        mpg.setStrategy(strategyConfig);
        mpg.setTemplateEngine(new FreemarkerTemplateEngine());
        mpg.execute();
    }

    /**
     * 读取控制台内容
     */
    private static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入" + tip + ":");
        if (scanner.hasNext()) {
            String ipt = scanner.next();
            if (StringUtils.isNotBlank(ipt)) {
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的" + tip + "!");
    }

    private static Map<String, String> getPathInfo() {
        Map<String, String> pathInfo = new HashMap<>();
        pathInfo.put(ConstVal.ENTITY_PATH, System.getProperty("user.dir") + "/src/main/java/io/github/talelin/latticy/model");
        pathInfo.put(ConstVal.MAPPER_PATH, System.getProperty("user.dir") + "/src/main/java/io/github/talelin/latticy/mapper");
        pathInfo.put(ConstVal.SERVICE_PATH, System.getProperty("user.dir") + "/src/main/java/io/github/talelin/latticy/service");
        pathInfo.put(ConstVal.SERVICE_IMPL_PATH, System.getProperty("user.dir") + "/src/main/java/io/github/talelin/latticy/service/impl");
        pathInfo.put(ConstVal.CONTROLLER_PATH, System.getProperty("user.dir") + "/src/main/java/io/github/talelin/latticy/controller/v1");
        pathInfo.put(ConstVal.XML_PATH, System.getProperty("user.dir") + "/src/main/resources/mapper");
        return pathInfo;
    }
}

2.2创建模板

2.2.1创建mapper.xml.ftl

在单元测试rescoures/mpg/templates下创建文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="${package.Mapper}.${table.mapperName}">

<#if enableCache>
    <!-- 开启二级缓存 -->
    <cache type="org.mybatis.caches.ehcache.LoggingEhcache"/>

</#if>
<#if baseResultMap>
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="${package.Entity}.${entity}">
<#list table.fields as field>
<#if field.keyFlag><#--生成主键排在第一位-->
        <id column="${field.name}" property="${field.propertyName}" />
</#if>
</#list>
<#list table.commonFields as field><#--生成公共字段 -->
    <#if field.keyFlag>
        <id column="${field.name}" property="${field.propertyName}" />
    <#else>
        <result column="${field.name}" property="${field.propertyName}" />
    </#if>
</#list>
<#list table.fields as field>
<#if !field.keyFlag><#--生成普通字段 -->
        <result column="${field.name}" property="${field.propertyName}" />
</#if>
</#list>
    </resultMap>

</#if>
<#if baseColumnList>
    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
<#list table.commonFields as field>
        ${field.name},
</#list>
        ${table.fieldNames}
    </sql>

</#if>
</mapper>
2.2.2创建entity.java.ftl

在单元测试rescoures/mpg/templates下创建文件

package ${package.Entity};

<#list table.importPackages as pkg>
<#--子类变量包含id才导入相应的包-->
<#if (pkg =="com.baomidou.mybatisplus.annotation.IdType" || pkg =="com.baomidou.mybatisplus.annotation.TableId")>
    <#list table.fields as field>
        <#if field.keyFlag>
import ${pkg};
        </#if>
    </#list>
<#else>
import ${pkg};
</#if>
</#list>
<#if swagger2>
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
</#if>
<#if entityLombokModel>
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
</#if>

/**
<#if table.comment != "">
 * ${table.comment!}
 *
</#if>
 * @author ${author}
 * @since ${date}
 */
<#if entityLombokModel>
@Data
    <#if superEntityClass??>
@EqualsAndHashCode(callSuper = true)
    <#else>
@EqualsAndHashCode(callSuper = false)
    </#if>
@Accessors(chain = true)
</#if>
<#if table.convert>
@TableName("${table.name}")
</#if>
<#if swagger2>
@ApiModel(value="${entity}对象", description="${table.comment!}")
</#if>
<#if superEntityClass??>
public class ${entity} extends ${superEntityClass}<#if activeRecord><${entity}></#if> {
<#elseif activeRecord>
public class ${entity} extends Model<${entity}> {
<#else>
public class ${entity} implements Serializable {
</#if>

<#if entitySerialVersionUID>
    private static final long serialVersionUID = 1L;
</#if>
<#-- ----------  BEGIN 字段循环遍历  ---------->
<#list table.fields as field>
    <#if field.keyFlag>
        <#assign keyPropertyName="${field.propertyName}"/>
    </#if>

    <#if field.comment!?length gt 0>
        <#if swagger2>
    @ApiModelProperty(value = "${field.comment}")
        <#else>
    /**
     * ${field.comment}
     */
        </#if>
    </#if>
    <#if field.keyFlag>
        <#-- 主键 -->
        <#if field.keyIdentityFlag>
    @TableId(value = "${field.name}", type = IdType.AUTO)
        <#elseif idType??>
    @TableId(value = "${field.name}", type = IdType.${idType})
        <#elseif field.convert>
    @TableId("${field.name}")
        </#if>
        <#-- 普通字段 -->
    <#elseif field.fill??>
    <#-- -----   存在字段填充设置   ----->
        <#if field.convert>
    @TableField(value = "${field.name}", fill = FieldFill.${field.fill})
        <#else>
    @TableField(fill = FieldFill.${field.fill})
        </#if>
    <#elseif field.convert>
    @TableField("${field.name}")
    </#if>
    <#-- 乐观锁注解 -->
    <#if (versionFieldName!"") == field.name>
    @Version
    </#if>
    <#-- 逻辑删除注解 -->
    <#if (logicDeleteFieldName!"") == field.name>
    @TableLogic
    </#if>
    private ${field.propertyType} ${field.propertyName};
</#list>
<#------------  END 字段循环遍历  ---------->

<#if !entityLombokModel>
    <#list table.fields as field>
        <#if field.propertyType == "boolean">
            <#assign getprefix="is"/>
        <#else>
            <#assign getprefix="get"/>
        </#if>
    public ${field.propertyType} ${getprefix}${field.capitalName}() {
        return ${field.propertyName};
    }

    <#if entityBuilderModel>
    public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
    <#else>
    public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
    </#if>
        this.${field.propertyName} = ${field.propertyName};
        <#if entityBuilderModel>
        return this;
        </#if>
    }
    </#list>
</#if>

<#if entityColumnConstant>
    <#list table.fields as field>
    public static final String ${field.name?upper_case} = "${field.name}";

    </#list>
</#if>
<#if activeRecord>
    @Override
    protected Serializable pkVal() {
    <#if keyPropertyName??>
        return this.${keyPropertyName};
    <#else>
        return null;
    </#if>
    }

</#if>
<#if !entityLombokModel>
    @Override
    public String toString() {
        return "${entity}{" +
    <#list table.fields as field>
        <#if field_index==0>
            "${field.propertyName}=" + ${field.propertyName} +
        <#else>
            ", ${field.propertyName}=" + ${field.propertyName} +
        </#if>
    </#list>
        "}";
    }
</#if>
}
2.2.3controller.java.ftl

在单元测试rescoures/mpg/templates下创建文件

package ${package.Controller};


import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import ${package.Entity}.${entity};
import io.github.talelin.latticy.vo.CreatedVO;
import io.github.talelin.latticy.vo.DeletedVO;
import io.github.talelin.latticy.vo.PageResponseVO;
import io.github.talelin.latticy.vo.UpdatedVO;

import javax.validation.constraints.Min;
import javax.validation.constraints.Max;
import javax.validation.constraints.Positive;

<#if restControllerStyle>
import org.springframework.web.bind.annotation.RestController;
<#else>
import org.springframework.stereotype.Controller;
</#if>
<#if superControllerClassPackage??>
import ${superControllerClassPackage};
</#if>

/**
<#if table.comment != "">
 * ${table.comment!}前端控制器
 *
</#if>
* @author ${author}
* @since ${date}
*/
<#if restControllerStyle>
@RestController
<#else>
@Controller
</#if>
@RequestMapping("/${package.Controller?split(".")?last}<#if package.ModuleName?? && package.ModuleName != "">/${package.ModuleName}</#if>/<#if controllerMappingHyphenStyle??>${controllerMappingHyphen?replace("-do", "")}<#else>${table.entityPath?replace("DO", "")}</#if>")
<#if kotlin>
class ${table.controllerName}<#if superControllerClass??> : ${superControllerClass}()</#if>
<#else>
<#if superControllerClass??>
public class ${table.controllerName} extends ${superControllerClass} {
<#else>
public class ${table.controllerName} {
</#if>

    @PostMapping("")
    public CreatedVO create() {
        return new CreatedVO();
    }

    @PutMapping("/{id}")
    public UpdatedVO update(@PathVariable @Positive(message = "{id.positive}") Integer id) {
        return new UpdatedVO();
    }

    @DeleteMapping("/{id}")
    public DeletedVO delete(@PathVariable @Positive(message = "{id.positive}") Integer id) {
        return new DeletedVO();
    }

    @GetMapping("/{id}")
    public ${entity} get(@PathVariable(value = "id") @Positive(message = "{id.positive}") Integer id) {
        return null;
    }

    @GetMapping("/page")
    public PageResponseVO<${entity}> page(
            @RequestParam(name = "page", required = false, defaultValue = "0")
            @Min(value = 0, message = "{page.number.min}") Integer page,
            @RequestParam(name = "count", required = false, defaultValue = "10")
            @Min(value = 1, message = "{page.count.min}")
            @Max(value = 30, message = "{page.count.max}") Integer count
    ) {
        return null;
    }

}
</#if>
2.2.4创建 controller.java.ftl

在单元测试rescoures/mpg/templates/premium/controller.java.ftl 下创建文件

package ${package.Controller};


import io.github.talelin.autoconfigure.exception.NotFoundException;
<#if package.Controller?split(".")?last == "cms">
import io.github.talelin.core.annotation.LoginRequired;
</#if>
import io.github.talelin.latticy.common.mybatis.Page;
import io.github.talelin.latticy.common.util.PageUtil;
import io.github.talelin.latticy.dto.query.BasePageDTO;
import ${package.Service}.${table.serviceName};
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import ${package.Entity}.${entity};
import io.github.talelin.latticy.vo.CreatedVO;
import io.github.talelin.latticy.vo.DeletedVO;
import io.github.talelin.latticy.vo.PageResponseVO;
import io.github.talelin.latticy.vo.UpdatedVO;

import javax.validation.constraints.Positive;

<#if restControllerStyle>
import org.springframework.web.bind.annotation.RestController;
<#else>
import org.springframework.stereotype.Controller;
</#if>
<#if superControllerClassPackage??>
import ${superControllerClassPackage};
</#if>

/**
<#if table.comment != "">
 * ${table.comment!}前端控制器
 * 注意:该代码生成器只实现了针对单表的新增、更新、删除、查询单个实体对象和分页查询多个实体对象的方法,如果业务较为复杂,不建议使用代码生成器!
 *
</#if>
* @author ${author}
* @since ${date}
*/
<#if restControllerStyle>
@RestController
<#else>
@Controller
</#if>
@RequestMapping("/${package.Controller?split(".")?last}<#if package.ModuleName?? && package.ModuleName != "">/${package.ModuleName}</#if>/<#if controllerMappingHyphenStyle??>${controllerMappingHyphen?replace("-do", "")}<#else>${table.entityPath?replace("DO", "")}</#if>")
<#if kotlin>
class ${table.controllerName}<#if superControllerClass??> : ${superControllerClass}()</#if>
<#else>
<#if superControllerClass??>
public class ${table.controllerName} extends ${superControllerClass} {
<#else>
public class ${table.controllerName} {
</#if>

    @Autowired
    private ${table.serviceName} ${table.serviceName?uncap_first};

    <#if package.Controller?split(".")?last == "cms">
    @LoginRequired
    </#if>
    @PostMapping("")
    public CreatedVO create(@RequestBody ${entity} dto) {
        ${entity} ${entity?uncap_first} = new ${entity}();
        BeanUtils.copyProperties(dto, ${entity?uncap_first});
        ${table.serviceName?uncap_first}.save(${entity?uncap_first});
        return new CreatedVO();
    }

    <#if package.Controller?split(".")?last == "cms">
    @LoginRequired
    </#if>
    @PutMapping("/{id}")
    public UpdatedVO update(
            @PathVariable @Positive(message = "{id.positive}") Integer id,
            @RequestBody ${entity} dto
    ) {
        get(id);
        dto.setId(id);
        ${table.serviceName?uncap_first}.updateById(dto);
        return new UpdatedVO();
    }

    <#if package.Controller?split(".")?last == "cms">
    @LoginRequired
    </#if>
    @DeleteMapping("/{id}")
    public DeletedVO delete(@PathVariable @Positive(message = "{id.positive}") Integer id) {
        ${entity} ${entity?uncap_first} = get(id);
        ${table.serviceName?uncap_first}.removeById(${entity?uncap_first}.getId());
        return new DeletedVO();
    }

    @GetMapping("/{id}")
    <#if package.Controller?split(".")?last == "cms">
    @LoginRequired
    </#if>
    public ${entity} get(@PathVariable(value = "id") @Positive(message = "{id.positive}") Integer id) {
        ${entity} ${entity?uncap_first} = ${table.serviceName?uncap_first}.getById(id);
        if (${entity?uncap_first} == null) {
            throw new NotFoundException();
        }
        return ${entity?uncap_first};
    }

    @GetMapping("/page")
    <#if package.Controller?split(".")?last == "cms">
    @LoginRequired
    </#if>
    public PageResponseVO<${entity}> page(
            @Validated BasePageDTO dto
    ) {
        return PageUtil.build(${table.serviceName?uncap_first}.page(new Page<>(dto.getPage(), dto.getCount()), null));
    }

}
</#if>
JAVA、基础技术、技术与框架SpringBoot系列(二十九)MyBatisPlus高级功能插图

三、乐观锁

当出现并发操作的时候,需要确保各个用户对数据的操作不产生冲突,此时需要以重并发控制手段。常用的方法:

关于解决幻读幻写的方式:

  • 底层
    • 数据库读写分离
    • 提升服务器配置
  • 代码层
    • 引入锁机制
  • 应用层
    • 引入消息队列
  • 悲观锁:使用数据库的锁机制,但锁的时候无法读取
  • 乐观锁:通常加入版本号
  • 分布式锁

乐观锁的实现如下:

  1. 取出记录时,获取当前version
  2. 更新时,带上这个version
  3. 执行更新时, set version = newVersion where version = oldVersion
  4. 如果oldVersion与数据库中的version不一致,就更新失败

配置乐观锁插件

3.1创建MybatisPlusConfig

在common/mybatis创建类

package io.github.talelin.latticy.common.configuration;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import io.github.talelin.autoconfigure.bean.PermissionMetaCollector;
import io.github.talelin.latticy.common.interceptor.RequestLogInterceptor;
import io.github.talelin.latticy.module.log.MDCAccessServletFilter;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author pedro@TaleLin
 * @author colorful@TaleLin
 */
@Configuration(proxyBeanMethods = false)
public class CommonConfiguration {

    @Bean
    public RequestLogInterceptor requestLogInterceptor() {
        return new RequestLogInterceptor();
    }

    /**
     * 新的分页插件,一缓和二缓遵循mybatis的规则
     * 需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
     * 参考链接:https://mp.baomidou.com/guide/interceptor.html
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }

    /**
     * 参考链接:https://mp.baomidou.com/guide/interceptor.html
     */
    @Bean
    @SuppressWarnings("deprecation")
    public ConfigurationCustomizer configurationCustomizer() {
        return configuration -> configuration.setUseDeprecatedExecutor(false);
    }

    @Bean
    public ISqlInjector sqlInjector() {
        return new DefaultSqlInjector();
    }

    /**
     * 记录每个被 @PermissionMeta 记录的信息,在beans的后置调用
     *
     * @return PermissionMetaCollector
     */
    @Bean
    public PermissionMetaCollector postProcessBeans() {
        return new PermissionMetaCollector();
    }


    /**
     * 接口中,自动转换的有:驼峰转换为下划线,空值输出null
     */
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer customJackson() {
        return jacksonObjectMapperBuilder -> {
            // jacksonObjectMapperBuilder.serializationInclusion(JsonInclude.Include.NON_NULL);
            jacksonObjectMapperBuilder.failOnUnknownProperties(false);
            jacksonObjectMapperBuilder.propertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
        };
    }

    /**
     * 用于将 request 相关信息(如请求 url)放入 MDC 中供日志使用
     *
     * @return Logback 的 MDCInsertingServletFilter
     */
    @Bean
    public FilterRegistrationBean<MDCAccessServletFilter> mdcInsertingServletFilter() {
        FilterRegistrationBean<MDCAccessServletFilter> filterRegistrationBean = new FilterRegistrationBean<>();
        MDCAccessServletFilter mdcAccessServletFilter = new MDCAccessServletFilter();
        filterRegistrationBean.setFilter(mdcAccessServletFilter);
        filterRegistrationBean.setName("mdc-access-servlet-filter");
        return filterRegistrationBean;
    }
}

3.2 创建BannerBO

在version字段上添加@version注解

package io.github.talelin.latticy.model;

import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;

import java.util.Date;

@Getter
@Setter
@TableName("banner")
public class BannerDO {
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    private String name;

    private String title;

    private String description;

    private String img;

    @Version
    private Integer version;

    @JsonIgnore
    private Date createTime;

    @JsonIgnore
    private Date updateTime;

    /* @TableLogic注解参数
    value = "" 默认的原值
    delval = "" 删除后的值
    @TableLogic(value="原值",delval="改值")
    * */
    @JsonIgnore
    @TableLogic
    private Date deleteTime;
}

3.3创建BannerMapper

package io.github.talelin.latticy.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import io.github.talelin.latticy.model.BannerDO;
import org.apache.ibatis.annotations.*;
import org.springframework.boot.Banner;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface BannerMapper extends BaseMapper<BannerDO> {

}

3.4 创建BannerService

3.4.1创建BannerService接口
public void updateLock();
3.4.2创建BannerService实现类
    public void updateLock(){
        BannerDO bannerDO = bannerMapper.selectById(23);
        Integer Version = bannerDO.getVersion();
        bannerDO.setName("testName23");
        bannerDO.setVersion(Version);
        bannerDO.setDescription("testDesc23");
        System.out.println(bannerDO.getVersion());
        bannerMapper.updateById(bannerDO);
        //update succes

        bannerDO.setName("testName231");
        bannerDO.setDescription("testDesc231");
        bannerDO.setVersion(Version);
        bannerMapper.updateById(bannerDO);
        //update error

    }

3.5 创建BannerController

    @Autowired
    private BannerService bannerService;
    @GetMapping("/testupdateLock")
    public String updateLock() {
        bannerService.updateLock();
        return "testUpdate";
    }
JAVA、基础技术、技术与框架SpringBoot系列(二十九)MyBatisPlus高级功能插图1

从结果可以看出,第一次更新后,version会自增;第二次更新的时候,因为version还是旧的,所以无法更新;这就是乐观锁的使用场景