Java tutorial
/** * Copyright 2009-2015 the original author or authors. * * 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.mook.locker.interceptor; import java.lang.reflect.Method; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import org.apache.ibatis.binding.MapperMethod; import org.apache.ibatis.executor.parameter.ParameterHandler; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.logging.Log; import org.apache.ibatis.logging.LogFactory; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ParameterMapping; import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Intercepts; import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Plugin; import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.SystemMetaObject; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.TypeException; import org.apache.ibatis.type.TypeHandler; import com.mook.locker.annotation.VersionLocker; /** * <p>MyBatis???<br> * * @author 342252328@qq.com * @date 2016-05-27 * @version 1.0 * @since JDK1.7 * */ @Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }), @Signature(type = ParameterHandler.class, method = "setParameters", args = { PreparedStatement.class }) }) public class OptimisticLocker implements Interceptor { private static final Log log = LogFactory.getLog(OptimisticLocker.class); Properties props = null; @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public Object intercept(Invocation invocation) throws Exception { String interceptMethod = invocation.getMethod().getName(); String versionColumn = props.getProperty("versionColumn", "version"); if ("prepare".equals(interceptMethod)) { StatementHandler handler = (StatementHandler) invocation.getTarget(); MetaObject hm = SystemMetaObject.forObject(handler); MappedStatement ms = (MappedStatement) hm.getValue("delegate.mappedStatement"); SqlCommandType sqlCmdType = ms.getSqlCommandType(); if (sqlCmdType != SqlCommandType.UPDATE) { return invocation.proceed(); } BoundSql boundSql = (BoundSql) hm.getValue("delegate.boundSql"); if (hasVersionLocker(ms, boundSql)) { return invocation.proceed(); } Object originalVersion = hm.getValue("delegate.boundSql.parameterObject.version"); Object versionIncr = castTypeAndOptValue(originalVersion, hm.getValue("delegate.boundSql.parameterObject"), ValueType.INCREASE); hm.setValue("delegate.boundSql.parameterObject.version", versionIncr); String originalSql = (String) hm.getValue("delegate.boundSql.sql"); StringBuilder builder = new StringBuilder(originalSql); builder.append(" and "); builder.append(versionColumn); builder.append(" = ?"); hm.setValue("delegate.boundSql.sql", builder.toString()); if (log.isDebugEnabled()) { log.debug("==> originalSql: " + originalSql); } return invocation.proceed(); } else if ("setParameters".equals(interceptMethod)) { ParameterHandler handler = (ParameterHandler) invocation.getTarget(); MetaObject hm = SystemMetaObject.forObject(handler); MappedStatement ms = (MappedStatement) hm.getValue("mappedStatement"); SqlCommandType sqlCmdType = ms.getSqlCommandType(); if (sqlCmdType != SqlCommandType.UPDATE) { return invocation.proceed(); } Configuration configuration = (Configuration) hm.getValue("configuration"); BoundSql boundSql = (BoundSql) hm.getValue("boundSql"); if (hasVersionLocker(ms, boundSql)) { return invocation.proceed(); } Object result = invocation.proceed(); ParameterMapping versionMapping = new ParameterMapping.Builder(configuration, versionColumn, Object.class).build(); Object parameterObject = boundSql.getParameterObject(); MetaObject pm = configuration.newMetaObject(parameterObject); if (parameterObject instanceof MapperMethod.ParamMap<?>) { MapperMethod.ParamMap<?> paramMap = (MapperMethod.ParamMap<?>) parameterObject; if (!paramMap.containsKey(versionColumn)) { throw new TypeException("??MyBatis@Param"); } } Object value = pm.getValue(versionColumn); TypeHandler typeHandler = versionMapping.getTypeHandler(); JdbcType jdbcType = versionMapping.getJdbcType(); if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); } List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); try { PreparedStatement ps = (PreparedStatement) invocation.getArgs()[0]; Object val = castTypeAndOptValue(value, parameterObject, ValueType.DECREASE); typeHandler.setParameter(ps, parameterMappings.size() + 1, val, jdbcType); } catch (TypeException e) { throw new TypeException( "Could not set parameters for mapping: " + parameterMappings + ". Cause: " + e, e); } catch (SQLException e) { throw new TypeException( "Could not set parameters for mapping: " + parameterMappings + ". Cause: " + e, e); } return result; } return invocation.proceed(); } private Object castTypeAndOptValue(Object value, Object parameterObject, ValueType vt) { Class<?> valType = value.getClass(); if (valType == Long.class || valType == long.class) { return (Long) value + vt.value; } else if (valType == Integer.class || valType == int.class) { return (Integer) value + vt.value; } else if (valType == Float.class || valType == float.class) { return (Float) value + vt.value; } else if (valType == Double.class || valType == double.class) { return (Double) value + vt.value; } else { if (parameterObject instanceof MapperMethod.ParamMap<?>) { throw new TypeException("??MyBatis@Param"); } else { throw new TypeException("Property 'version' in " + parameterObject.getClass().getSimpleName() + " must be [ long, int, float, double ] or [ Long, Integer, Float, Double ]"); } } } private boolean hasVersionLocker(MappedStatement ms, BoundSql boundSql) { Map<String, Class<?>> mapperMap = new HashMap<String, Class<?>>(); Collection<Class<?>> mappers = ms.getConfiguration().getMapperRegistry().getMappers(); if (null != mappers && !mappers.isEmpty()) { for (Class<?> me : mappers) { mapperMap.put(me.getName(), me); } } Class<?>[] paramCls = null; Object paramObj = boundSql.getParameterObject(); if (paramObj instanceof MapperMethod.ParamMap<?>) { MapperMethod.ParamMap<?> mmp = (MapperMethod.ParamMap<?>) paramObj; if (null != mmp && !mmp.isEmpty()) { paramCls = new Class<?>[mmp.size() / 2]; int mmpLen = mmp.size() / 2; for (int i = 0; i < mmpLen; i++) { Object index = mmp.get("param" + (i + 1)); paramCls[i] = index.getClass(); } } } String id = ms.getId(); int pos = id.lastIndexOf("."); String nameSpace = id.substring(0, pos); if (mapperMap.containsKey(nameSpace)) { Class<?> mapper = mapperMap.get(nameSpace); Method m = null; try { if (null == paramCls && paramObj instanceof Map) { paramCls = new Class<?>[] { Map.class }; } else { paramCls = new Class<?>[] { paramObj.getClass() }; } m = mapper.getDeclaredMethod(id.substring(pos + 1), paramCls); } catch (NoSuchMethodException | SecurityException e) { throw new RuntimeException("Map?"); } VersionLocker vl = m.getAnnotation(VersionLocker.class); if (null != vl && vl.value() == false) { return true; } else { return false; } } else { throw new RuntimeException("?"); } } @Override public Object plugin(Object target) { if (target instanceof StatementHandler || target instanceof ParameterHandler) { return Plugin.wrap(target, this); } else { return target; } } @Override public void setProperties(Properties properties) { if (null != properties && !properties.isEmpty()) props = properties; } private enum ValueType { INCREASE(1), DECREASE(-1); private Integer value; private ValueType(Integer value) { this.value = value; } } }