All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor Maven / Gradle / Ivy

/*
 * Copyright (c) 2011-2022, baomidou ([email protected]).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.baomidou.mybatisplus.extension.plugins.inner;

import com.baomidou.mybatisplus.annotation.Version;
import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
import com.baomidou.mybatisplus.core.conditions.ISqlSegment;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.segments.NormalSegmentList;
import com.baomidou.mybatisplus.core.conditions.update.Update;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.enums.SqlKeyword;
import com.baomidou.mybatisplus.core.mapper.Mapper;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;

import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Optimistic Lock Light version
 * 

Intercept on {@link Executor}.update;

*

Support version types: int/Integer, long/Long, java.util.Date, java.sql.Timestamp

*

For extra types, please define a subclass and override {@code getUpdatedVersionVal}() method.

*
*

How to use?

*

(1) Define an Entity and add {@link Version} annotation on one entity field.

*

(2) Add {@link OptimisticLockerInnerInterceptor} into mybatis plugin.

*
*

How to work?

*

if update entity with version column=1:

*

(1) no {@link OptimisticLockerInnerInterceptor}:

*

SQL: update tbl_test set name='abc' where id=100001;

*

(2) add {@link OptimisticLockerInnerInterceptor}:

*

SQL: update tbl_test set name='abc',version=2 where id=100001 and version=1;

* * @author yuxiaobin * @since 3.4.0 */ @SuppressWarnings({"unchecked"}) public class OptimisticLockerInnerInterceptor implements InnerInterceptor { private RuntimeException exception; public void setException(RuntimeException exception) { this.exception = exception; } /** * entity类缓存 */ private static final Map> ENTITY_CLASS_CACHE = new ConcurrentHashMap<>(); /** * 变量占位符正则 */ private static final Pattern PARAM_PAIRS_RE = Pattern.compile("#\\{ew\\.paramNameValuePairs\\.(" + Constants.WRAPPER_PARAM + "\\d+)\\}"); /** * paramNameValuePairs存放的version值的key */ private static final String UPDATED_VERSION_VAL_KEY = "#updatedVersionVal#"; /** * Support wrapper mode */ private final boolean wrapperMode; public OptimisticLockerInnerInterceptor() { this(false); } public OptimisticLockerInnerInterceptor(boolean wrapperMode) { this.wrapperMode = wrapperMode; } @Override public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException { if (SqlCommandType.UPDATE != ms.getSqlCommandType()) { return; } if (parameter instanceof Map) { Map map = (Map) parameter; doOptimisticLocker(map, ms.getId()); } } protected void doOptimisticLocker(Map map, String msId) { // updateById(et), update(et, wrapper); Object et = map.getOrDefault(Constants.ENTITY, null); if (Objects.nonNull(et)) { // version field TableFieldInfo fieldInfo = this.getVersionFieldInfo(et.getClass()); if (null == fieldInfo) { return; } try { Field versionField = fieldInfo.getField(); // 旧的 version 值 Object originalVersionVal = versionField.get(et); if (originalVersionVal == null) { if (null != exception) { /** * 自定义异常处理 */ throw exception; } return; } String versionColumn = fieldInfo.getColumn(); // 新的 version 值 Object updatedVersionVal = this.getUpdatedVersionVal(fieldInfo.getPropertyType(), originalVersionVal); String methodName = msId.substring(msId.lastIndexOf(StringPool.DOT) + 1); if ("update".equals(methodName)) { AbstractWrapper aw = (AbstractWrapper) map.getOrDefault(Constants.WRAPPER, null); if (aw == null) { UpdateWrapper uw = new UpdateWrapper<>(); uw.eq(versionColumn, originalVersionVal); map.put(Constants.WRAPPER, uw); } else { aw.apply(versionColumn + " = {0}", originalVersionVal); } } else { map.put(Constants.MP_OPTLOCK_VERSION_ORIGINAL, originalVersionVal); } versionField.set(et, updatedVersionVal); } catch (IllegalAccessException e) { throw ExceptionUtils.mpe(e); } } // update(LambdaUpdateWrapper) or update(UpdateWrapper) else if (wrapperMode && map.entrySet().stream().anyMatch(t -> Objects.equals(t.getKey(), Constants.WRAPPER))) { setVersionByWrapper(map, msId); } } protected TableFieldInfo getVersionFieldInfo(Class entityClazz) { TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClazz); return (null != tableInfo && tableInfo.isWithVersion()) ? tableInfo.getVersionFieldInfo() : null; } private void setVersionByWrapper(Map map, String msId) { Object ew = map.get(Constants.WRAPPER); if (null != ew && ew instanceof AbstractWrapper && ew instanceof Update) { Class entityClass = ENTITY_CLASS_CACHE.get(msId); if (null == entityClass) { try { final String className = msId.substring(0, msId.lastIndexOf('.')); entityClass = ReflectionKit.getSuperClassGenericType(Class.forName(className), Mapper.class, 0); ENTITY_CLASS_CACHE.put(msId, entityClass); } catch (ClassNotFoundException e) { throw ExceptionUtils.mpe(e); } } final TableFieldInfo versionField = getVersionFieldInfo(entityClass); if (null == versionField) { return; } final String versionColumn = versionField.getColumn(); final FieldEqFinder fieldEqFinder = new FieldEqFinder(versionColumn, (Wrapper) ew); if (!fieldEqFinder.isPresent()) { return; } final Map paramNameValuePairs = ((AbstractWrapper) ew).getParamNameValuePairs(); final Object originalVersionValue = paramNameValuePairs.get(fieldEqFinder.valueKey); if (originalVersionValue == null) { return; } final Object updatedVersionVal = getUpdatedVersionVal(originalVersionValue.getClass(), originalVersionValue); if (originalVersionValue == updatedVersionVal) { return; } // 拼接新的version值 paramNameValuePairs.put(UPDATED_VERSION_VAL_KEY, updatedVersionVal); ((Update) ew).setSql(String.format("%s = #{%s.%s}", versionColumn, "ew.paramNameValuePairs", UPDATED_VERSION_VAL_KEY)); } } /** * EQ字段查找器 */ private static class FieldEqFinder { /** * 状态机 */ enum State { INIT, FIELD_FOUND, EQ_FOUND, VERSION_VALUE_PRESENT; } /** * 字段值的key */ private String valueKey; /** * 当前状态 */ private State state; /** * 字段名 */ private final String fieldName; public FieldEqFinder(String fieldName, Wrapper wrapper) { this.fieldName = fieldName; state = State.INIT; find(wrapper); } /** * 是否已存在 */ public boolean isPresent() { return state == State.VERSION_VALUE_PRESENT; } private boolean find(Wrapper wrapper) { Matcher matcher; final NormalSegmentList segments = wrapper.getExpression().getNormal(); for (ISqlSegment segment : segments) { // 如果字段已找到并且当前segment为EQ if (state == State.FIELD_FOUND && segment == SqlKeyword.EQ) { this.state = State.EQ_FOUND; // 如果EQ找到并且value已找到 } else if (state == State.EQ_FOUND && (matcher = PARAM_PAIRS_RE.matcher(segment.getSqlSegment())).matches()) { this.valueKey = matcher.group(1); this.state = State.VERSION_VALUE_PRESENT; return true; // 处理嵌套 } else if (segment instanceof Wrapper) { if (find((Wrapper) segment)) { return true; } // 判断字段是否是要查找字段 } else if (segment.getSqlSegment().equals(this.fieldName)) { this.state = State.FIELD_FOUND; } } return false; } } /** * This method provides the control for version value.
* Returned value type must be the same as original one. * * @param originalVersionVal ignore * @return updated version val */ protected Object getUpdatedVersionVal(Class clazz, Object originalVersionVal) { if (long.class.equals(clazz) || Long.class.equals(clazz)) { return ((long) originalVersionVal) + 1; } else if (int.class.equals(clazz) || Integer.class.equals(clazz)) { return ((int) originalVersionVal) + 1; } else if (Date.class.equals(clazz)) { return new Date(); } else if (Timestamp.class.equals(clazz)) { return new Timestamp(System.currentTimeMillis()); } else if (LocalDateTime.class.equals(clazz)) { return LocalDateTime.now(); } //not supported type, return original val. return originalVersionVal; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy