Index: forgon-tools/src/main/java/com/forgon/system/transaction/model/TransactionInfo.java =================================================================== diff -u --- forgon-tools/src/main/java/com/forgon/system/transaction/model/TransactionInfo.java (revision 0) +++ forgon-tools/src/main/java/com/forgon/system/transaction/model/TransactionInfo.java (revision 23785) @@ -0,0 +1,146 @@ +/** + * + */ +package com.forgon.system.transaction.model; + +import java.lang.reflect.Method; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +import org.springframework.core.NamedThreadLocal; +import org.springframework.transaction.TransactionStatus; + +import com.forgon.system.concurrent.model.OptimisticLock; + +/** + * @author dandan 2018年7月18日 下午5:17:48 + * + */ +public class TransactionInfo { + + private static final ThreadLocal transactionInfoHolder = new NamedThreadLocal( + "Dingxiang TransactionInfo"); + + public static TransactionInfo currentTransactionInfo() { + return transactionInfoHolder.get(); + } + + private Object transactionId; + private String name; + private String joinpointIdentification; + private TransactionInfo oldTransactionInfo; + private TransactionStatus transactionStatus; + private Date startTime = new Date(); + private Thread thread = Thread.currentThread(); + private List locks = new LinkedList(); + + public TransactionInfo(Class targetClass, Method method, + TransactionStatus transactionStatus) { + transactionId = new Object(); + name = method.getName(); + joinpointIdentification = methodIdentification(method, targetClass); + this.transactionStatus = transactionStatus; + } + + @Override + public int hashCode() { + // TODO Auto-generated method stub + if (transactionId != null) { + return transactionId.hashCode(); + } + return 0; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof TransactionInfo) { + TransactionInfo other = (TransactionInfo) obj; + if (transactionId == other.transactionId) { + return true; + } + if (transactionId != null) { + return transactionId.equals(other.transactionId); + } + } + return false; + } + + public void bindToThread() { + // Expose current TransactionStatus, preserving any existing + // TransactionStatus + // for restoration after this transaction is complete. + this.oldTransactionInfo = transactionInfoHolder.get(); + transactionInfoHolder.set(this); + } + + public void restoreThreadLocalStatus() { + // Use stack to restore old transaction TransactionInfo. + // Will be null if none was set. + transactionInfoHolder.set(this.oldTransactionInfo); + } + + private String methodIdentification(Method method, Class targetClass) { + return (targetClass != null ? targetClass : method.getDeclaringClass()) + .getName() + "." + method.getName(); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Object getTransactionId() { + return transactionId; + } + + public String getJoinpointIdentification() { + return joinpointIdentification; + } + + public TransactionStatus getTransactionStatus() { + return transactionStatus; + } + + public Date getStartTime() { + return startTime; + } + + public void setTransactionId(Object transactionId) { + this.transactionId = transactionId; + } + + public void setJoinpointIdentification(String joinpointIdentification) { + this.joinpointIdentification = joinpointIdentification; + } + + public void setTransactionStatus(TransactionStatus transactionStatus) { + this.transactionStatus = transactionStatus; + } + + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + + public Thread getThread() { + return thread; + } + + public void setThread(Thread thread) { + this.thread = thread; + } + + public List getLocks() { + return locks; + } + + public void addLock(OptimisticLock optimisticLock) { + locks.add(optimisticLock); + } +} Index: forgon-tools/src/main/java/com/forgon/system/concurrent/model/OptimisticLockException.java =================================================================== diff -u --- forgon-tools/src/main/java/com/forgon/system/concurrent/model/OptimisticLockException.java (revision 0) +++ forgon-tools/src/main/java/com/forgon/system/concurrent/model/OptimisticLockException.java (revision 23785) @@ -0,0 +1,15 @@ +/** + * + */ +package com.forgon.system.concurrent.model; + +/** + * @author dandan 2018年7月21日 上午10:25:52 + * + */ +public class OptimisticLockException extends RuntimeException { + + public OptimisticLockException(String message) { + super(message); + } +} Index: forgon-tools/src/main/java/com/forgon/system/concurrent/service/OptimisticLockManager.java =================================================================== diff -u --- forgon-tools/src/main/java/com/forgon/system/concurrent/service/OptimisticLockManager.java (revision 0) +++ forgon-tools/src/main/java/com/forgon/system/concurrent/service/OptimisticLockManager.java (revision 23785) @@ -0,0 +1,136 @@ +/** + * + */ +package com.forgon.system.concurrent.service; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.collections.CollectionUtils; + +import com.forgon.system.concurrent.model.EntityLocksInfo; +import com.forgon.system.concurrent.model.OptimisticLock; +import com.forgon.system.transaction.model.TransactionInfo; + +/** + * @author dandan 2018年7月16日 上午11:07:47 + * + */ +public class OptimisticLockManager { + + private ConcurrentHashMap> threadToLockListMap = new ConcurrentHashMap>(); + // private ConcurrentHashMap> + // transactionToLockListMap = new ConcurrentHashMap>(); + private ConcurrentHashMap entityToLocksMap = new ConcurrentHashMap(); + + private void throwException(String msg) { + throw new RuntimeException(String.format("OptimisticLock:%s", msg)); + } + + public void requestLockById(String entity, Long id) { + if (entity == null || id == null) { + throwException("参数非法!"); + } + requestLockById(entity, id.toString()); + } + + public void requestLockById(String entity, String id) { + if (entity == null || id == null) { + throwException("参数非法!"); + } + EntityLocksInfo entityLocksInfo = entityToLocksMap.get(entity); + if(entityLocksInfo == null){ + entityLocksInfo = entityToLocksMap.putIfAbsent(entity, new EntityLocksInfo(entity)); + } + entityLocksInfo.requestLockById(id); + } + + public void requestLockByLongIds(String entity, Collection ids) { + if (entity == null || CollectionUtils.isEmpty(ids)) { + throwException("参数非法!"); + } + for (Long id : ids) { + requestLockById(entity, id); + } + } + + public void requestLockByStringIds(String entity, Collection ids) { + if (entity == null || CollectionUtils.isEmpty(ids)) { + throwException("参数非法!"); + } + for (String id : ids) { + requestLockById(entity, id); + } + } + + private Set getTransactionInfoByOptimisticLocks( + List optimisticLockList) { + Set retSet = new HashSet(); + if (CollectionUtils.isNotEmpty(optimisticLockList)) { + for (OptimisticLock optimisticLock : optimisticLockList) { + retSet.add(optimisticLock.getTransactionInfo()); + } + } + return retSet; + } + + public void releaseCurrentThreadLocks() { + Thread thread = Thread.currentThread(); + List optimisticLockList = threadToLockListMap + .get(thread); + if (CollectionUtils.isNotEmpty(optimisticLockList)) { + threadToLockListMap.remove(thread); + // 移除transactionToLockListMap对应的项 + // Set transactionInfoSet = + // getTransactionInfoByOptimisticLocks(optimisticLockList); + // for (TransactionInfo transactionInfo : transactionInfoSet) { + // transactionToLockListMap.remove(transactionInfo); + // } + + } + } + + private Set getEntitySet(List optimisticLockList) { + Set retSet = new HashSet(); + if (CollectionUtils.isNotEmpty(optimisticLockList)) { + for (OptimisticLock optimisticLock : optimisticLockList) { + retSet.add(optimisticLock.getEntity()); + } + } + return retSet; + } + + public void releaseTransactionLocks(TransactionInfo transactionInfo) { + Thread thread = Thread.currentThread(); + // List optimisticLockList = + // transactionToLockListMap.remove(transactionInfo); + List optimisticLockList = transactionInfo.getLocks(); + if (CollectionUtils.isNotEmpty(optimisticLockList)) { + List optimisticLockListInThread = threadToLockListMap + .get(thread); + // 移除线程map的锁 + if (optimisticLockListInThread != null) { + optimisticLockListInThread.removeAll(optimisticLockList); + } + // 移除entityToLocksMap的锁 + Set entitySet = getEntitySet(optimisticLockList); + for (String entity : entitySet) { + EntityLocksInfo entityLocksInfo = entityToLocksMap.get(entity); + if (entityLocksInfo != null) { + entityLocksInfo.releaseTransactionLocks(transactionInfo); + } else { + // TODO log + } + } + } + + } + + public void releaseAllLocks() { + + } +} Index: forgon-tools/src/main/java/com/forgon/system/concurrent/service/OptimisticLockInterceptor.java =================================================================== diff -u --- forgon-tools/src/main/java/com/forgon/system/concurrent/service/OptimisticLockInterceptor.java (revision 0) +++ forgon-tools/src/main/java/com/forgon/system/concurrent/service/OptimisticLockInterceptor.java (revision 23785) @@ -0,0 +1,58 @@ +/** + * + */ +package com.forgon.system.concurrent.service; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +import com.forgon.system.transaction.model.TransactionInfo; +import com.forgon.system.transaction.util.TransactionUtils; + +/** + * @author dandan 2018年7月16日 上午10:14:40 + * + */ +public class OptimisticLockInterceptor implements MethodInterceptor { + + /* + * (non-Javadoc) + * + * @see + * org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept + * .MethodInvocation) + */ + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + // 获取事务信息 + TransactionStatus transactionStatus = TransactionUtils + .getCurrentTransactionStatus(); + TransactionInfo transactionInfo = null; + boolean bind = false; + if (transactionStatus != null && transactionStatus.isNewTransaction()) { + transactionInfo = new TransactionInfo(invocation.getClass(), + invocation.getMethod(), transactionStatus); + transactionInfo.bindToThread(); + bind = true; + try { + TransactionSynchronizationManager + .registerSynchronization(new OptimisticLockTransactionSynchronizationAdapter( + transactionInfo)); + } catch (Exception e) { + e.printStackTrace(); + } + } + Object rv; + try { + rv = invocation.proceed(); + } finally { + if (bind) { + transactionInfo.restoreThreadLocalStatus(); + } + } + return rv; + } + +} Index: forgon-tools/src/main/java/com/forgon/system/concurrent/model/OptimisticLock.java =================================================================== diff -u --- forgon-tools/src/main/java/com/forgon/system/concurrent/model/OptimisticLock.java (revision 0) +++ forgon-tools/src/main/java/com/forgon/system/concurrent/model/OptimisticLock.java (revision 23785) @@ -0,0 +1,91 @@ +/** + * + */ +package com.forgon.system.concurrent.model; + +import java.util.Date; + +import com.forgon.system.transaction.model.TransactionInfo; + +/** + * @author dandan 2018年7月16日 下午3:24:20 + * + */ +public class OptimisticLock { + + public static final String LOCKTYPE_ENTITY = "Entity"; + public static final String LOCKTYPE_RECORD = "Record"; + + private String type = LOCKTYPE_RECORD; + private String entity; + private String id; + private Thread thread = Thread.currentThread(); + private TransactionInfo transactionInfo; + private Date requestTime = new Date(); + + public boolean isRecord() { + return LOCKTYPE_RECORD.equals(type); + } + + public boolean isEntity() { + return LOCKTYPE_ENTITY.equals(type); + } + + public String getMapKey() { + if (LOCKTYPE_RECORD.equals(type)) { + return entity + "_" + id; + } else if (LOCKTYPE_ENTITY.equals(type)) { + return entity; + } + return null; + } + + public String getType() { + return type; + } + + public String getEntity() { + return entity; + } + + public String getId() { + return id; + } + + public Thread getThread() { + return thread; + } + + public TransactionInfo getTransactionInfo() { + return transactionInfo; + } + + public void setTransactionInfo(TransactionInfo transactionInfo) { + this.transactionInfo = transactionInfo; + } + + public Date getRequestTime() { + return requestTime; + } + + public void setType(String type) { + this.type = type; + } + + public void setEntity(String entity) { + this.entity = entity; + } + + public void setId(String id) { + this.id = id; + } + + public void setThread(Thread thread) { + this.thread = thread; + } + + public void setRequestTime(Date requestTime) { + this.requestTime = requestTime; + } + +} Index: forgon-tools/src/main/java/com/forgon/system/transaction/util/TransactionUtils.java =================================================================== diff -u --- forgon-tools/src/main/java/com/forgon/system/transaction/util/TransactionUtils.java (revision 0) +++ forgon-tools/src/main/java/com/forgon/system/transaction/util/TransactionUtils.java (revision 23785) @@ -0,0 +1,30 @@ +/** + * + */ +package com.forgon.system.transaction.util; + +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.interceptor.TransactionAspectSupport; + +import com.forgon.system.transaction.model.TransactionInfo; + +/** + * @author dandan 2018年7月17日 下午4:46:49 + * + */ +public class TransactionUtils { + + public static TransactionStatus getCurrentTransactionStatus() { + TransactionStatus transactionStatus = null; + try { + transactionStatus = TransactionAspectSupport + .currentTransactionStatus(); + } catch (Exception e) { + } + return transactionStatus; + } + + public static TransactionInfo currentTransactionInfo() { + return TransactionInfo.currentTransactionInfo(); + } +} Index: forgon-tools/src/main/java/com/forgon/system/concurrent/model/EntityLocksInfo.java =================================================================== diff -u --- forgon-tools/src/main/java/com/forgon/system/concurrent/model/EntityLocksInfo.java (revision 0) +++ forgon-tools/src/main/java/com/forgon/system/concurrent/model/EntityLocksInfo.java (revision 23785) @@ -0,0 +1,85 @@ +/** + * + */ +package com.forgon.system.concurrent.model; + +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +import com.forgon.system.transaction.model.TransactionInfo; +import com.forgon.system.transaction.util.TransactionUtils; + +/** + * @author dandan 2018年7月17日 下午4:12:07 + * + */ +public class EntityLocksInfo { + + private String entity; + private ConcurrentHashMap idToLockMap = new ConcurrentHashMap(); + + // @Override + // public int hashCode() { + // return entity.hashCode(); + // } + // + // @Override + // public boolean equals(Object obj) { + // if (this == obj) { + // return true; + // } + // if (obj instanceof EntityLocksInfo) { + // EntityLocksInfo other = (EntityLocksInfo) obj; + // return entity.equals(other.entity); + // } + // return false; + // } + public void requestLockById(String id) { + OptimisticLock existingOptimisticLock = idToLockMap.get(id); + TransactionInfo transactionInfo = TransactionUtils + .currentTransactionInfo(); + if (transactionInfo == null) { + // TODO log + return; + } + if (existingOptimisticLock == null) { + OptimisticLock newOptimisticLock = new OptimisticLock(); + newOptimisticLock.setEntity(entity); + newOptimisticLock.setId(id); + newOptimisticLock.setTransactionInfo(transactionInfo); + + existingOptimisticLock = idToLockMap.putIfAbsent(id, + newOptimisticLock); + } + if (existingOptimisticLock != null) { + if (existingOptimisticLock.getTransactionInfo() != transactionInfo) { + throw new OptimisticLockException(String.format( + "id为%s的记录已被事务%s锁定,请稍后重试!", id, + transactionInfo.getJoinpointIdentification())); + } + } + } + + public void releaseTransactionLocks(TransactionInfo transactionInfo) { + List locks = transactionInfo.getLocks(); + if (locks != null) { + for (OptimisticLock optimisticLock : locks) { + idToLockMap.remove(optimisticLock.getId()); + } + } + } + + public EntityLocksInfo(String entity) { + super(); + this.entity = entity; + } + + public String getEntity() { + return entity; + } + + public void setEntity(String entity) { + this.entity = entity; + } + +} Index: forgon-tools/src/main/java/com/forgon/system/concurrent/service/OptimisticLockTransactionSynchronizationAdapter.java =================================================================== diff -u --- forgon-tools/src/main/java/com/forgon/system/concurrent/service/OptimisticLockTransactionSynchronizationAdapter.java (revision 0) +++ forgon-tools/src/main/java/com/forgon/system/concurrent/service/OptimisticLockTransactionSynchronizationAdapter.java (revision 23785) @@ -0,0 +1,30 @@ +/** + * + */ +package com.forgon.system.concurrent.service; + +import org.springframework.transaction.support.TransactionSynchronizationAdapter; + +import com.forgon.system.transaction.model.TransactionInfo; + +/** + * @author dandan 2018年7月16日 上午11:04:24 + * + */ +public class OptimisticLockTransactionSynchronizationAdapter extends + TransactionSynchronizationAdapter { + + private TransactionInfo transactionInfo = null; + + public OptimisticLockTransactionSynchronizationAdapter( + TransactionInfo transactionInfo) { + super(); + this.transactionInfo = transactionInfo; + } + + @Override + public void afterCompletion(int status) { + // 事务完成时,清除事务相关的锁 + + } +}