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>
三、乐观锁
当出现并发操作的时候,需要确保各个用户对数据的操作不产生冲突,此时需要以重并发控制手段。常用的方法:
关于解决幻读幻写的方式:
- 底层
- 数据库读写分离
- 提升服务器配置
- 代码层
- 引入锁机制
- 应用层
- 引入消息队列
- 悲观锁:使用数据库的锁机制,但锁的时候无法读取
- 乐观锁:通常加入版本号
- 分布式锁
乐观锁的实现如下:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果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";
}
从结果可以看出,第一次更新后,version会自增;第二次更新的时候,因为version还是旧的,所以无法更新;这就是乐观锁的使用场景