├── src ├── test │ ├── resources │ │ ├── application.yml │ │ └── TestDatabase.sql │ └── java │ │ └── org │ │ └── easydevelop │ │ ├── business │ │ ├── domain │ │ │ ├── User.java │ │ │ └── UserOrder.java │ │ ├── dao │ │ │ ├── OrderDaoImpl.java │ │ │ └── UserDaoImpl.java │ │ ├── BusinessTest.java │ │ ├── service │ │ │ └── UserServceImpl.java │ │ └── TestApplicationConfig.java │ │ ├── mapreduce │ │ ├── stategy │ │ │ ├── IntReduceProviderTest.java │ │ │ ├── LongBasicReduceProviderTest.java │ │ │ ├── LongObjectReduceProviderTest.java │ │ │ ├── DoubleBasicReduceProviderTest.java │ │ │ ├── FloatBasicReduceProviderTest.java │ │ │ ├── DoubleObjectReduceProviderTest.java │ │ │ ├── FloatObjectReduceProviderTest.java │ │ │ ├── IntegerReduceProviderTest.java │ │ │ ├── ListReduceProviderTest.java │ │ │ ├── CollectionReduceProviderTest.java │ │ │ ├── SetReduceProviderTest.java │ │ │ └── KeyValueMapReduceProviderTest.java │ │ └── MapReduceTest.java │ │ ├── select │ │ └── ShardingTest.java │ │ └── generateid │ │ └── GenerateIdTest.java └── main │ └── java │ └── org │ └── easydevelop │ ├── generateid │ ├── strategy │ │ └── KeyGenerateStrategy.java │ ├── annotation │ │ └── GenerateId.java │ └── aspect │ │ └── KeyGenerateAspect.java │ ├── readonly │ ├── ReadOnlyDsSelectStrategy.java │ └── RoundRobinReadonlyDsSelectStrategy.java │ ├── select │ ├── strategy │ │ └── SelectDataSourceStrategy.java │ ├── annotation │ │ └── SelectDataSource.java │ └── aspect │ │ └── SelectDataSourceAspect.java │ ├── mapreduce │ ├── strategy │ │ ├── ReduceStrategy.java │ │ ├── provider │ │ │ ├── ReduceProvider.java │ │ │ ├── IntReduceProvider.java │ │ │ ├── LongBasicReduceProvider.java │ │ │ ├── LongObjectReduceProvider.java │ │ │ ├── FloatBasicReduceProvider.java │ │ │ ├── FloatObjectReduceProvider.java │ │ │ ├── DoubleBasicReduceProvider.java │ │ │ ├── IntegerReduceProvider.java │ │ │ ├── DoubleObjectReduceProvider.java │ │ │ ├── SetReduceProvider.java │ │ │ ├── ListReduceProvider.java │ │ │ ├── CollectionReduceProvider.java │ │ │ ├── KeyValueMapReduceProvider.java │ │ │ └── AbstractReduceProvider.java │ │ └── ReduceProviderManager.java │ ├── annotation │ │ └── MapReduce.java │ └── aspect │ │ ├── ReduceResultHolder.java │ │ └── MapReduceAspect.java │ ├── EnableShardingMethod.java │ ├── common │ ├── TransactionStatusHelper.java │ ├── ReflectHelper.java │ └── SpElHelper.java │ ├── sharding │ ├── aspect │ │ └── ShardingAspect.java │ ├── DataSourceSet.java │ ├── annotation │ │ └── ShardingContext.java │ └── ShardingRoutingDataSource.java │ └── ShardingConfiguaration.java ├── .gitignore ├── pom.xml └── readme.md /src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | sharding-method: 2 | mapreduce: 3 | reduce: 4 | future: 5 | timeout-mills: 0 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | server_back.properties 2 | *.class 3 | *.log 4 | *.log.* 5 | /target/* 6 | /.settings/* 7 | /bin/* 8 | /.project 9 | /.classpath 10 | /logs 11 | /target/ 12 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/generateid/strategy/KeyGenerateStrategy.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.generateid.strategy; 2 | /** 3 | * @author xudeyou 4 | */ 5 | public interface KeyGenerateStrategy { 6 | Object[] generateKey(Object[] metadata); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/readonly/ReadOnlyDsSelectStrategy.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.readonly; 2 | /** 3 | * @author xudeyou 4 | */ 5 | public interface ReadOnlyDsSelectStrategy { 6 | Integer select(int partionNumber,int slaveCount); 7 | } 8 | -------------------------------------------------------------------------------- /src/test/java/org/easydevelop/business/domain/User.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.business.domain; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author xudeyou 7 | */ 8 | @Data 9 | public class User { 10 | private Integer userId; 11 | private String name; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/select/strategy/SelectDataSourceStrategy.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.select.strategy; 2 | 3 | /** 4 | * @author xudeyou 5 | */ 6 | public interface SelectDataSourceStrategy { 7 | 8 | int select(Object[] shardingMetadata,int datasourceSize); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/org/easydevelop/business/domain/UserOrder.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.business.domain; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author xudeyou 7 | */ 8 | @Data 9 | public class UserOrder { 10 | private Integer userId; 11 | private Integer orderId; 12 | private Long amount; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/mapreduce/strategy/ReduceStrategy.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.strategy; 2 | 3 | import java.util.List; 4 | import java.util.concurrent.Future; 5 | 6 | 7 | /** 8 | * 9 | * @author deyou 10 | * 11 | * @param for final aggregation result class 12 | * @param for sharding result class 13 | */ 14 | public interface ReduceStrategy { 15 | R reduce(List> shardingFutrueList,Class shardingResultClass,Class reduceResultClass); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/mapreduce/strategy/provider/ReduceProvider.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.strategy.provider; 2 | 3 | import java.util.List; 4 | 5 | 6 | /** 7 | * 8 | * @author deyou 9 | * 10 | * @param for final aggregation result class 11 | * @param for sharding result class 12 | */ 13 | public interface ReduceProvider { 14 | R reduce(List shardingResultList,Class shardingResultClass,Class reduceResultClass); 15 | boolean support(Class shardingResultClass,Class reduceResultClass); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/EnableShardingMethod.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | import org.springframework.context.annotation.Import; 10 | 11 | /** 12 | * @author xudeyou 13 | */ 14 | @Target(ElementType.TYPE) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Documented 17 | @Import(ShardingConfiguaration.class) 18 | public @interface EnableShardingMethod { 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/org/easydevelop/business/dao/OrderDaoImpl.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.business.dao; 2 | 3 | import org.easydevelop.business.domain.UserOrder; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.jdbc.core.JdbcTemplate; 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * @author xudeyou 10 | */ 11 | @Component 12 | public class OrderDaoImpl { 13 | 14 | @Autowired 15 | private JdbcTemplate jdbcTemplate; 16 | 17 | public void saveOrder(UserOrder order){ 18 | int update = jdbcTemplate.update("INSERT INTO `user_order` (`user_id`, `order_id`, `amount`) VALUES (?, ?, ?);",order.getUserId(),order.getOrderId(),order.getAmount()); 19 | if(update != 1){ 20 | throw new RuntimeException("update error!"); 21 | } 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/common/TransactionStatusHelper.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.common; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.transaction.interceptor.TransactionAspectSupport; 7 | import org.springframework.transaction.interceptor.TransactionAttribute; 8 | 9 | /** 10 | * @author xudeyou 11 | */ 12 | public class TransactionStatusHelper { 13 | 14 | @Autowired 15 | private TransactionAspectSupport transactionAspectSupport; 16 | 17 | public Boolean isMethodReadOnly(Method method, Class clazz){ 18 | 19 | TransactionAttribute transactionAttribute = transactionAspectSupport.getTransactionAttributeSource().getTransactionAttribute(method, clazz); 20 | if(transactionAttribute != null){ 21 | return transactionAttribute.isReadOnly(); 22 | } 23 | 24 | return null; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/mapreduce/strategy/provider/IntReduceProvider.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.strategy.provider; 2 | 3 | import org.springframework.core.annotation.Order; 4 | 5 | /** 6 | * @author xudeyou 7 | */ 8 | @Order(208) 9 | public class IntReduceProvider extends AbstractReduceProvider { 10 | 11 | private Integer defaultValue = 0; 12 | 13 | @Override 14 | protected Object addToReduceResult(Object reduceCollection, Object shardingResult) { 15 | Integer r = (Integer) reduceCollection; 16 | Integer s = (Integer) shardingResult; 17 | r = r + s; 18 | return r; 19 | } 20 | 21 | @Override 22 | protected Class getDefaultConcreteClass() { 23 | return int.class; 24 | } 25 | 26 | @Override 27 | protected Class getRootClass() { 28 | return int.class; 29 | } 30 | 31 | @Override 32 | protected Object createDefaultConcreteObject() { 33 | return defaultValue; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/mapreduce/strategy/provider/LongBasicReduceProvider.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.strategy.provider; 2 | 3 | import org.springframework.core.annotation.Order; 4 | 5 | /** 6 | * @author xudeyou 7 | */ 8 | @Order(218) 9 | public class LongBasicReduceProvider extends AbstractReduceProvider { 10 | 11 | private Long defaultValue = 0l; 12 | 13 | @Override 14 | protected Object addToReduceResult(Object reduceCollection, Object shardingResult) { 15 | Long r = (Long) reduceCollection; 16 | Long s = (Long) shardingResult; 17 | r = r + s; 18 | return r; 19 | } 20 | 21 | @Override 22 | protected Class getDefaultConcreteClass() { 23 | return long.class; 24 | } 25 | 26 | @Override 27 | protected Class getRootClass() { 28 | return long.class; 29 | } 30 | 31 | @Override 32 | protected Object createDefaultConcreteObject() { 33 | return defaultValue; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/mapreduce/strategy/provider/LongObjectReduceProvider.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.strategy.provider; 2 | 3 | import org.springframework.core.annotation.Order; 4 | 5 | /** 6 | * @author xudeyou 7 | */ 8 | @Order(219) 9 | public class LongObjectReduceProvider extends AbstractReduceProvider { 10 | 11 | private Long defaultValue = 0l; 12 | 13 | @Override 14 | protected Object addToReduceResult(Object reduceCollection, Object shardingResult) { 15 | Long r = (Long) reduceCollection; 16 | Long s = (Long) shardingResult; 17 | r = r + s; 18 | return r; 19 | } 20 | 21 | @Override 22 | protected Class getDefaultConcreteClass() { 23 | return Long.class; 24 | } 25 | 26 | @Override 27 | protected Class getRootClass() { 28 | return Long.class; 29 | } 30 | 31 | @Override 32 | protected Object createDefaultConcreteObject() { 33 | return defaultValue; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/mapreduce/strategy/provider/FloatBasicReduceProvider.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.strategy.provider; 2 | 3 | import org.springframework.core.annotation.Order; 4 | 5 | /** 6 | * @author xudeyou 7 | */ 8 | @Order(238) 9 | public class FloatBasicReduceProvider extends AbstractReduceProvider { 10 | 11 | private Float defaultValue = 0.0f; 12 | 13 | @Override 14 | protected Object addToReduceResult(Object reduceCollection, Object shardingResult) { 15 | Float r = (Float) reduceCollection; 16 | Float s = (Float) shardingResult; 17 | r = r + s; 18 | return r; 19 | } 20 | 21 | @Override 22 | protected Class getDefaultConcreteClass() { 23 | return float.class; 24 | } 25 | 26 | @Override 27 | protected Class getRootClass() { 28 | return float.class; 29 | } 30 | 31 | @Override 32 | protected Object createDefaultConcreteObject() { 33 | return defaultValue; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/mapreduce/strategy/provider/FloatObjectReduceProvider.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.strategy.provider; 2 | 3 | import org.springframework.core.annotation.Order; 4 | 5 | /** 6 | * @author xudeyou 7 | */ 8 | @Order(239) 9 | public class FloatObjectReduceProvider extends AbstractReduceProvider { 10 | 11 | private Float defaultValue = 0.0f; 12 | 13 | @Override 14 | protected Object addToReduceResult(Object reduceCollection, Object shardingResult) { 15 | Float r = (Float) reduceCollection; 16 | Float s = (Float) shardingResult; 17 | r = r + s; 18 | return r; 19 | } 20 | 21 | @Override 22 | protected Class getDefaultConcreteClass() { 23 | return Float.class; 24 | } 25 | 26 | @Override 27 | protected Class getRootClass() { 28 | return Float.class; 29 | } 30 | 31 | @Override 32 | protected Object createDefaultConcreteObject() { 33 | return defaultValue; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/mapreduce/strategy/provider/DoubleBasicReduceProvider.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.strategy.provider; 2 | 3 | import org.springframework.core.annotation.Order; 4 | 5 | /** 6 | * @author xudeyou 7 | */ 8 | @Order(228) 9 | public class DoubleBasicReduceProvider extends AbstractReduceProvider { 10 | 11 | private Double defaultValue = 0.0; 12 | 13 | @Override 14 | protected Object addToReduceResult(Object reduceCollection, Object shardingResult) { 15 | Double r = (Double) reduceCollection; 16 | Double s = (Double) shardingResult; 17 | r = r + s; 18 | return r; 19 | } 20 | 21 | @Override 22 | protected Class getDefaultConcreteClass() { 23 | return double.class; 24 | } 25 | 26 | @Override 27 | protected Class getRootClass() { 28 | return double.class; 29 | } 30 | 31 | @Override 32 | protected Object createDefaultConcreteObject() { 33 | return defaultValue; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/mapreduce/strategy/provider/IntegerReduceProvider.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.strategy.provider; 2 | 3 | import org.springframework.core.annotation.Order; 4 | 5 | /** 6 | * @author xudeyou 7 | */ 8 | @Order(209) 9 | public class IntegerReduceProvider extends AbstractReduceProvider { 10 | 11 | private Integer defaultValue = 0; 12 | 13 | @Override 14 | protected Object addToReduceResult(Object reduceCollection, Object shardingResult) { 15 | Integer r = (Integer) reduceCollection; 16 | Integer s = (Integer) shardingResult; 17 | r = r + s; 18 | return r; 19 | } 20 | 21 | @Override 22 | protected Class getDefaultConcreteClass() { 23 | return Integer.class; 24 | } 25 | 26 | @Override 27 | protected Class getRootClass() { 28 | return Integer.class; 29 | } 30 | 31 | @Override 32 | protected Object createDefaultConcreteObject() { 33 | return defaultValue; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/mapreduce/annotation/MapReduce.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | 10 | /** 11 | * if the annotation method has a return value,then the aggregation result will replace the return value 12 | * if 13 | * @author deyou 14 | * 15 | */ 16 | @Documented 17 | @Retention(RetentionPolicy.RUNTIME) 18 | @Target(ElementType.METHOD) 19 | public @interface MapReduce { 20 | /** 21 | * SpEl referent to a instance that implements AggregationStrategy 22 | * @return 23 | */ 24 | String reduceStrategy() default ""; 25 | 26 | String dsSet() default ""; 27 | 28 | /** 29 | * is read from master only 30 | * @return 31 | */ 32 | boolean isForceMaster() default false; 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/mapreduce/strategy/provider/DoubleObjectReduceProvider.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.strategy.provider; 2 | 3 | import org.springframework.core.annotation.Order; 4 | 5 | /** 6 | * @author xudeyou 7 | */ 8 | @Order(229) 9 | public class DoubleObjectReduceProvider extends AbstractReduceProvider { 10 | 11 | private Double defaultValue = 0.0; 12 | 13 | @Override 14 | protected Object addToReduceResult(Object reduceCollection, Object shardingResult) { 15 | Double r = (Double) reduceCollection; 16 | Double s = (Double) shardingResult; 17 | r = r + s; 18 | return r; 19 | } 20 | 21 | @Override 22 | protected Class getDefaultConcreteClass() { 23 | return Double.class; 24 | } 25 | 26 | @Override 27 | protected Class getRootClass() { 28 | return Double.class; 29 | } 30 | 31 | @Override 32 | protected Object createDefaultConcreteObject() { 33 | return defaultValue; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/mapreduce/strategy/provider/SetReduceProvider.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.strategy.provider; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | 6 | import org.springframework.core.annotation.Order; 7 | 8 | /** 9 | * @author xudeyou 10 | */ 11 | @Order(10) 12 | public class SetReduceProvider extends AbstractReduceProvider { 13 | 14 | @SuppressWarnings("unchecked") 15 | @Override 16 | protected Object addToReduceResult(Object reduceCollection, Object shardingResult) { 17 | Set r = (Set) reduceCollection; 18 | Set s = (Set) shardingResult; 19 | r.addAll(s); 20 | return r; 21 | } 22 | 23 | @Override 24 | protected Class getRootClass() { 25 | return Set.class; 26 | } 27 | 28 | @Override 29 | protected Class getDefaultConcreteClass() { 30 | return HashSet.class; 31 | } 32 | 33 | @Override 34 | protected Object createDefaultConcreteObject() { 35 | return new HashSet<>(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/mapreduce/strategy/provider/ListReduceProvider.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.strategy.provider; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.springframework.core.annotation.Order; 7 | 8 | /** 9 | * @author xudeyou 10 | */ 11 | @Order(0) 12 | public class ListReduceProvider extends AbstractReduceProvider { 13 | 14 | @SuppressWarnings("unchecked") 15 | @Override 16 | protected Object addToReduceResult(Object reduceCollection, Object shardingResult) { 17 | List r = (List) reduceCollection; 18 | List s = (List) shardingResult; 19 | r.addAll(s); 20 | return r; 21 | } 22 | 23 | @Override 24 | protected Class getRootClass() { 25 | return List.class; 26 | } 27 | 28 | @Override 29 | protected Class getDefaultConcreteClass() { 30 | return ArrayList.class; 31 | } 32 | 33 | @SuppressWarnings("rawtypes") 34 | @Override 35 | protected Object createDefaultConcreteObject() { 36 | return new ArrayList(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/mapreduce/strategy/provider/CollectionReduceProvider.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.strategy.provider; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | 6 | import org.springframework.core.annotation.Order; 7 | 8 | /** 9 | * @author xudeyou 10 | */ 11 | @Order(99) 12 | public class CollectionReduceProvider extends AbstractReduceProvider { 13 | 14 | @SuppressWarnings("unchecked") 15 | @Override 16 | protected Object addToReduceResult(Object reduceCollection, Object shardingResult) { 17 | Collection r = (Collection) reduceCollection; 18 | Collection s = (Collection) shardingResult; 19 | r.addAll(s); 20 | return r; 21 | } 22 | 23 | @Override 24 | protected Class getRootClass() { 25 | return Collection.class; 26 | } 27 | 28 | @Override 29 | protected Class getDefaultConcreteClass() { 30 | return ArrayList.class; 31 | } 32 | 33 | @Override 34 | protected Object createDefaultConcreteObject() { 35 | return new ArrayList<>(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/mapreduce/strategy/provider/KeyValueMapReduceProvider.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.strategy.provider; 2 | 3 | import java.util.Map; 4 | 5 | import org.springframework.core.annotation.Order; 6 | 7 | import com.gs.collections.impl.map.mutable.ConcurrentHashMap; 8 | 9 | /** 10 | * @author xudeyou 11 | */ 12 | @Order(199) 13 | public class KeyValueMapReduceProvider extends AbstractReduceProvider { 14 | 15 | @SuppressWarnings("unchecked") 16 | @Override 17 | protected Object addToReduceResult(Object reduceCollection, Object shardingResult) { 18 | Map r = (Map) reduceCollection; 19 | Map s = (Map) shardingResult; 20 | r.putAll(s); 21 | return r; 22 | } 23 | 24 | 25 | @Override 26 | protected Class getRootClass() { 27 | return Map.class; 28 | } 29 | 30 | @Override 31 | protected Class getDefaultConcreteClass() { 32 | return ConcurrentHashMap.class; 33 | } 34 | 35 | @Override 36 | protected Object createDefaultConcreteObject() { 37 | return new ConcurrentHashMap<>(); 38 | } 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/readonly/RoundRobinReadonlyDsSelectStrategy.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.readonly; 2 | 3 | import java.util.concurrent.ConcurrentHashMap; 4 | import java.util.concurrent.atomic.AtomicInteger; 5 | 6 | /** 7 | * @author xudeyou 8 | */ 9 | public class RoundRobinReadonlyDsSelectStrategy implements ReadOnlyDsSelectStrategy { 10 | 11 | private static final int CHANGE_THRESHOLD = Integer.MAX_VALUE -100000; 12 | private ConcurrentHashMap map = new ConcurrentHashMap<>(); 13 | 14 | @Override 15 | public Integer select(int partionNumber, int slaveCount) { 16 | if(slaveCount == 0){ 17 | return null; 18 | } 19 | 20 | AtomicInteger computeIfAbsent = map.computeIfAbsent(partionNumber, k->new AtomicInteger(0)); 21 | 22 | int incrementAndGet = computeIfAbsent.incrementAndGet(); 23 | 24 | if(incrementAndGet > CHANGE_THRESHOLD){ 25 | boolean compareAndSet = computeIfAbsent.compareAndSet(incrementAndGet, 0); 26 | if(!compareAndSet){ 27 | //ignore it,do it in next loop 28 | } 29 | } 30 | 31 | return incrementAndGet % slaveCount; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/sharding/aspect/ShardingAspect.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.sharding.aspect; 2 | 3 | import org.aspectj.lang.ProceedingJoinPoint; 4 | import org.aspectj.lang.annotation.Around; 5 | import org.aspectj.lang.annotation.Aspect; 6 | import org.easydevelop.sharding.annotation.ShardingContext; 7 | import org.springframework.core.annotation.Order; 8 | 9 | /** 10 | * @author xudeyou 11 | */ 12 | @Aspect 13 | @Order(-3) 14 | public class ShardingAspect { 15 | 16 | private static ThreadLocal shardingContext = new ThreadLocal<>(); 17 | 18 | public static ShardingContext getShardingContext() { 19 | return shardingContext.get(); 20 | } 21 | 22 | 23 | 24 | @Around("@within(shardingContext)") 25 | public Object around(ProceedingJoinPoint jp,ShardingContext shardingContext) throws Throwable{ 26 | 27 | //为带有ShardingContext注解类的方法调用时带上ShardingContext注解 28 | ShardingContext lastShardingContext = ShardingAspect.shardingContext.get(); 29 | 30 | try { 31 | ShardingAspect.shardingContext.set(shardingContext); 32 | return jp.proceed(); 33 | } catch (Throwable e) { 34 | throw e; 35 | } finally{ 36 | ShardingAspect.shardingContext.set(lastShardingContext); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/generateid/annotation/GenerateId.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.generateid.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | 10 | @Documented 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.METHOD) 13 | public @interface GenerateId { 14 | 15 | /** 16 | * SpEL expressions 17 | * ex: 18 | * void test(String str1,Integer id) : defaultKeyNames={"['id']"} 19 | * void test(String str1,Order order) : defaultKeyNames={"['order'].id"} 20 | * @return 21 | */ 22 | String[] keyEls() default {}; 23 | 24 | /** 25 | * SpEl referent to a instance that implements KeyGenerateStrategy 26 | * @return 27 | */ 28 | String strategy() default ""; 29 | 30 | /** 31 | * -1 not allowd 32 | * 0 not defined 33 | * 1 allowd 34 | * @return 35 | */ 36 | int allowCallerDefinedKeyValue() default 0; 37 | 38 | /** 39 | * provide more information for KeyGenerateStrategy 40 | * 41 | * SpEL expressions 42 | * ex: 43 | * void test(String str1,Integer id) : defaultKeyNames={"['id']"} 44 | * void test(String str1,Order order) : defaultKeyNames={"['order'].id"} 45 | * @return 46 | */ 47 | String[] strategyMetadataEls() default {}; 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/select/annotation/SelectDataSource.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.select.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * to declare the method should be sharding 11 | * @author deyou 12 | * 13 | */ 14 | @Documented 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Target(ElementType.METHOD) 17 | public @interface SelectDataSource { 18 | 19 | /** 20 | * declare which parameter(s) should use for sharding judgment. 21 | * if not specified,use the default values declare in the class 22 | * SpEL expressions 23 | * ex: 24 | * void test(String str1,Integer id) : defaultKeyNames={"['id']"} 25 | * void test(String str1,Order order) : defaultKeyNames={"['order'].id"} 26 | * @return 27 | */ 28 | String[] keyNameEls() default {}; 29 | 30 | /** 31 | * SpEl referent to a instance that implements ShardingStrategy 32 | * @return 33 | */ 34 | String strategy() default ""; 35 | 36 | /** 37 | * declare the sharding data-sources 38 | * if not specified,use the default values declare in the class 39 | * @return 40 | */ 41 | String dsSet() default ""; 42 | 43 | /** 44 | * even though it's read-only transaction,use master datasource 45 | * @return 46 | */ 47 | boolean forceMaster() default false; 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/sharding/DataSourceSet.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.sharding; 2 | 3 | import java.util.List; 4 | 5 | import javax.sql.DataSource; 6 | 7 | /** 8 | * @author xudeyou 9 | */ 10 | public class DataSourceSet { 11 | 12 | private String datasourceSetName; 13 | 14 | private List masterDataSources; 15 | 16 | private List> slaveDataSources; 17 | 18 | public DataSourceSet(String datasourceSetName, List masterDataSources ,List> slaveDataSources) { 19 | super(); 20 | this.datasourceSetName = datasourceSetName; 21 | this.masterDataSources = masterDataSources; 22 | this.slaveDataSources = slaveDataSources; 23 | if(slaveDataSources != null && slaveDataSources.size() != masterDataSources.size()){ 24 | throw new IllegalArgumentException("the master number should be the same with the slave list's number"); 25 | } 26 | } 27 | 28 | public DataSourceSet(String datasourceSetName, List masterDataSources) { 29 | this(datasourceSetName, masterDataSources, null); 30 | } 31 | 32 | public String getDatasourceSetName() { 33 | return datasourceSetName; 34 | } 35 | 36 | public List getMasterDataSources() { 37 | return masterDataSources; 38 | } 39 | 40 | public List> getSlaveDataSources() { 41 | return slaveDataSources; 42 | } 43 | 44 | public void setSlaveDataSources(List> slaveDataSources) { 45 | this.slaveDataSources = slaveDataSources; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/org/easydevelop/mapreduce/stategy/IntReduceProviderTest.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.stategy; 2 | 3 | import org.easydevelop.business.TestApplicationConfig; 4 | import org.easydevelop.mapreduce.annotation.MapReduce; 5 | import org.easydevelop.sharding.annotation.ShardingContext; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.test.context.junit4.SpringRunner; 14 | 15 | 16 | 17 | /** 18 | * @author xudeyou 19 | */ 20 | @RunWith(SpringRunner.class) 21 | @SpringBootTest(classes={IntReduceProviderTest.class,TestApplicationConfig.class}) 22 | @Configuration 23 | public class IntReduceProviderTest { 24 | 25 | 26 | @Service("agTest") 27 | @ShardingContext(dataSourceSet="orderSet") 28 | public static class AgTest{ 29 | 30 | @MapReduce 31 | public int listTest1(){ 32 | return 1; 33 | } 34 | 35 | @MapReduce 36 | public int listTest2(int value){ 37 | return value; 38 | } 39 | } 40 | 41 | 42 | @Autowired 43 | private AgTest agTest; 44 | 45 | @Test 46 | public void listTest1(){ 47 | int agTest1 = agTest.listTest1(); 48 | Assert.assertTrue(agTest1 == 2); 49 | } 50 | 51 | @Test 52 | public void listTest2(){ 53 | int agTest1 = agTest.listTest2(2); 54 | Assert.assertTrue(agTest1 == 4); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/org/easydevelop/mapreduce/stategy/LongBasicReduceProviderTest.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.stategy; 2 | 3 | import org.easydevelop.business.TestApplicationConfig; 4 | import org.easydevelop.mapreduce.annotation.MapReduce; 5 | import org.easydevelop.sharding.annotation.ShardingContext; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.test.context.junit4.SpringRunner; 14 | 15 | 16 | 17 | /** 18 | * @author xudeyou 19 | */ 20 | @RunWith(SpringRunner.class) 21 | @SpringBootTest(classes={LongBasicReduceProviderTest.class,TestApplicationConfig.class}) 22 | @Configuration 23 | public class LongBasicReduceProviderTest { 24 | 25 | 26 | @Service("agTest") 27 | @ShardingContext(dataSourceSet="orderSet") 28 | public static class AgTest{ 29 | 30 | @MapReduce 31 | public long listTest1(){ 32 | return 1; 33 | } 34 | 35 | @MapReduce 36 | public long listTest2(int value){ 37 | return value; 38 | } 39 | } 40 | 41 | 42 | @Autowired 43 | private AgTest agTest; 44 | 45 | @Test 46 | public void listTest1(){ 47 | long agTest1 = agTest.listTest1(); 48 | Assert.assertTrue(agTest1 == 2); 49 | } 50 | 51 | @Test 52 | public void listTest2(){ 53 | long agTest1 = agTest.listTest2(2); 54 | Assert.assertTrue(agTest1 == 4); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/org/easydevelop/mapreduce/stategy/LongObjectReduceProviderTest.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.stategy; 2 | 3 | import org.easydevelop.business.TestApplicationConfig; 4 | import org.easydevelop.mapreduce.annotation.MapReduce; 5 | import org.easydevelop.sharding.annotation.ShardingContext; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.test.context.junit4.SpringRunner; 14 | 15 | 16 | 17 | /** 18 | * @author xudeyou 19 | */ 20 | @RunWith(SpringRunner.class) 21 | @SpringBootTest(classes={LongObjectReduceProviderTest.class,TestApplicationConfig.class}) 22 | @Configuration 23 | public class LongObjectReduceProviderTest { 24 | 25 | 26 | @Service("agTest") 27 | @ShardingContext(dataSourceSet="orderSet") 28 | public static class AgTest{ 29 | 30 | @MapReduce 31 | public Long listTest1(){ 32 | return 1l; 33 | } 34 | 35 | @MapReduce 36 | public Long listTest2(Long value){ 37 | return value; 38 | } 39 | } 40 | 41 | 42 | @Autowired 43 | private AgTest agTest; 44 | 45 | @Test 46 | public void listTest1(){ 47 | long agTest1 = agTest.listTest1(); 48 | Assert.assertTrue(agTest1 == 2); 49 | } 50 | 51 | @Test 52 | public void listTest2(){ 53 | Long agTest1 = agTest.listTest2(2l); 54 | Assert.assertTrue(agTest1 == 4); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/org/easydevelop/mapreduce/stategy/DoubleBasicReduceProviderTest.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.stategy; 2 | 3 | import org.easydevelop.business.TestApplicationConfig; 4 | import org.easydevelop.mapreduce.annotation.MapReduce; 5 | import org.easydevelop.sharding.annotation.ShardingContext; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.test.context.junit4.SpringRunner; 14 | 15 | 16 | 17 | /** 18 | * @author xudeyou 19 | */ 20 | @RunWith(SpringRunner.class) 21 | @SpringBootTest(classes={DoubleBasicReduceProviderTest.class,TestApplicationConfig.class}) 22 | @Configuration 23 | public class DoubleBasicReduceProviderTest { 24 | 25 | 26 | @Service("agTest") 27 | @ShardingContext(dataSourceSet="orderSet") 28 | public static class AgTest{ 29 | 30 | @MapReduce 31 | public double listTest1(){ 32 | return 0.1; 33 | } 34 | 35 | @MapReduce 36 | public double listTest2(double value){ 37 | return value; 38 | } 39 | } 40 | 41 | 42 | @Autowired 43 | private AgTest agTest; 44 | 45 | @Test 46 | public void listTest1(){ 47 | double agTest1 = agTest.listTest1(); 48 | Assert.assertTrue(agTest1 == 0.1 + 0.1); 49 | } 50 | 51 | @Test 52 | public void listTest2(){ 53 | double agTest1 = agTest.listTest2(0.1); 54 | Assert.assertTrue(agTest1 == 0.1+0.1); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/org/easydevelop/mapreduce/stategy/FloatBasicReduceProviderTest.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.stategy; 2 | 3 | import org.easydevelop.business.TestApplicationConfig; 4 | import org.easydevelop.mapreduce.annotation.MapReduce; 5 | import org.easydevelop.sharding.annotation.ShardingContext; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.test.context.junit4.SpringRunner; 14 | 15 | 16 | 17 | /** 18 | * @author xudeyou 19 | */ 20 | @RunWith(SpringRunner.class) 21 | @SpringBootTest(classes={FloatBasicReduceProviderTest.class,TestApplicationConfig.class}) 22 | @Configuration 23 | public class FloatBasicReduceProviderTest { 24 | 25 | 26 | @Service("agTest") 27 | @ShardingContext(dataSourceSet="orderSet") 28 | public static class AgTest{ 29 | 30 | @MapReduce 31 | public float listTest1(){ 32 | return 0.1f; 33 | } 34 | 35 | @MapReduce 36 | public float listTest2(float value){ 37 | return value; 38 | } 39 | } 40 | 41 | 42 | @Autowired 43 | private AgTest agTest; 44 | 45 | @Test 46 | public void listTest1(){ 47 | float agTest1 = agTest.listTest1(); 48 | Assert.assertTrue(agTest1 == 0.1f + 0.1f); 49 | } 50 | 51 | @Test 52 | public void listTest2(){ 53 | float agTest1 = agTest.listTest2(0.1f); 54 | Assert.assertTrue(agTest1 == 0.1f+0.1f); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/org/easydevelop/mapreduce/stategy/DoubleObjectReduceProviderTest.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.stategy; 2 | 3 | import org.easydevelop.business.TestApplicationConfig; 4 | import org.easydevelop.mapreduce.annotation.MapReduce; 5 | import org.easydevelop.sharding.annotation.ShardingContext; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.test.context.junit4.SpringRunner; 14 | 15 | 16 | 17 | /** 18 | * @author xudeyou 19 | */ 20 | @RunWith(SpringRunner.class) 21 | @SpringBootTest(classes={DoubleObjectReduceProviderTest.class,TestApplicationConfig.class}) 22 | @Configuration 23 | public class DoubleObjectReduceProviderTest { 24 | 25 | 26 | @Service("agTest") 27 | @ShardingContext(dataSourceSet="orderSet") 28 | public static class AgTest{ 29 | 30 | @MapReduce 31 | public Double listTest1(){ 32 | return 0.1; 33 | } 34 | 35 | @MapReduce 36 | public Double listTest2(Double value){ 37 | return value; 38 | } 39 | } 40 | 41 | 42 | @Autowired 43 | private AgTest agTest; 44 | 45 | @Test 46 | public void listTest1(){ 47 | double agTest1 = agTest.listTest1(); 48 | Assert.assertTrue(agTest1 == 0.1 + 0.1); 49 | } 50 | 51 | @Test 52 | public void listTest2(){ 53 | double agTest1 = agTest.listTest2(0.1); 54 | Assert.assertTrue(agTest1 == 0.1+0.1); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/org/easydevelop/mapreduce/stategy/FloatObjectReduceProviderTest.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.stategy; 2 | 3 | import org.easydevelop.business.TestApplicationConfig; 4 | import org.easydevelop.mapreduce.annotation.MapReduce; 5 | import org.easydevelop.sharding.annotation.ShardingContext; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.test.context.junit4.SpringRunner; 14 | 15 | 16 | 17 | /** 18 | * @author xudeyou 19 | */ 20 | @RunWith(SpringRunner.class) 21 | @SpringBootTest(classes={FloatObjectReduceProviderTest.class,TestApplicationConfig.class}) 22 | @Configuration 23 | public class FloatObjectReduceProviderTest { 24 | 25 | 26 | @Service("agTest") 27 | @ShardingContext(dataSourceSet="orderSet") 28 | public static class AgTest{ 29 | 30 | @MapReduce 31 | public Float listTest1(){ 32 | return 0.1f; 33 | } 34 | 35 | @MapReduce 36 | public Float listTest2(Float value){ 37 | return value; 38 | } 39 | } 40 | 41 | 42 | @Autowired 43 | private AgTest agTest; 44 | 45 | @Test 46 | public void listTest1(){ 47 | Float agTest1 = agTest.listTest1(); 48 | Assert.assertTrue(agTest1 == 0.1f + 0.1f); 49 | } 50 | 51 | @Test 52 | public void listTest2(){ 53 | Float agTest1 = agTest.listTest2(0.1f); 54 | Assert.assertTrue(agTest1 == 0.1f+0.1f); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/mapreduce/strategy/provider/AbstractReduceProvider.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.strategy.provider; 2 | 3 | import java.util.List; 4 | 5 | import org.easydevelop.common.ReflectHelper; 6 | 7 | /** 8 | * @author xudeyou 9 | */ 10 | public abstract class AbstractReduceProvider implements ReduceProvider { 11 | 12 | @Override 13 | public Object reduce(List shardingResultList, Class shardingResultClass, Class reduceResultClass) { 14 | 15 | Object reduceCollection = createReduceClassObject(reduceResultClass); 16 | for(Object shardingResult :shardingResultList){ 17 | reduceCollection = addToReduceResult(reduceCollection, shardingResult); 18 | } 19 | return reduceCollection; 20 | } 21 | 22 | protected abstract Object addToReduceResult(Object reduceCollection, Object shardingResult); 23 | 24 | @Override 25 | public boolean support(Class shardingResultClass, Class reduceResultClass) { 26 | if (getRootClass().isAssignableFrom(shardingResultClass) 27 | && getRootClass().isAssignableFrom(reduceResultClass) 28 | && (createReduceClassObject(reduceResultClass) != null) 29 | ) { 30 | return true; 31 | } 32 | return false; 33 | } 34 | 35 | protected Object createReduceClassObject(Class reduceResultClass) { 36 | if(ReflectHelper.canInitInstanceWithNoParameter(reduceResultClass)){ 37 | return ReflectHelper.createInstance(reduceResultClass); 38 | }else{ 39 | if(reduceResultClass.isAssignableFrom(getDefaultConcreteClass())){ 40 | return createDefaultConcreteObject(); 41 | }else{ 42 | return null; 43 | } 44 | } 45 | } 46 | 47 | protected abstract Class getDefaultConcreteClass(); 48 | 49 | protected abstract Object createDefaultConcreteObject(); 50 | 51 | protected abstract Class getRootClass(); 52 | 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/org/easydevelop/mapreduce/stategy/IntegerReduceProviderTest.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.stategy; 2 | 3 | import org.easydevelop.business.TestApplicationConfig; 4 | import org.easydevelop.mapreduce.annotation.MapReduce; 5 | import org.easydevelop.sharding.annotation.ShardingContext; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.test.context.junit4.SpringRunner; 14 | 15 | 16 | 17 | /** 18 | * @author xudeyou 19 | */ 20 | @RunWith(SpringRunner.class) 21 | @SpringBootTest(classes={IntegerReduceProviderTest.class,TestApplicationConfig.class}) 22 | @Configuration 23 | public class IntegerReduceProviderTest { 24 | 25 | 26 | @Service("agTest") 27 | @ShardingContext(dataSourceSet="orderSet") 28 | public static class AgTest{ 29 | 30 | @MapReduce 31 | public Integer listTest1(){ 32 | return 1; 33 | } 34 | 35 | @MapReduce 36 | public Integer listTest2(int value){ 37 | return value; 38 | } 39 | 40 | @MapReduce 41 | public Integer listTest3(){ 42 | return null; 43 | } 44 | } 45 | 46 | 47 | @Autowired 48 | private AgTest agTest; 49 | 50 | @Test 51 | public void test1(){ 52 | int agTest1 = agTest.listTest1(); 53 | Assert.assertTrue(agTest1 == 2); 54 | } 55 | 56 | @Test 57 | public void test2(){ 58 | int agTest1 = agTest.listTest2(2); 59 | Assert.assertTrue(agTest1 == 4); 60 | } 61 | 62 | @Test 63 | public void test3(){ 64 | boolean exception = false; 65 | try { 66 | agTest.listTest3(); 67 | } catch (Exception e) { 68 | exception = true; 69 | } 70 | Assert.assertTrue(exception); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/org/easydevelop/business/BusinessTest.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.business; 2 | 3 | import java.util.List; 4 | 5 | import org.easydevelop.business.domain.User; 6 | import org.easydevelop.business.service.UserServceImpl; 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.context.annotation.Configuration; 13 | import org.springframework.test.context.junit4.SpringRunner; 14 | 15 | 16 | 17 | /** 18 | * @author xudeyou 19 | */ 20 | @RunWith(SpringRunner.class) 21 | @SpringBootTest(classes={BusinessTest.class,TestApplicationConfig.class}) 22 | @Configuration 23 | public class BusinessTest { 24 | 25 | @Autowired 26 | private UserServceImpl userService; 27 | 28 | @Test 29 | public void testBusiness(){ 30 | 31 | //delete master's users 32 | int deleteAllUsers = userService.deleteAllUsers(); 33 | System.out.println(deleteAllUsers); 34 | 35 | User user1 = new User(); 36 | user1.setName("user1"); 37 | userService.saveUser(user1); 38 | 39 | User user2 = new User(); 40 | user2.setName("user2"); 41 | userService.saveUser(user2); 42 | 43 | User user3 = new User(); 44 | user3.setName("user3"); 45 | userService.saveUser(user3); 46 | 47 | userService.deleteUser(user1.getUserId()); 48 | 49 | user3.setName("user3-new"); 50 | userService.updateUser(user3); 51 | 52 | User findUser = userService.findUserByMaster(user3.getUserId()); 53 | Assert.assertTrue(findUser.getName().equals("user3-new")); 54 | 55 | List findAllUsers = userService.findAllUsersByMaster(); 56 | Assert.assertTrue(findAllUsers.size() == 2); 57 | 58 | User readOnlyUser = userService.findUser(1001); 59 | Assert.assertTrue(readOnlyUser != null); 60 | 61 | List readOnlyUsers = userService.findAllUsers(); 62 | Assert.assertTrue(readOnlyUsers.get(0).getUserId().equals(1000)); 63 | 64 | } 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/mapreduce/aspect/ReduceResultHolder.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.aspect; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | /** 7 | * when using Aggregation,if your aggregation result type different from sharding result type, 8 | * you can place this instance in the method parameter and set the method's return type to void 9 | * and then the aggregation result will return by getResult() 10 | */ 11 | public class ReduceResultHolder { 12 | 13 | private Class reduceResultClass; 14 | private Class shardingResultClass; 15 | 16 | public ReduceResultHolder(Class reduceResultClass, Class shardingResultClass) { 17 | super(); 18 | this.reduceResultClass = reduceResultClass; 19 | this.shardingResultClass = shardingResultClass; 20 | } 21 | 22 | public Class getReduceResultClass() { 23 | return reduceResultClass; 24 | } 25 | 26 | public Class getShardingResultClass() { 27 | return shardingResultClass; 28 | } 29 | 30 | private ThreadLocal shardingResultPosition = new ThreadLocal<>(); 31 | private ThreadLocal isResultSet = new ThreadLocal<>(); 32 | 33 | // private ArrayList shardingResults; 34 | private Object[] shardingResults; 35 | 36 | private R result; 37 | 38 | public R getResult(){ 39 | return result; 40 | } 41 | 42 | public void setShardingResult(S obj){ 43 | Integer pos = shardingResultPosition.get(); 44 | shardingResults[pos] = obj; 45 | isResultSet.set(true); 46 | } 47 | 48 | @SuppressWarnings("unchecked") 49 | public List getShardingResults(){ 50 | return (List) Arrays.asList(shardingResults); 51 | } 52 | 53 | @SuppressWarnings("unchecked") 54 | void setResult(Object result){ 55 | this.result = (R) result; 56 | } 57 | 58 | void setShardingResultPosition(Integer position){ 59 | shardingResultPosition.set(position); 60 | } 61 | 62 | void init(int ShardingCount){ 63 | shardingResults = new Object[ShardingCount]; 64 | } 65 | 66 | boolean isShardingResultSet(){ 67 | Boolean set = isResultSet.get(); 68 | if(set == null || !set){ 69 | return false; 70 | }else{ 71 | return true; 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/common/ReflectHelper.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.common; 2 | 3 | import java.lang.reflect.Constructor; 4 | import java.lang.reflect.InvocationTargetException; 5 | import java.lang.reflect.Modifier; 6 | import java.util.Optional; 7 | 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import com.gs.collections.impl.map.mutable.ConcurrentHashMap; 12 | 13 | /** 14 | * @author xudeyou 15 | */ 16 | public class ReflectHelper { 17 | 18 | private static Logger LOG = LoggerFactory.getLogger(ReflectHelper.class); 19 | 20 | private static ConcurrentHashMap, Optional>> mapConstructor = new ConcurrentHashMap<>(); 21 | private static boolean hasPublicParameterLessConstructor(Class clazz){ 22 | return getPublicParameterLessConstructor(clazz).isPresent(); 23 | } 24 | 25 | public static boolean canInitInstanceWithNoParameter(Class clazz){ 26 | if(clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers())){ 27 | return false; 28 | } 29 | return hasPublicParameterLessConstructor(clazz); 30 | } 31 | 32 | private static Optional> getPublicParameterLessConstructor(Class clazz){ 33 | Optional> result = mapConstructor.get(clazz); 34 | if(result == null){ 35 | result = mapConstructor.computeIfAbsent(clazz, c->{ 36 | try { 37 | Constructor constructor = clazz.getConstructor(); 38 | return Optional.of(constructor); 39 | } catch (NoSuchMethodException | SecurityException e) { 40 | LOG.info(clazz.getName() + " has no PublicParameterLessConstructor"); 41 | return Optional.empty(); 42 | } 43 | }); 44 | } 45 | return result; 46 | } 47 | 48 | 49 | @SuppressWarnings("unchecked") 50 | public static R createInstance(Class clazz){ 51 | Optional> optional = getPublicParameterLessConstructor(clazz); 52 | if(optional.isPresent()){ 53 | Constructor constructor = optional.get(); 54 | try { 55 | return (R) constructor.newInstance(); 56 | } catch (InstantiationException | IllegalAccessException | IllegalArgumentException 57 | | InvocationTargetException e) { 58 | throw new RuntimeException("Illegal Call!",e); 59 | } 60 | } else { 61 | throw new RuntimeException("Illegal Call!"); 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/org/easydevelop/business/service/UserServceImpl.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.business.service; 2 | 3 | import java.util.List; 4 | 5 | import org.easydevelop.business.TestApplicationConfig; 6 | import org.easydevelop.business.dao.UserDaoImpl; 7 | import org.easydevelop.business.domain.User; 8 | import org.easydevelop.generateid.annotation.GenerateId; 9 | import org.easydevelop.select.annotation.SelectDataSource; 10 | import org.easydevelop.sharding.annotation.ShardingContext; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.transaction.annotation.Transactional; 14 | 15 | /** 16 | * @author xudeyou 17 | */ 18 | @Service 19 | @ShardingContext(dataSourceSet="orderSet",shardingKeyEls="[user].userId",shardingStrategy=TestApplicationConfig.BY_USER_ID_MOD,generateIdStrategy=TestApplicationConfig.INT_INCREASE,generateIdEls="[user].userId") 20 | public class UserServceImpl { 21 | 22 | @Autowired 23 | private UserDaoImpl userDao; 24 | 25 | @Transactional 26 | @SelectDataSource 27 | public void updateUser(User user){ 28 | userDao.updateUser(user); 29 | } 30 | 31 | @Transactional 32 | @SelectDataSource 33 | @GenerateId 34 | public void saveUser(User user){ 35 | userDao.saveUser(user); 36 | } 37 | 38 | @Transactional 39 | @SelectDataSource(keyNameEls="[userId]") 40 | public void deleteUser(int userId){ 41 | userDao.deleteUser(userId); 42 | } 43 | 44 | @Transactional(readOnly=true) 45 | @SelectDataSource(keyNameEls="[userId]") 46 | public User findUser(int userId){ 47 | return userDao.findUser(userId); 48 | } 49 | 50 | @Transactional(readOnly=true) 51 | @SelectDataSource(keyNameEls="[userId]",forceMaster=true) 52 | public User findUserByMaster(int userId){ 53 | return userDao.findUser(userId); 54 | } 55 | 56 | public List findAllUsersByMaster(){ 57 | return userDao.findAllUsersByMaster(); 58 | } 59 | 60 | public List findAllUsers(){ 61 | return userDao.findAllUsers(); 62 | } 63 | 64 | public double calcUserAvgAge(){ 65 | List allUsers = userDao.findAllUsers(); 66 | return allUsers.stream().mapToInt(u->u.getUserId()).average().getAsDouble(); 67 | } 68 | 69 | public int deleteAllUsers(){ 70 | return userDao.deleteAllUsers(); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/sharding/annotation/ShardingContext.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.sharding.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * to declare some methods in the class should be sharding 11 | * @author deyou 12 | * 13 | */ 14 | @Documented 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Target(ElementType.TYPE) 17 | public @interface ShardingContext { 18 | 19 | /** 20 | * declare the default sharding data-sources 21 | * @return 22 | */ 23 | String dataSourceSet() default ""; 24 | 25 | 26 | /** 27 | * SpEl referent to a instance that implements ShardingStrategy 28 | * @return 29 | */ 30 | String shardingStrategy() default ""; 31 | 32 | /** 33 | * declare which parameter(s) should use for sharding judgment by default. 34 | * SpEL expressions 35 | * ex: 36 | * void test(String str1,Integer id) : defaultKeyNames={"['id']"} 37 | * void test(String str1,Order order) : defaultKeyNames={"['order'].id"} 38 | * @return 39 | */ 40 | String[] shardingKeyEls() default {}; 41 | 42 | /** 43 | * SpEl referent to a instance that implements GenerateKeyStrategy 44 | * @return 45 | */ 46 | String generateIdStrategy() default ""; 47 | 48 | /** 49 | * SpEL expressions 50 | * ex: 51 | * void test(String str1,Integer id) : defaultKeyNames={"['id']"} 52 | * void test(String str1,Order order) : defaultKeyNames={"['order'].id"} 53 | * @return 54 | */ 55 | String[] generateIdEls() default {}; 56 | 57 | /** 58 | * 当传入的参数的ID对应的位置已经有值,是否允许使用传入来的值作为ID 59 | * -1 not allowd 60 | * 0 not defined 61 | * 1 allowd 62 | * @return 63 | */ 64 | int generateIdByCaller() default -1; 65 | 66 | /** 67 | * provide more information for KeyGenerateStrategy 68 | * 69 | * SpEL expressions 70 | * ex: 71 | * void test(String str1,Integer id) : defaultKeyNames={"['id']"} 72 | * void test(String str1,Order order) : defaultKeyNames={"['order'].id"} 73 | * @return 74 | */ 75 | String[] generateIdMetadataEls() default {}; 76 | 77 | /** 78 | * SpEl referent to a instance that implements ReduceStrategy 79 | * @return 80 | */ 81 | String reduceStrategy() default "@defalutReduceStrategy"; 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/org/easydevelop/business/dao/UserDaoImpl.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.business.dao; 2 | 3 | import java.util.List; 4 | 5 | import org.easydevelop.business.TestApplicationConfig; 6 | import org.easydevelop.business.domain.User; 7 | import org.easydevelop.mapreduce.annotation.MapReduce; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.jdbc.core.BeanPropertyRowMapper; 10 | import org.springframework.jdbc.core.JdbcTemplate; 11 | import org.springframework.stereotype.Component; 12 | import org.springframework.transaction.annotation.Transactional; 13 | import org.springframework.util.Assert; 14 | 15 | /** 16 | * @author xudeyou 17 | */ 18 | @Component 19 | public class UserDaoImpl { 20 | 21 | @Autowired 22 | private JdbcTemplate jdbcTemplate; 23 | 24 | public void saveUser(User user){ 25 | int update = jdbcTemplate.update("INSERT INTO `user` (`user_id`, `name`) VALUES (?, ?)",user.getUserId(),user.getName()); 26 | Assert.isTrue(update == 1,"it should be inserted!"); 27 | } 28 | 29 | public void updateUser(User user){ 30 | int update = jdbcTemplate.update("UPDATE `user` SET `name`=? WHERE `user_id`=?;",user.getName(),user.getUserId()); 31 | Assert.isTrue(update == 1,"it should be updated!"); 32 | } 33 | 34 | public void deleteUser(int userId) { 35 | int update = jdbcTemplate.update("DELETE FROM `user` WHERE `user_id`=?;",userId); 36 | Assert.isTrue(update == 1,"it should be deleted!"); 37 | } 38 | 39 | private BeanPropertyRowMapper rowMapper = new BeanPropertyRowMapper<>(User.class); 40 | public User findUser(int userId){ 41 | return jdbcTemplate.queryForObject("SELECT * FROM user WHERE user_id = ?", new Object[]{userId}, rowMapper); 42 | } 43 | 44 | @Transactional 45 | @MapReduce(reduceStrategy=TestApplicationConfig.AGGREGATION_USER_ORDER_BY_USER_ID) 46 | public List findAllUsersByMaster(){ 47 | return jdbcTemplate.query("SELECT * FROM user", rowMapper); 48 | } 49 | 50 | @Transactional(readOnly=true) 51 | @MapReduce(reduceStrategy=TestApplicationConfig.AGGREGATION_USER_ORDER_BY_USER_ID) 52 | public List findAllUsers(){ 53 | return jdbcTemplate.query("SELECT * FROM user", rowMapper); 54 | } 55 | 56 | @Transactional 57 | @MapReduce(reduceStrategy=TestApplicationConfig.UPDATE_COUNT_ADD) 58 | public int deleteAllUsers(){ 59 | int update = jdbcTemplate.update("delete from user"); 60 | System.out.println(update); 61 | return update; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/org/easydevelop/mapreduce/stategy/ListReduceProviderTest.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.stategy; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.Collection; 6 | import java.util.HashSet; 7 | 8 | import org.easydevelop.business.TestApplicationConfig; 9 | import org.easydevelop.mapreduce.annotation.MapReduce; 10 | import org.easydevelop.sharding.annotation.ShardingContext; 11 | import org.junit.Assert; 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.boot.test.context.SpringBootTest; 16 | import org.springframework.context.annotation.Configuration; 17 | import org.springframework.stereotype.Service; 18 | import org.springframework.test.context.junit4.SpringRunner; 19 | 20 | 21 | 22 | /** 23 | * @author xudeyou 24 | */ 25 | @RunWith(SpringRunner.class) 26 | @SpringBootTest(classes={ListReduceProviderTest.class,TestApplicationConfig.class}) 27 | @Configuration 28 | public class ListReduceProviderTest { 29 | 30 | 31 | @Service("agTest") 32 | @ShardingContext(dataSourceSet="orderSet") 33 | public static class AgTest{ 34 | 35 | @MapReduce 36 | public Collection listTest1(){ 37 | return Arrays.asList(1); 38 | } 39 | 40 | @MapReduce 41 | public Collection listTest2(int value){ 42 | ArrayList arrayList = new ArrayList(); 43 | arrayList.add(value); 44 | return arrayList; 45 | } 46 | 47 | @MapReduce 48 | public Collection listTest3(){ 49 | return null; 50 | } 51 | 52 | @MapReduce 53 | public Collection listTest4(){ 54 | HashSet set = new HashSet(); 55 | set.add(1); 56 | return set; 57 | } 58 | 59 | } 60 | 61 | 62 | @Autowired 63 | private AgTest agTest; 64 | 65 | @Test 66 | public void listTest1(){ 67 | Collection agTest1 = agTest.listTest1(); 68 | Assert.assertTrue(agTest1.size() == 2); 69 | } 70 | 71 | @Test 72 | public void listTest2(){ 73 | Collection agTest1 = agTest.listTest2(1); 74 | Assert.assertTrue(agTest1.size() == 2); 75 | } 76 | 77 | @Test 78 | public void listTest3(){ 79 | boolean exception = false; 80 | try { 81 | agTest.listTest3(); 82 | } catch (Exception e) { 83 | exception = true; 84 | } 85 | Assert.assertTrue(exception); 86 | } 87 | 88 | @Test 89 | public void listTest4(){ 90 | Collection listTest4 = agTest.listTest4(); 91 | Assert.assertTrue(listTest4.size() == 2); 92 | } 93 | 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/test/java/org/easydevelop/mapreduce/stategy/CollectionReduceProviderTest.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.stategy; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | 8 | import org.easydevelop.business.TestApplicationConfig; 9 | import org.easydevelop.mapreduce.annotation.MapReduce; 10 | import org.easydevelop.sharding.annotation.ShardingContext; 11 | import org.junit.Assert; 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.boot.test.context.SpringBootTest; 16 | import org.springframework.context.annotation.Configuration; 17 | import org.springframework.stereotype.Service; 18 | import org.springframework.test.context.junit4.SpringRunner; 19 | 20 | 21 | 22 | /** 23 | * @author xudeyou 24 | */ 25 | @RunWith(SpringRunner.class) 26 | @SpringBootTest(classes={CollectionReduceProviderTest.class,TestApplicationConfig.class}) 27 | @Configuration 28 | public class CollectionReduceProviderTest { 29 | 30 | 31 | @Service("agTest") 32 | @ShardingContext(dataSourceSet="orderSet") 33 | public static class AgTest{ 34 | 35 | @MapReduce 36 | public List listTest1(){ 37 | return Arrays.asList(1); 38 | } 39 | 40 | @MapReduce 41 | public List listTest2(int value){ 42 | ArrayList arrayList = new ArrayList(); 43 | arrayList.add(value); 44 | return arrayList; 45 | } 46 | 47 | @MapReduce 48 | public List listTest3(){ 49 | return null; 50 | } 51 | 52 | @MapReduce 53 | public LinkedList listTest4(){ 54 | LinkedList arrayList = new LinkedList(); 55 | arrayList.add(1); 56 | return arrayList; 57 | } 58 | 59 | } 60 | 61 | 62 | @Autowired 63 | private AgTest agTest; 64 | 65 | @Test 66 | public void listTest1(){ 67 | List agTest1 = agTest.listTest1(); 68 | Assert.assertTrue(agTest1.size() == 2); 69 | } 70 | 71 | @Test 72 | public void listTest2(){ 73 | List agTest1 = agTest.listTest2(1); 74 | Assert.assertTrue(agTest1.size() == 2); 75 | } 76 | 77 | @Test 78 | public void listTest3(){ 79 | boolean exception = false; 80 | try { 81 | agTest.listTest3(); 82 | } catch (Exception e) { 83 | exception = true; 84 | } 85 | Assert.assertTrue(exception); 86 | } 87 | 88 | @Test 89 | public void listTest4(){ 90 | LinkedList listTest4 = agTest.listTest4(); 91 | Assert.assertTrue(listTest4.size() == 2); 92 | } 93 | 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/test/java/org/easydevelop/mapreduce/stategy/SetReduceProviderTest.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.stategy; 2 | 3 | import java.util.HashSet; 4 | import java.util.LinkedHashSet; 5 | import java.util.Set; 6 | 7 | import org.easydevelop.business.TestApplicationConfig; 8 | import org.easydevelop.mapreduce.annotation.MapReduce; 9 | import org.easydevelop.sharding.annotation.ShardingContext; 10 | import org.junit.Assert; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.boot.test.context.SpringBootTest; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.stereotype.Service; 17 | import org.springframework.test.context.junit4.SpringRunner; 18 | 19 | 20 | 21 | /** 22 | * @author xudeyou 23 | */ 24 | @RunWith(SpringRunner.class) 25 | @SpringBootTest(classes={SetReduceProviderTest.class,TestApplicationConfig.class}) 26 | @Configuration 27 | public class SetReduceProviderTest { 28 | 29 | 30 | @Service("agTest") 31 | @ShardingContext(dataSourceSet="orderSet") 32 | public static class AgTest{ 33 | 34 | @MapReduce 35 | public Set test1(){ 36 | HashSet hashSet = new HashSet<>(); 37 | hashSet.add(1); 38 | return hashSet; 39 | } 40 | 41 | @MapReduce 42 | @SuppressWarnings({ "rawtypes", "unchecked" }) 43 | public HashSet test2(int value){ 44 | Set hashSet = new LinkedHashSet(); 45 | hashSet.add(value); 46 | return (HashSet) hashSet; 47 | } 48 | 49 | @MapReduce 50 | public Set setTest3(){ 51 | return null; 52 | } 53 | 54 | @MapReduce 55 | public LinkedHashSet test4(){ 56 | LinkedHashSet arrayList = new LinkedHashSet(); 57 | arrayList.add(1); 58 | return arrayList; 59 | } 60 | 61 | } 62 | 63 | 64 | @Autowired 65 | private AgTest agTest; 66 | 67 | @Test 68 | public void test1(){ 69 | Set agTest1 = agTest.test1(); 70 | Assert.assertTrue(agTest1.size() == 1); 71 | } 72 | 73 | @Test 74 | public void test2(){ 75 | HashSet agTest1 = agTest.test2(1); 76 | Assert.assertTrue(agTest1.size() == 1); 77 | } 78 | 79 | @Test 80 | public void test3(){ 81 | boolean exception = false; 82 | try { 83 | agTest.setTest3(); 84 | } catch (Exception e) { 85 | exception = true; 86 | } 87 | Assert.assertTrue(exception); 88 | } 89 | 90 | @Test 91 | public void test4(){ 92 | LinkedHashSet test4 = agTest.test4(); 93 | Assert.assertTrue(test4.size() == 1); 94 | } 95 | 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/test/java/org/easydevelop/mapreduce/stategy/KeyValueMapReduceProviderTest.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.stategy; 2 | 3 | import java.util.HashMap; 4 | import java.util.LinkedHashMap; 5 | import java.util.Map; 6 | 7 | import org.easydevelop.business.TestApplicationConfig; 8 | import org.easydevelop.mapreduce.annotation.MapReduce; 9 | import org.easydevelop.sharding.annotation.ShardingContext; 10 | import org.junit.Assert; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.boot.test.context.SpringBootTest; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.stereotype.Service; 17 | import org.springframework.test.context.junit4.SpringRunner; 18 | 19 | 20 | 21 | /** 22 | * @author xudeyou 23 | */ 24 | @RunWith(SpringRunner.class) 25 | @SpringBootTest(classes={KeyValueMapReduceProviderTest.class,TestApplicationConfig.class}) 26 | @Configuration 27 | public class KeyValueMapReduceProviderTest { 28 | 29 | 30 | @Service("agTest") 31 | @ShardingContext(dataSourceSet="orderSet") 32 | public static class AgTest{ 33 | 34 | @MapReduce 35 | public Map test1(){ 36 | Map hashSet = new HashMap<>(); 37 | hashSet.put(1,1); 38 | return hashSet; 39 | } 40 | 41 | @MapReduce 42 | public HashMap test2(int value){ 43 | LinkedHashMap hashSet = new LinkedHashMap(); 44 | hashSet.put(value,value); 45 | return hashSet; 46 | } 47 | 48 | @MapReduce 49 | public Map setTest3(){ 50 | return null; 51 | } 52 | 53 | @MapReduce 54 | public LinkedHashMap test4(){ 55 | LinkedHashMap arrayList = new LinkedHashMap(); 56 | arrayList.put(1,1); 57 | return arrayList; 58 | } 59 | 60 | } 61 | 62 | 63 | @Autowired 64 | private AgTest agTest; 65 | 66 | @Test 67 | public void test1(){ 68 | Map agTest1 = agTest.test1(); 69 | Assert.assertTrue(agTest1.size() == 1); 70 | } 71 | 72 | @Test 73 | public void test2(){ 74 | HashMap agTest1 = agTest.test2(1); 75 | Assert.assertTrue(agTest1.size() == 1); 76 | } 77 | 78 | @Test 79 | public void test3(){ 80 | boolean exception = false; 81 | try { 82 | agTest.setTest3(); 83 | } catch (Exception e) { 84 | exception = true; 85 | } 86 | Assert.assertTrue(exception); 87 | } 88 | 89 | @Test 90 | public void test4(){ 91 | LinkedHashMap test4 = agTest.test4(); 92 | Assert.assertTrue(test4.size() == 1); 93 | } 94 | 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/test/java/org/easydevelop/mapreduce/MapReduceTest.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce; 2 | 3 | import org.easydevelop.business.TestApplicationConfig; 4 | import org.easydevelop.business.domain.UserOrder; 5 | import org.easydevelop.mapreduce.annotation.MapReduce; 6 | import org.easydevelop.mapreduce.aspect.ReduceResultHolder; 7 | import org.easydevelop.sharding.ShardingRoutingDataSource; 8 | import org.easydevelop.sharding.annotation.ShardingContext; 9 | import org.junit.Assert; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | import org.springframework.context.annotation.Configuration; 15 | import org.springframework.stereotype.Service; 16 | import org.springframework.test.context.junit4.SpringRunner; 17 | import org.springframework.transaction.annotation.Transactional; 18 | 19 | 20 | 21 | /** 22 | * @author xudeyou 23 | */ 24 | @RunWith(SpringRunner.class) 25 | @SpringBootTest(classes={MapReduceTest.class,TestApplicationConfig.class}) 26 | @Configuration 27 | public class MapReduceTest { 28 | 29 | 30 | @Service("agTest") 31 | @ShardingContext(dataSourceSet="orderSet") 32 | public static class AgTest{ 33 | 34 | @Autowired 35 | private ShardingRoutingDataSource routingDataSource; 36 | 37 | @MapReduce 38 | public int agTest1(){ 39 | return 1; 40 | } 41 | 42 | @MapReduce 43 | public int agTest2(int value){ 44 | return value; 45 | } 46 | 47 | @MapReduce 48 | public void agTest3(int value,ReduceResultHolder resultHolder){ 49 | resultHolder.setShardingResult(value); 50 | } 51 | 52 | @Transactional(readOnly=true) 53 | @MapReduce 54 | public int readOnlyTest(){ 55 | return routingDataSource.getCurrentSlavePosition(); 56 | } 57 | 58 | } 59 | 60 | 61 | @Autowired 62 | private AgTest agTest; 63 | 64 | @Test 65 | public void agTest1(){ 66 | int agTest1 = agTest.agTest1(); 67 | Assert.assertTrue(agTest1 == 2); 68 | } 69 | 70 | @Test 71 | public void agTest2(){ 72 | int agTest1 = agTest.agTest2(3); 73 | Assert.assertTrue(agTest1 == 6); 74 | } 75 | 76 | @Test 77 | public void agTest3(){ 78 | ReduceResultHolder resultHolder = new ReduceResultHolder<>(Integer.class,Integer.class); 79 | agTest.agTest3(3,resultHolder); 80 | Assert.assertTrue(resultHolder.getResult() == 6); 81 | } 82 | 83 | @Test 84 | public void readOnlyTest(){ 85 | UserOrder order = new UserOrder(); 86 | order.setUserId(6); 87 | Integer shardingDatasourceSeq = agTest.readOnlyTest(); 88 | Assert.assertTrue(shardingDatasourceSeq != null && shardingDatasourceSeq.equals(0)); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/common/SpElHelper.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.common; 2 | 3 | import javax.annotation.PostConstruct; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.ApplicationContext; 7 | import org.springframework.context.expression.BeanFactoryResolver; 8 | import org.springframework.expression.Expression; 9 | import org.springframework.expression.ExpressionParser; 10 | import org.springframework.expression.spel.standard.SpelExpressionParser; 11 | import org.springframework.expression.spel.support.StandardEvaluationContext; 12 | 13 | import com.gs.collections.impl.map.mutable.ConcurrentHashMap; 14 | 15 | /** 16 | * @author xudeyou 17 | */ 18 | public class SpElHelper { 19 | 20 | private ExpressionParser parser = new SpelExpressionParser(); 21 | private ConcurrentHashMap mapExpression = new ConcurrentHashMap<>(); 22 | private ThreadLocal evaluationContext = new ThreadLocal<>(); 23 | @Autowired 24 | private ApplicationContext beanFactory; 25 | private BeanFactoryResolver beanResolver; 26 | 27 | @PostConstruct 28 | private void init(){ 29 | beanResolver = new BeanFactoryResolver(beanFactory); 30 | } 31 | 32 | private Expression getExpression(String expressionStr) { 33 | Expression expression = mapExpression.get(expressionStr); 34 | if(expression == null){ 35 | expression = parser.parseExpression(expressionStr); 36 | mapExpression.put(expressionStr, expression); 37 | } 38 | return expression; 39 | } 40 | 41 | @SuppressWarnings("unchecked") 42 | public R getValue(String spEl,Object root){ 43 | Expression exp = getExpression(spEl); 44 | StandardEvaluationContext standardEvaluationContext = evaluationContext.get(); 45 | if(standardEvaluationContext == null){ 46 | standardEvaluationContext = new StandardEvaluationContext(); 47 | standardEvaluationContext.setBeanResolver(beanResolver); 48 | evaluationContext.set(standardEvaluationContext); 49 | } 50 | 51 | standardEvaluationContext.setRootObject(root); 52 | return (R) exp.getValue(standardEvaluationContext); 53 | } 54 | 55 | public R getValue(String spEl){ 56 | return getValue(spEl, null); 57 | } 58 | 59 | public void setValue(String spEl,Object root,Object value){ 60 | Expression exp = getExpression(spEl); 61 | StandardEvaluationContext standardEvaluationContext = evaluationContext.get(); 62 | if(standardEvaluationContext == null){ 63 | standardEvaluationContext = new StandardEvaluationContext(); 64 | standardEvaluationContext.setBeanResolver(beanResolver); 65 | evaluationContext.set(standardEvaluationContext); 66 | } 67 | 68 | standardEvaluationContext.setRootObject(root); 69 | exp.setValue(standardEvaluationContext, value); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/test/resources/TestDatabase.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS `sharding_test_0`; 2 | USE `sharding_test_0`; 3 | 4 | DROP TABLE IF EXISTS `user`; 5 | CREATE TABLE `user` ( 6 | `user_id` int(11) NOT NULL, 7 | `name` varchar(45) COLLATE utf8_bin NOT NULL, 8 | PRIMARY KEY (`user_id`) 9 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; 10 | 11 | 12 | 13 | DROP TABLE IF EXISTS `user_order`; 14 | CREATE TABLE `user_order` ( 15 | `user_id` int(11) NOT NULL, 16 | `order_id` int(11) NOT NULL, 17 | `amount` bigint(20) NOT NULL, 18 | PRIMARY KEY (`user_id`,`order_id`) 19 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; 20 | 21 | 22 | CREATE DATABASE IF NOT EXISTS `sharding_test_1` /*!40100 DEFAULT CHARACTER SET utf8 COLLATE utf8_bin */; 23 | USE `sharding_test_1`; 24 | 25 | DROP TABLE IF EXISTS `user`; 26 | CREATE TABLE `user` ( 27 | `user_id` int(11) NOT NULL, 28 | `name` varchar(45) COLLATE utf8_bin NOT NULL, 29 | PRIMARY KEY (`user_id`) 30 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; 31 | 32 | 33 | 34 | DROP TABLE IF EXISTS `user_order`; 35 | CREATE TABLE `user_order` ( 36 | `user_id` int(11) NOT NULL, 37 | `order_id` int(11) NOT NULL, 38 | `amount` bigint(20) NOT NULL, 39 | PRIMARY KEY (`user_id`,`order_id`) 40 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; 41 | 42 | CREATE DATABASE IF NOT EXISTS `sharding_test_0_s0`; 43 | USE `sharding_test_0_s0`; 44 | 45 | DROP TABLE IF EXISTS `user`; 46 | CREATE TABLE `user` ( 47 | `user_id` int(11) NOT NULL, 48 | `name` varchar(45) COLLATE utf8_bin NOT NULL, 49 | PRIMARY KEY (`user_id`) 50 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; 51 | 52 | 53 | 54 | DROP TABLE IF EXISTS `user_order`; 55 | CREATE TABLE `user_order` ( 56 | `user_id` int(11) NOT NULL, 57 | `order_id` int(11) NOT NULL, 58 | `amount` bigint(20) NOT NULL, 59 | PRIMARY KEY (`user_id`,`order_id`) 60 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; 61 | 62 | 63 | CREATE DATABASE IF NOT EXISTS `sharding_test_1_s0`; 64 | USE `sharding_test_1_s0`; 65 | 66 | DROP TABLE IF EXISTS `user`; 67 | CREATE TABLE `user` ( 68 | `user_id` int(11) NOT NULL, 69 | `name` varchar(45) COLLATE utf8_bin NOT NULL, 70 | PRIMARY KEY (`user_id`) 71 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; 72 | 73 | 74 | 75 | DROP TABLE IF EXISTS `user_order`; 76 | CREATE TABLE `user_order` ( 77 | `user_id` int(11) NOT NULL, 78 | `order_id` int(11) NOT NULL, 79 | `amount` bigint(20) NOT NULL, 80 | PRIMARY KEY (`user_id`,`order_id`) 81 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; 82 | 83 | 84 | INSERT INTO `sharding_test_0_s0`.`user` (`user_id`, `name`) VALUES ('1000', 'readonly'); 85 | INSERT INTO `sharding_test_1_s0`.`user` (`user_id`, `name`) VALUES ('1001', 'readonly2'); 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/test/java/org/easydevelop/select/ShardingTest.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.select; 2 | 3 | import org.easydevelop.business.TestApplicationConfig; 4 | import org.easydevelop.business.domain.UserOrder; 5 | import org.easydevelop.select.annotation.SelectDataSource; 6 | import org.easydevelop.sharding.ShardingRoutingDataSource; 7 | import org.easydevelop.sharding.annotation.ShardingContext; 8 | import org.junit.Assert; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.context.annotation.Configuration; 14 | import org.springframework.stereotype.Service; 15 | import org.springframework.test.context.junit4.SpringRunner; 16 | import org.springframework.transaction.annotation.Transactional; 17 | 18 | 19 | 20 | /** 21 | * @author xudeyou 22 | */ 23 | @RunWith(SpringRunner.class) 24 | @SpringBootTest(classes={ShardingTest.class,TestApplicationConfig.class}) 25 | @Configuration 26 | public class ShardingTest { 27 | 28 | @ShardingContext(dataSourceSet="orderSet",shardingKeyEls="[order].userId",shardingStrategy=TestApplicationConfig.BY_USER_ID_MOD) 29 | @Service 30 | public static class ShardingServiceInstance{ 31 | 32 | @Autowired 33 | private ShardingRoutingDataSource routingDataSource; 34 | 35 | @SelectDataSource 36 | public int saveOrderWithUserId5(UserOrder order){ 37 | return routingDataSource.getCurrentLookupDsSequence(); 38 | } 39 | 40 | @SelectDataSource 41 | public int saveOrderWithUserId6(UserOrder order){ 42 | return routingDataSource.getCurrentLookupDsSequence(); 43 | } 44 | 45 | @SelectDataSource 46 | @Transactional(readOnly=true) 47 | public int readOnlyTest(UserOrder order){ 48 | return routingDataSource.getCurrentSlavePosition(); 49 | } 50 | 51 | } 52 | 53 | 54 | 55 | 56 | @Autowired 57 | private ShardingServiceInstance shardingServiceInstance; 58 | 59 | @Test 60 | public void saveOrderWithUser5(){ 61 | UserOrder order = new UserOrder(); 62 | order.setUserId(5); 63 | Integer shardingDatasourceSeq = shardingServiceInstance.saveOrderWithUserId5(order); 64 | Assert.assertTrue(shardingDatasourceSeq != null && shardingDatasourceSeq.equals(1)); 65 | } 66 | 67 | @Test 68 | public void saveOrderWithUser6(){ 69 | UserOrder order = new UserOrder(); 70 | order.setUserId(6); 71 | Integer shardingDatasourceSeq = shardingServiceInstance.saveOrderWithUserId6(order); 72 | Assert.assertTrue(shardingDatasourceSeq != null && shardingDatasourceSeq.equals(0)); 73 | } 74 | 75 | @Test 76 | public void readOnlyTest(){ 77 | UserOrder order = new UserOrder(); 78 | order.setUserId(6); 79 | Integer shardingDatasourceSeq = shardingServiceInstance.readOnlyTest(order); 80 | Assert.assertTrue(shardingDatasourceSeq != null && shardingDatasourceSeq.equals(0)); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | sharding-method 4 | 0.3.0-ALPHA 5 | org.easydevelop.baselib 6 | 7 | 8 | 9 | org.springframework.boot 10 | spring-boot-starter-parent 11 | 1.5.6.RELEASE 12 | 13 | 14 | 15 | 16 | UTF-8 17 | UTF-8 18 | 1.8 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-aop 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-jdbc 31 | test 32 | 33 | 34 | 35 | mysql 36 | mysql-connector-java 37 | test 38 | 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-test 43 | test 44 | 45 | 46 | 47 | com.goldmansachs 48 | gs-collections 49 | 5.1.0 50 | test 51 | 52 | 53 | 54 | org.ehcache 55 | ehcache-clustered 56 | test 57 | 58 | 59 | 60 | org.projectlombok 61 | lombok 62 | test 63 | 64 | 65 | 66 | 67 | org.springframework.boot 68 | spring-boot-starter-batch 69 | 70 | 71 | com.sleepycat 72 | je 73 | 5.0.73 74 | 75 | 76 | 77 | 78 | 79 | 83 | 84 | 85 | org.apache.maven.plugins 86 | maven-surefire-plugin 87 | 88 | 89 | **/*Test.java 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/mapreduce/strategy/ReduceProviderManager.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.strategy; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.concurrent.ExecutionException; 6 | import java.util.concurrent.Future; 7 | import java.util.concurrent.TimeUnit; 8 | import java.util.concurrent.TimeoutException; 9 | 10 | import org.easydevelop.mapreduce.strategy.provider.ReduceProvider; 11 | import org.springframework.core.annotation.AnnotationAwareOrderComparator; 12 | 13 | /** 14 | * @author xudeyou 15 | */ 16 | public class ReduceProviderManager implements ReduceStrategy { 17 | 18 | public static enum OnFutureExceptionOrTimeout{ 19 | THROW_EXCEPTION, 20 | RETURN_NULL; 21 | } 22 | 23 | public static class ShardingGetException extends RuntimeException{ 24 | 25 | private static final long serialVersionUID = 1L; 26 | 27 | public ShardingGetException(Throwable cause) { 28 | super(cause); 29 | } 30 | } 31 | 32 | private OnFutureExceptionOrTimeout onFutureExceptionOrTimeOut; 33 | private int futureTimeOutMills; 34 | private List> orderedReduceProviderList; 35 | public ReduceProviderManager(OnFutureExceptionOrTimeout onFutureExceptionOrTimeOut,int futureTimeOutMills,List> reduceProviderList){ 36 | this.onFutureExceptionOrTimeOut = onFutureExceptionOrTimeOut; 37 | this.futureTimeOutMills = futureTimeOutMills; 38 | orderedReduceProviderList = new ArrayList<>(reduceProviderList); 39 | AnnotationAwareOrderComparator.sort(orderedReduceProviderList); 40 | } 41 | 42 | 43 | protected List getResults(List> shardingFutrueList){ 44 | 45 | ArrayList arrayList = new ArrayList<>(shardingFutrueList.size()); 46 | for(Future f: shardingFutrueList){ 47 | S shardingResult = null; 48 | try { 49 | if(futureTimeOutMills == 0){ 50 | shardingResult = f.get(); 51 | } else { 52 | shardingResult = f.get(futureTimeOutMills, TimeUnit.MILLISECONDS); 53 | } 54 | } catch (InterruptedException | ExecutionException | TimeoutException e) { 55 | switch(onFutureExceptionOrTimeOut){ 56 | case THROW_EXCEPTION: 57 | throw new ShardingGetException(e); 58 | case RETURN_NULL: 59 | //do nothing 60 | continue; 61 | default: 62 | throw new RuntimeException("Unkown choice!"); 63 | } 64 | } 65 | arrayList.add(shardingResult); 66 | } 67 | 68 | return arrayList; 69 | } 70 | 71 | @Override 72 | public R reduce(List> shardingFutrueList, Class shardingResultClass, Class reduceResultClass) { 73 | 74 | List results = this.getResults(shardingFutrueList); 75 | 76 | for(ReduceProvider provider :orderedReduceProviderList){ 77 | if(provider.support(shardingResultClass, reduceResultClass)){//TODO cache and speed up 78 | return provider.reduce(results,shardingResultClass, reduceResultClass); 79 | } 80 | } 81 | 82 | throw new RuntimeException("can not find suitable reduce provider,please add a provider or use a custom ReduceStrategy"); 83 | } 84 | 85 | 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/sharding/ShardingRoutingDataSource.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.sharding; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import javax.annotation.PostConstruct; 8 | import javax.sql.DataSource; 9 | 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; 12 | import org.springframework.util.Assert; 13 | import org.springframework.util.StringUtils; 14 | 15 | /** 16 | * @author xudeyou 17 | */ 18 | public class ShardingRoutingDataSource extends AbstractRoutingDataSource { 19 | 20 | @Autowired 21 | private List dataSourceSetList; 22 | 23 | private Map mapDataSourceSet; 24 | 25 | @PostConstruct 26 | private void init(){ 27 | 28 | HashMap mapDataSource = new HashMap<>(); 29 | mapDataSourceSet = new HashMap<>(); 30 | for(DataSourceSet set:dataSourceSetList){ 31 | String datasourceSetName = set.getDatasourceSetName(); 32 | mapDataSourceSet.put(datasourceSetName, set); 33 | List> slaveDataSources = set.getSlaveDataSources(); 34 | for(int shardingPartitionPos = 0; shardingPartitionPos < set.getMasterDataSources().size(); shardingPartitionPos++){ 35 | //master 36 | mapDataSource.put(calcLookupKey(datasourceSetName, shardingPartitionPos,null), set.getMasterDataSources().get(shardingPartitionPos)); 37 | 38 | //slaves 39 | List list = slaveDataSources.get(shardingPartitionPos); 40 | if(list != null && list.size() != 0){ 41 | for(int slavePosition = 0; slavePosition < list.size(); slavePosition++){ 42 | mapDataSource.put(calcLookupKey(datasourceSetName, shardingPartitionPos,slavePosition),list.get(slavePosition) ); 43 | } 44 | } 45 | } 46 | } 47 | 48 | super.setTargetDataSources(mapDataSource); 49 | } 50 | 51 | private ThreadLocal currentLookupDsSet = new ThreadLocal<>(); 52 | private ThreadLocal currentLookupDsSequence = new ThreadLocal<>(); 53 | private ThreadLocal currentSlavePosition = new ThreadLocal<>(); 54 | 55 | 56 | public DataSourceSet getDataSourcesByDsSetName(String name){ 57 | return mapDataSourceSet.get(name);//TODO generate a unmodifiable one 58 | } 59 | 60 | private String getCurrentLookupKey() { 61 | String dsSet = currentLookupDsSet.get(); 62 | Integer sequence = currentLookupDsSequence.get(); 63 | Integer slavePosition = currentSlavePosition.get(); 64 | 65 | if(dsSet != null && sequence != null){ 66 | return calcLookupKey(dsSet, sequence,slavePosition); 67 | }else{ 68 | return null; 69 | } 70 | } 71 | 72 | 73 | 74 | 75 | public String getCurrentLookupDsSet() { 76 | return currentLookupDsSet.get(); 77 | } 78 | 79 | 80 | 81 | 82 | public Integer getCurrentLookupDsSequence() { 83 | return currentLookupDsSequence.get(); 84 | } 85 | 86 | public Integer getCurrentSlavePosition() { 87 | return currentSlavePosition.get(); 88 | } 89 | 90 | 91 | 92 | 93 | public void setCurrentLookupKey(String dbSetName,Integer shardingPartitionPos,Integer slavePositon) { 94 | Assert.isTrue(dbSetName == null || !StringUtils.isEmpty(dbSetName),"can not be empty!"); 95 | currentLookupDsSet.set(dbSetName); 96 | currentLookupDsSequence.set(shardingPartitionPos); 97 | currentSlavePosition.set(slavePositon); 98 | } 99 | 100 | private String calcLookupKey(String dbSetName, int shardingPartitionPos,Integer slavePositon) { 101 | return dbSetName+"-"+shardingPartitionPos+(slavePositon==null?"":("-" + slavePositon)); 102 | } 103 | 104 | @Override 105 | protected Object determineCurrentLookupKey() { 106 | return getCurrentLookupKey(); 107 | } 108 | 109 | @Override 110 | @Deprecated 111 | public void setTargetDataSources(Map targetDataSources) { 112 | throw new RuntimeException("you can not call this method by yourself!"); 113 | } 114 | 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/test/java/org/easydevelop/generateid/GenerateIdTest.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.generateid; 2 | 3 | import java.util.concurrent.atomic.AtomicInteger; 4 | 5 | import org.easydevelop.business.TestApplicationConfig; 6 | import org.easydevelop.business.domain.User; 7 | import org.easydevelop.business.domain.UserOrder; 8 | import org.easydevelop.generateid.annotation.GenerateId; 9 | import org.easydevelop.generateid.strategy.KeyGenerateStrategy; 10 | import org.easydevelop.sharding.annotation.ShardingContext; 11 | import org.junit.Assert; 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.boot.test.context.SpringBootTest; 16 | import org.springframework.context.annotation.Configuration; 17 | import org.springframework.stereotype.Service; 18 | import org.springframework.test.context.junit4.SpringRunner; 19 | 20 | 21 | 22 | /** 23 | * @author xudeyou 24 | */ 25 | @RunWith(SpringRunner.class) 26 | @SpringBootTest(classes={GenerateIdTest.class,TestApplicationConfig.class}) 27 | @Configuration 28 | public class GenerateIdTest { 29 | 30 | @ShardingContext(generateIdEls={"[order].orderId"},generateIdStrategy="@intGenerator") 31 | @Service 32 | public static class KeyTest{ 33 | 34 | 35 | @GenerateId 36 | public Integer parameterFieldInject(UserOrder order){ 37 | return order.getOrderId(); 38 | } 39 | 40 | @GenerateId(keyEls="[orderId]") 41 | public Integer parameterReplace(String abc,Integer orderId){ 42 | return orderId; 43 | } 44 | 45 | @GenerateId(keyEls="[testUser].name",strategy="@stringGenerator") 46 | public String strParameterFieldInject(User testUser){ 47 | return testUser.getName(); 48 | } 49 | 50 | @GenerateId(keyEls={"[testUser].userId","[testUser].name"},strategy="@multiFieldGenerator") 51 | public void multiParameterFieldInject(User testUser){ 52 | } 53 | 54 | @GenerateId(allowCallerDefinedKeyValue=1) 55 | public Integer alloUserDefinedKeyTest(UserOrder order){ 56 | return order.getOrderId(); 57 | } 58 | 59 | @GenerateId(keyEls="[testUser].name",strategy="@stringGenerator",strategyMetadataEls="[testUser].userId") 60 | public String metadataCheck(User testUser){ 61 | return testUser.getName(); 62 | } 63 | 64 | } 65 | 66 | public static final String INT_GENERATOR = "intGenerator"; 67 | @Service(INT_GENERATOR) 68 | public static class IntKeyGenerateStrategy implements KeyGenerateStrategy{ 69 | 70 | AtomicInteger atomInt = new AtomicInteger(0); 71 | 72 | @Override 73 | public Object[] generateKey(Object[] metaData) { 74 | return new Object[]{atomInt.incrementAndGet()}; 75 | } 76 | } 77 | 78 | public static final String STRING_GENERATOR = "stringGenerator"; 79 | @Service(STRING_GENERATOR) 80 | public static class StringKeyGenerateStrategy implements KeyGenerateStrategy{ 81 | 82 | AtomicInteger atomInt = new AtomicInteger(0); 83 | 84 | @Override 85 | public Object[] generateKey(Object[] metaData) { 86 | if(metaData.length == 0){ 87 | return new Object[]{"userName-" + atomInt.incrementAndGet()}; 88 | }else{ 89 | return new Object[]{metaData[0]}; 90 | } 91 | } 92 | } 93 | 94 | public static final String MULTI_FIELD_GENERATOR = "multiFieldGenerator"; 95 | @Service(MULTI_FIELD_GENERATOR) 96 | public static class MultiFiledKeyGenerateStrategy implements KeyGenerateStrategy{ 97 | 98 | AtomicInteger atomInt = new AtomicInteger(0); 99 | 100 | @Override 101 | public Object[] generateKey(Object[] metaData) { 102 | return new Object[]{atomInt.incrementAndGet(),"userName-" + atomInt.incrementAndGet()}; 103 | } 104 | } 105 | 106 | 107 | @Autowired 108 | private KeyTest keyTest; 109 | 110 | @Test 111 | public void parameterReplace(){ 112 | Integer inject = keyTest.parameterReplace(null, null); 113 | Assert.assertTrue(inject != null); 114 | } 115 | 116 | @Test 117 | public void parameterFieldInject(){ 118 | UserOrder order = new UserOrder(); 119 | Integer orderId = keyTest.parameterFieldInject(order); 120 | Assert.assertTrue(orderId != null); 121 | } 122 | 123 | @Test 124 | public void strParameterFieldInject(){ 125 | User user = new User(); 126 | String userName = keyTest.strParameterFieldInject(user); 127 | Assert.assertTrue(userName != null); 128 | } 129 | 130 | @Test 131 | public void multiParameterFieldInject(){ 132 | User user = new User(); 133 | keyTest.multiParameterFieldInject(user); 134 | Assert.assertTrue(user.getName() != null && user.getUserId() != null); 135 | } 136 | 137 | @Test 138 | public void alloUserDefinedKeyTest(){ 139 | UserOrder order = new UserOrder(); 140 | order.setAmount(1000l); 141 | order.setOrderId(1000); 142 | order.setUserId(1); 143 | keyTest.alloUserDefinedKeyTest(order); 144 | Assert.assertTrue(order.getOrderId().equals(1000)); 145 | } 146 | 147 | @Test 148 | public void metadataCheck(){ 149 | User order = new User(); 150 | order.setUserId(1); 151 | keyTest.metadataCheck(order); 152 | Assert.assertTrue(order.getName().equals("1")); 153 | } 154 | 155 | 156 | } 157 | -------------------------------------------------------------------------------- /src/test/java/org/easydevelop/business/TestApplicationConfig.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.business; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import java.util.concurrent.ExecutionException; 7 | import java.util.concurrent.Future; 8 | import java.util.concurrent.atomic.AtomicInteger; 9 | 10 | import javax.sql.DataSource; 11 | 12 | import org.easydevelop.EnableShardingMethod; 13 | import org.easydevelop.business.domain.User; 14 | import org.easydevelop.generateid.strategy.KeyGenerateStrategy; 15 | import org.easydevelop.mapreduce.strategy.ReduceStrategy; 16 | import org.easydevelop.select.strategy.SelectDataSourceStrategy; 17 | import org.easydevelop.sharding.DataSourceSet; 18 | import org.easydevelop.sharding.ShardingRoutingDataSource; 19 | import org.springframework.context.annotation.Bean; 20 | import org.springframework.context.annotation.ComponentScan; 21 | import org.springframework.context.annotation.Configuration; 22 | import org.springframework.jdbc.core.JdbcTemplate; 23 | import org.springframework.jdbc.datasource.DataSourceTransactionManager; 24 | import org.springframework.transaction.annotation.EnableTransactionManagement; 25 | 26 | import com.mysql.jdbc.jdbc2.optional.MysqlDataSource; 27 | 28 | /** 29 | * @author xudeyou 30 | */ 31 | @Configuration 32 | @EnableShardingMethod 33 | @EnableTransactionManagement 34 | @ComponentScan("org.easydevelop.business") 35 | public class TestApplicationConfig { 36 | 37 | public static final String UPDATE_COUNT_ADD = "@aggUpdateCountAdd"; 38 | public static final String BY_USER_ID_MOD = "@modUserId"; 39 | public static final String INT_INCREASE = "@intIncrease"; 40 | public static final String AGGREGATION_USER_ORDER_BY_USER_ID = "@aggOrderByUserId"; 41 | 42 | @Bean 43 | public DataSourceSet getDataSourceSet(){ 44 | 45 | MysqlDataSource ds1 = new MysqlDataSource(); 46 | ds1.setURL("jdbc:mysql://192.168.92.134:3306/sharding_test_0"); 47 | ds1.setUser("root"); 48 | ds1.setPassword("abcde"); 49 | 50 | MysqlDataSource ds1_s0 = new MysqlDataSource(); 51 | ds1_s0.setURL("jdbc:mysql://192.168.92.134:3306/sharding_test_0_s0"); 52 | ds1_s0.setUser("root"); 53 | ds1_s0.setPassword("abcde"); 54 | 55 | 56 | MysqlDataSource ds2 = new MysqlDataSource(); 57 | ds2.setURL("jdbc:mysql://192.168.92.134:3306/sharding_test_1"); 58 | ds2.setUser("root"); 59 | ds2.setPassword("abcde"); 60 | 61 | MysqlDataSource ds2_s0 = new MysqlDataSource(); 62 | ds2_s0.setURL("jdbc:mysql://192.168.92.134:3306/sharding_test_1_s0"); 63 | ds2_s0.setUser("root"); 64 | ds2_s0.setPassword("abcde"); 65 | 66 | ArrayList arrayList = new ArrayList(2); 67 | arrayList.add(ds1); 68 | arrayList.add(ds2); 69 | 70 | List> slaveList = Arrays.asList(Arrays.asList(ds1_s0),Arrays.asList(ds2_s0)); 71 | 72 | DataSourceSet dataSourceSet = new DataSourceSet("orderSet",arrayList,slaveList); 73 | return dataSourceSet; 74 | } 75 | 76 | @Bean 77 | public JdbcTemplate jdbcTemplate(ShardingRoutingDataSource dataSource){ 78 | return new JdbcTemplate(dataSource); 79 | } 80 | 81 | @Bean 82 | public DataSourceTransactionManager transactionManager(ShardingRoutingDataSource dataSource){ 83 | DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource); 84 | return dataSourceTransactionManager; 85 | } 86 | 87 | @Bean 88 | public SelectDataSourceStrategy modUserId(){ 89 | return new SelectDataSourceStrategy() { 90 | 91 | @Override 92 | public int select(Object[] shardingMetadata, int datasourceSize) { 93 | Integer userId = (Integer) shardingMetadata[0]; 94 | return userId % datasourceSize; 95 | } 96 | }; 97 | } 98 | 99 | @Bean 100 | public KeyGenerateStrategy intIncrease(){ 101 | return new KeyGenerateStrategy() { 102 | 103 | AtomicInteger atomicInteger = new AtomicInteger(0); 104 | 105 | @Override 106 | public Object[] generateKey(Object[] metadata) { 107 | return new Object[]{atomicInteger.incrementAndGet()}; 108 | } 109 | }; 110 | 111 | } 112 | 113 | @Bean 114 | public ReduceStrategy,List> aggOrderByUserId(){ 115 | return new ReduceStrategy,List>() { 116 | 117 | @Override 118 | public List reduce(List>> subFutrueList, Class> shardingResultClass, 119 | Class> reduceResultClass) { 120 | 121 | List result = new ArrayList<>(); 122 | subFutrueList.forEach(listFuture->{ 123 | try { 124 | result.addAll(listFuture.get()); 125 | } catch (Exception e) { 126 | throw new RuntimeException(e); 127 | } 128 | }); 129 | result.sort((first,second)->Integer.compare(first.getUserId(), second.getUserId())); 130 | return result; 131 | } 132 | 133 | 134 | 135 | }; 136 | 137 | } 138 | 139 | 140 | @Bean 141 | public ReduceStrategy aggUpdateCountAdd(){ 142 | return new ReduceStrategy() { 143 | 144 | @Override 145 | public Integer reduce(List> subFutrueList, Class shardingResultClass, 146 | Class reduceResultClass) { 147 | return subFutrueList.stream().map(future -> { 148 | try { 149 | return future.get(); 150 | } catch (InterruptedException | ExecutionException e) { 151 | throw new RuntimeException(e); 152 | } 153 | }).mapToInt(i->i).sum(); 154 | } 155 | }; 156 | 157 | } 158 | 159 | 160 | } 161 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 核心特性 2 | 全数据库全SQL兼容、完美RR级别读写分离、与原生一致的ACID特性、轻量简单易扩展 3 | 4 | # 另外一个轮子的意义 5 | 很多人会质疑 市面上较为流行的Sharding中间件/应用层Sharing框架已经有很多,他们都已经发展了很久了,功能也很强大,为什么要一个再重复制造这么个轮子呢? 6 | 7 | 之所以这里有一个新的轮子并不是因为我懒得无事,而是我对目前基于传统RDB上Sharding框架的设计的理念不太赞同,虽然他们或许都很圆,跑的很快,但是使用不当的话,容易翻车。我期望的轮子是既能跑的快,但也能跑的稳。 8 | 9 | 10 | 我们目前国内主流的Sharding框架都是基于SQL来完成,其主要流程: 11 | 1. 是解析上层传入的SQL 12 | 2. 结合对应的分表分库配置,对传入的SQL进行改写并分发到对应的单机数据库上 13 | 3. 获得各个单机数据库的返回结果后,根据原SQL归并结果,返回用户期待的结果 14 | 15 | 这种实现希望提供一个屏蔽底层Sharding逻辑的解决方案,对上层应用来说,只有一个RDB,这样应用可以透明访问多个数据库。 16 | 17 | 然而,这仅仅只是一个美丽的目标。因种种原因,这些层次的Sharding方案都无法提供跟原生数据库一样的功能: 18 | * ACID里的A无法保证 19 | * ACID里的C可能被打破 20 | * ACID里的I与原生不一致 21 | * 由于SQL解析复杂,性能等考虑,很多数据库SQL不支持 22 | 23 | 正因为存在这些差异,本质上,上层应用必须明确的知道经过此类Sharding方案后得到的查询结果、事务结果与原生的有啥不一致才能写出正确可靠的程序。 24 | 25 | **因此,基于SQL的Sharding方案对应用层并不透明。** 26 | 27 | 如果要基于SQL层的框架写出正确可靠的代码的话,我们需要遵循一些范式: 28 | * 所有事务(包括读、写)都不能跨库 29 | * 跨分片的查询提供的隔离级别与原生不一致 30 | * 某些聚合查询的性能消耗很大,要慎用 31 | * ...... 32 | 33 | 这些范式对于实际上就是使用Sharding数据库框架时,不透明的表现。而这些表现都是隐式的隐藏于SQL中,难以REVIEW。 34 | 35 | 而且这些范式对于很多人来说不一定能够充分理解执行的含义,以至于忽略了。 36 | 37 | 由上面最重要的一点“所有事务(包括读、写)都不能跨库”决定,**一个合理设计的代码里绝大多数的业务代码中数据库访问都不会跨分区,核心业务代码都在同一分区内进行。** 38 | 因此,我们大多数情况下,需要的只是一个协助我们便捷选择对应分片的一个框架。 39 | 40 | 因此我的想法很简单,提供一个方便透明选择分片、并辅以自动生成ID的框架。对于需要访问多个分片的少数业务,框架提供手段,便捷地获取所有分片数据库的数据,并由用户自行归并得出所需结果(简单的归并框架可以自动进行)。 41 | 42 | # 基本使用方法 43 | 44 | 先简略展示以下框架的基本用法(以下代码在UT案例中,但为突出重点,有所裁剪) 45 | 46 | Service层 47 | 48 | @Service 49 | @ShardingContext(dataSourceSet="orderSet",shardingKeyEls="[user].userId",shardingStrategy="@modUserId",generateIdStrategy="@snowflaker",generateIdEls="[user].userId") 50 | public class UserServceImpl { 51 | 52 | @Autowired 53 | private UserDaoImpl userDao; 54 | 55 | @Transactional 56 | @SelectDataSource 57 | public void updateUser(User user){ 58 | userDao.updateUser(user); 59 | } 60 | 61 | @Transactional 62 | @SelectDataSource 63 | @GenerateId 64 | public void saveUser(User user){ 65 | userDao.saveUser(user); 66 | } 67 | 68 | @Transactional(readOnly=true) 69 | @SelectDataSource(keyNameEls="[userId]") 70 | public User findUser(int userId){ 71 | return userDao.findUser(userId); 72 | } 73 | 74 | public List findAllUsers(){ 75 | return userDao.findAllUsers(); 76 | } 77 | 78 | public double calcUserAvgAge(){ 79 | List allUsers = userDao.findAllUsers(); 80 | return allUsers.stream().mapToInt(u->u.getAge()).average().getAsDouble(); 81 | } 82 | } 83 | 84 | @ShardingContext表示当前的Service的Sharding上下文,就是说,如果有 选择数据源、Map到各数据库Reduce出结果、生成ID等操作时,如果某些参数没有指定,都从这个ShardingContext里面的配置取 85 | 86 | @SelectDataSource表示为该方法内执行的SQL根据Sharding策略选择一个Sharding数据源,在方法结束返回前,不能更改Sharding数据源 87 | 88 | @GenerateId表示生成ID,并将其赋值到参数的指定位置 89 | 90 | @GenerateId对应的逻辑会先执行,然后到@SelectDataSource然后到@Transaction 91 | 92 | @Transactional(readOnly=true)标签指定了事务时只读的,因此框架会根据readOnly标志自动选择读库(如果有的话) 93 | 94 | 从方法calcUserAvgAge可以看到在JDK8的LAMBADA表达式及Stream功能下,JAVA分析处理集合数据变得极为简单,这会大大减少我们自行加工Sharding分片数据的复杂度。 95 | 96 | 接下来看DAO层 97 | 98 | @Component 99 | public class UserDaoImpl { 100 | 101 | @Autowired 102 | private JdbcTemplate jdbcTemplate; 103 | 104 | public void updateUser(User user){ 105 | int update = jdbcTemplate.update("UPDATE `user` SET `name`=? WHERE `user_id`=?;",user.getName(),user.getUserId()); 106 | Assert.isTrue(update == 1,"it should be updated!"); 107 | } 108 | 109 | public User findUser(int userId){ 110 | return jdbcTemplate.queryForObject("SELECT * FROM user WHERE user_id = ?", new Object[]{userId}, rowMapper); 111 | } 112 | 113 | @Transactional 114 | @MapReduce 115 | public List findAllUsers(){ 116 | return jdbcTemplate.query("SELECT * FROM user", rowMapper); 117 | } 118 | 119 | @Transactional(readOnly=true) 120 | @MapReduce 121 | public void findAllUsers(ReduceResultHolder resultHolder){ 122 | List shardingUsers = jdbcTemplate.query("SELECT * FROM user", rowMapper); 123 | resultHolder.setShardingResult(shardingUsers); 124 | } 125 | } 126 | 127 | @MapReduce表示该方法将会在每个数据分片都执行一遍,然后进行数据聚合后返回。 128 | 对于聚合前后返回的数据类型一致的方法,调用时可以直接从返回值取得聚合结果。 129 | 对于聚合前后返回的数据类型不一致的方法,需要传入一个对象ReduceResultHolder,调用完成后,通过该对象获得聚合结果 130 | 131 | 默认情况下,框架会提供一个通用Reduce策略,如果是数字则累加返回,如果是Collection及其子类则合并后返回,如果是MAP则也是合并后返回。 132 | 如果该策略不适合,那么用户可自行设计指定Reduce策略。 133 | 134 | @Transaction表示每一个Sharding执行的SQL都处于一个事务中,并不是表示整个聚合操作是一个整体的事务。所以,MapReduce最好不要进行更新操作(考虑框架层次限制MapReduce只允许ReadOnly事务)。 135 | 136 | @MapReduce执行的操作会在@Transaction之前。 137 | 138 | 139 | # 优点缺点对比 140 | 以上是框架的主要使用形式,我们可以从这种实现中发现服务层的Sharding有以下好处 141 | * 全数据库、全SQL兼容 142 | * SQL层Sharding无法做到 143 | * 能完美实现读写分离 144 | * 基于SQL层实现的Sharding引入读写分离后,在上层Service感知的事务里,存在混乱的隔离级别的问题,其最多实现RC级别读写分离(若不在Service层介入相关辅助代码的话),而Service层Sharding在Service开始前就能确定该事务是读事务,整个读事务都在一个读库中完成,隔离级别与数据库一致 145 | * 无额外维护DBProxy可用性的负担 146 | * 相对于复杂的SQL解析,实现简单,相信花个一天就能看完所有代码,整个框架了如指掌 147 | * 无SQL解析成本,性能更高 148 | * 隔离级别及事务原子性等特征与使用的数据库一致,无额外学习负担,易于写出正确的程序 149 | * 框架限制了所有事务都在单库进行 150 | * 基于Sql的Sharding即使在非读写分离情况下,因其需要归并多个数据库的结果,其提供的隔离级别也是混乱的,但这个区别并没有显式的提示到程序员。 151 | 152 | 153 | 当然也存在缺点 154 | 155 | 劣势: 156 | * 跨库查询需要自行进行结果聚合 157 | * 是劣势也是优势 158 | * 劣势:需要完成额外的聚合代码 159 | * 优势:但其能能更好的调优,使用JDK8的Stream及Lambada表达式,能像写SQL一样简单的完成相关集合处理 160 | * 跨库事务需要自行保证 161 | * 是劣势也是优势 162 | * 劣势:需要额外自行实现跨库事务 163 | * 优势:目前所有的Sharding框架实现的跨库事务都有缺陷或者说限制,如Sharding-JDBC,Mycat等提供的跨库事务都并非严格意义的ACID,A可能被打破,I也与原生定义的不一样,程序员不熟悉时就很容易写出不可靠的代码。因此自行控制分布式事务,采用显式的事务控制或许是更好的选择。可参考使用本人写的另外一个框架EasyTransaction 164 | * 无法实现单库分表 165 | * 其实,单库分表并不是必须的,这可以用数据原生的表分区来实现,性能一样,使用更便捷 166 | 167 | # 具体使用方法 168 | 更具体使用案例请参考 测试Package:org.easydevelop.business里的案例 -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/ShardingConfiguaration.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.easydevelop.common.SpElHelper; 7 | import org.easydevelop.common.TransactionStatusHelper; 8 | import org.easydevelop.generateid.aspect.KeyGenerateAspect; 9 | import org.easydevelop.mapreduce.aspect.MapReduceAspect; 10 | import org.easydevelop.mapreduce.strategy.ReduceProviderManager; 11 | import org.easydevelop.mapreduce.strategy.ReduceProviderManager.OnFutureExceptionOrTimeout; 12 | import org.easydevelop.mapreduce.strategy.provider.CollectionReduceProvider; 13 | import org.easydevelop.mapreduce.strategy.provider.DoubleBasicReduceProvider; 14 | import org.easydevelop.mapreduce.strategy.provider.DoubleObjectReduceProvider; 15 | import org.easydevelop.mapreduce.strategy.provider.FloatBasicReduceProvider; 16 | import org.easydevelop.mapreduce.strategy.provider.FloatObjectReduceProvider; 17 | import org.easydevelop.mapreduce.strategy.provider.IntReduceProvider; 18 | import org.easydevelop.mapreduce.strategy.provider.IntegerReduceProvider; 19 | import org.easydevelop.mapreduce.strategy.provider.KeyValueMapReduceProvider; 20 | import org.easydevelop.mapreduce.strategy.provider.ListReduceProvider; 21 | import org.easydevelop.mapreduce.strategy.provider.LongBasicReduceProvider; 22 | import org.easydevelop.mapreduce.strategy.provider.LongObjectReduceProvider; 23 | import org.easydevelop.mapreduce.strategy.provider.ReduceProvider; 24 | import org.easydevelop.mapreduce.strategy.provider.SetReduceProvider; 25 | import org.easydevelop.readonly.RoundRobinReadonlyDsSelectStrategy; 26 | import org.easydevelop.select.aspect.SelectDataSourceAspect; 27 | import org.easydevelop.sharding.ShardingRoutingDataSource; 28 | import org.easydevelop.sharding.aspect.ShardingAspect; 29 | import org.springframework.beans.factory.annotation.Autowired; 30 | import org.springframework.beans.factory.annotation.Value; 31 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 32 | import org.springframework.context.annotation.Bean; 33 | import org.springframework.context.annotation.Configuration; 34 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 35 | import org.springframework.core.OrderComparator; 36 | 37 | /** 38 | * @author xudeyou 39 | */ 40 | @Configuration 41 | @EnableAspectJAutoProxy 42 | public class ShardingConfiguaration { 43 | 44 | @Bean 45 | public KeyGenerateAspect keyGenerateAspect(){ 46 | return new KeyGenerateAspect(); 47 | } 48 | 49 | @Bean 50 | public ShardingRoutingDataSource shardingRoutingDataSource(){ 51 | return new ShardingRoutingDataSource(); 52 | } 53 | 54 | @Bean 55 | public SelectDataSourceAspect selectDataSourceAspect(){ 56 | return new SelectDataSourceAspect(); 57 | } 58 | 59 | @Bean 60 | public MapReduceAspect mapReduceAspect(){ 61 | return new MapReduceAspect(); 62 | } 63 | 64 | @Bean 65 | public SpElHelper spElHelper(){ 66 | return new SpElHelper(); 67 | } 68 | 69 | @Bean 70 | public ShardingAspect shardingAspect(){ 71 | return new ShardingAspect(); 72 | } 73 | 74 | @Bean 75 | public TransactionStatusHelper transactionStatusHelper(){ 76 | return new TransactionStatusHelper(); 77 | } 78 | 79 | @Bean 80 | public RoundRobinReadonlyDsSelectStrategy readOnlyDsSelectStrategy(){ 81 | return new RoundRobinReadonlyDsSelectStrategy(); 82 | } 83 | 84 | 85 | @ConditionalOnMissingBean(name = "defalutReduceStrategy") 86 | @Configuration 87 | public static class ReduceProviderManagerConfiguration{ 88 | 89 | @Autowired 90 | @SuppressWarnings("rawtypes") 91 | private List reduceProviders = new ArrayList<>(); 92 | 93 | @Bean 94 | @SuppressWarnings({ "rawtypes", "unchecked" }) 95 | public ReduceProviderManager defalutReduceStrategy(){ 96 | OrderComparator.sort(reduceProviders); 97 | return new ReduceProviderManager(onFutureExceptionOrTimeOut, futureTimeOutMills, reduceProviders); 98 | } 99 | 100 | @Value("${sharding-method.mapreduce.reduce.future.exception:THROW_EXCEPTION}") 101 | private OnFutureExceptionOrTimeout onFutureExceptionOrTimeOut; 102 | 103 | @Value("${sharding-method.mapreduce.reduce.future.timeout-mills:30000}") 104 | private int futureTimeOutMills; 105 | 106 | @Bean 107 | public ListReduceProvider listReduceProvider(){ 108 | return new ListReduceProvider(); 109 | } 110 | 111 | @Bean 112 | public SetReduceProvider setReduceProvider(){ 113 | return new SetReduceProvider(); 114 | } 115 | 116 | @Bean 117 | public CollectionReduceProvider collectionReduceProvider(){ 118 | return new CollectionReduceProvider(); 119 | } 120 | 121 | @Bean 122 | public KeyValueMapReduceProvider keyValueMapReduceProvider(){ 123 | return new KeyValueMapReduceProvider(); 124 | } 125 | 126 | @Bean 127 | public IntReduceProvider intReduceProvider(){ 128 | return new IntReduceProvider(); 129 | } 130 | 131 | @Bean 132 | public IntegerReduceProvider integerReduceProvider(){ 133 | return new IntegerReduceProvider(); 134 | } 135 | 136 | @Bean 137 | public LongBasicReduceProvider longBasicReduceProvider(){ 138 | return new LongBasicReduceProvider(); 139 | } 140 | 141 | @Bean 142 | public LongObjectReduceProvider longObjectReduceProvider(){ 143 | return new LongObjectReduceProvider(); 144 | } 145 | 146 | @Bean 147 | public DoubleBasicReduceProvider doubleBasicReduceProvider(){ 148 | return new DoubleBasicReduceProvider(); 149 | } 150 | 151 | @Bean 152 | public DoubleObjectReduceProvider doubleObjectReduceProvider(){ 153 | return new DoubleObjectReduceProvider(); 154 | } 155 | 156 | @Bean 157 | public FloatBasicReduceProvider floatBasicReduceProvider(){ 158 | return new FloatBasicReduceProvider(); 159 | } 160 | 161 | @Bean 162 | public FloatObjectReduceProvider floatObjectReduceProvider(){ 163 | return new FloatObjectReduceProvider(); 164 | } 165 | 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/generateid/aspect/KeyGenerateAspect.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.generateid.aspect; 2 | 3 | import java.util.LinkedHashMap; 4 | 5 | import org.aspectj.lang.ProceedingJoinPoint; 6 | import org.aspectj.lang.annotation.Around; 7 | import org.aspectj.lang.annotation.Aspect; 8 | import org.aspectj.lang.reflect.MethodSignature; 9 | import org.easydevelop.common.SpElHelper; 10 | import org.easydevelop.generateid.annotation.GenerateId; 11 | import org.easydevelop.generateid.strategy.KeyGenerateStrategy; 12 | import org.easydevelop.sharding.annotation.ShardingContext; 13 | import org.easydevelop.sharding.aspect.ShardingAspect; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.core.annotation.Order; 16 | import org.springframework.util.Assert; 17 | import org.springframework.util.StringUtils; 18 | 19 | /** 20 | * @author xudeyou 21 | */ 22 | @Aspect 23 | @Order(-2) 24 | public class KeyGenerateAspect { 25 | 26 | @Autowired 27 | private SpElHelper spElHelper; 28 | 29 | @Around("@annotation(keyInject)") 30 | public Object around(ProceedingJoinPoint jp,GenerateId keyInject) throws Throwable{ 31 | 32 | //get KeyGenerate annotation from method's class 33 | ShardingContext keyGenerator = ShardingAspect.getShardingContext(); 34 | if(keyGenerator == null){ 35 | throw new RuntimeException("Class annotation config error,can not find KeyGenerator!"); 36 | } 37 | 38 | //get configurations 39 | String[] keyEls = getFinalIdNames(keyInject, keyGenerator); 40 | String strategy = getFinalStrategy(keyInject, keyGenerator); 41 | boolean allowUserDefindKeyValue = getAllowUserDefindKeyValue(keyInject, keyGenerator); 42 | String[] strategyMetadataEls = getStrategyMetadataEls(keyInject, keyGenerator); 43 | 44 | //get parameters based on keyNames 45 | MethodSignature signature = (MethodSignature) jp.getSignature(); 46 | LinkedHashMap mapArgs = getArgsLinkedHashMap(signature.getParameterNames(),jp.getArgs()); 47 | 48 | //get key values 49 | Object[] keyValues = getElsValues(keyEls,mapArgs); 50 | 51 | //check allowCallerDefinedKeyValue setting 52 | boolean callerDefinedKeyValue = false; 53 | for(Object o:keyValues){ 54 | if(o != null){ 55 | callerDefinedKeyValue = true; 56 | } 57 | } 58 | if(!allowUserDefindKeyValue && callerDefinedKeyValue){ 59 | throw new RuntimeException("the key's position should be null!"); 60 | } 61 | 62 | //generate keys and set to mapArgs 63 | if(!callerDefinedKeyValue){ 64 | Object[] metadata = getElsValues(strategyMetadataEls,mapArgs); 65 | Object[] generatedKeyValues = generateKeys(strategy,metadata); 66 | setGeneratedKeyValues(mapArgs,generatedKeyValues,keyEls); 67 | } 68 | 69 | //call with the adjusted parameters 70 | try { 71 | return jp.proceed(mapArgs.values().toArray()); 72 | } catch (Throwable e) { 73 | throw e; 74 | } 75 | } 76 | 77 | 78 | private String[] getStrategyMetadataEls(GenerateId keyInject, ShardingContext shardingContext) { 79 | String[] strategyMetadataEls = keyInject.strategyMetadataEls(); 80 | if(strategyMetadataEls.length == 0){ 81 | strategyMetadataEls = shardingContext.generateIdMetadataEls(); 82 | } 83 | return strategyMetadataEls; 84 | } 85 | 86 | 87 | private void setGeneratedKeyValues(LinkedHashMap mapArgs, Object[] generatedKeyValues, 88 | String[] keyEls) { 89 | for(int i = 0; i < keyEls.length; i++ ){ 90 | spElHelper.setValue(keyEls[i], mapArgs, generatedKeyValues[i]); 91 | } 92 | } 93 | 94 | 95 | private Object[] generateKeys(String strategyStr,Object[] metaData) { 96 | KeyGenerateStrategy strategy = spElHelper.getValue(strategyStr); 97 | if(strategy == null){ 98 | throw new RuntimeException("can not find specifc key generate strategy:" + strategyStr); 99 | } 100 | 101 | return strategy.generateKey(metaData); 102 | } 103 | 104 | 105 | private Object[] getElsValues(String[] keyEls, LinkedHashMap mapArgs) { 106 | Object[] result = new Object[keyEls.length]; 107 | for(int i = 0; i < keyEls.length; i++){ 108 | result[i] = spElHelper.getValue(keyEls[i], mapArgs); 109 | } 110 | return result; 111 | } 112 | 113 | private LinkedHashMap getArgsLinkedHashMap(String[] parameterNames, Object[] args) { 114 | 115 | Assert.isTrue(parameterNames.length == args.length,"the length should be the same!"); 116 | 117 | LinkedHashMap map = new LinkedHashMap<>(args.length); 118 | for(int i = 0; i < args.length; i++){ 119 | map.put(parameterNames[i], args[i]); 120 | } 121 | 122 | return map; 123 | } 124 | 125 | 126 | 127 | 128 | private boolean getAllowUserDefindKeyValue(GenerateId keyInject, ShardingContext shardingContext) { 129 | int allow = keyInject.allowCallerDefinedKeyValue(); 130 | if(allow == 0){ 131 | allow = shardingContext.generateIdByCaller(); 132 | if(allow == 0){ 133 | throw new RuntimeException("Class annotation config error,can not find key generate keyNames!"); 134 | } 135 | } 136 | return allow == -1?false:true; 137 | } 138 | 139 | private String[] getFinalIdNames(GenerateId keyInject, ShardingContext shardingContext) { 140 | String[] keyNames = keyInject.keyEls(); 141 | if(keyNames.length == 0){ 142 | keyNames = shardingContext.generateIdEls(); 143 | if(keyNames.length == 0){ 144 | throw new RuntimeException("Class annotation config error,can not find key generate keyNames!"); 145 | }else{ 146 | for(String keyName:keyNames){ 147 | if(StringUtils.isEmpty(keyName)){ 148 | throw new RuntimeException("keyName can not be null or empty!"); 149 | } 150 | } 151 | } 152 | } 153 | return keyNames; 154 | } 155 | 156 | private String getFinalStrategy(GenerateId keyInject, ShardingContext shardingContext) { 157 | String strategy = keyInject.strategy(); 158 | if(StringUtils.isEmpty(strategy)){ 159 | strategy = shardingContext.generateIdStrategy(); 160 | if(StringUtils.isEmpty(strategy)){ 161 | throw new RuntimeException("Class annotation config error,can not find key generate strategy!"); 162 | } 163 | } 164 | return strategy; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/select/aspect/SelectDataSourceAspect.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.select.aspect; 2 | 3 | import java.util.LinkedHashMap; 4 | 5 | import org.aspectj.lang.ProceedingJoinPoint; 6 | import org.aspectj.lang.annotation.Around; 7 | import org.aspectj.lang.annotation.Aspect; 8 | import org.aspectj.lang.reflect.MethodSignature; 9 | import org.easydevelop.common.SpElHelper; 10 | import org.easydevelop.common.TransactionStatusHelper; 11 | import org.easydevelop.readonly.ReadOnlyDsSelectStrategy; 12 | import org.easydevelop.select.annotation.SelectDataSource; 13 | import org.easydevelop.select.strategy.SelectDataSourceStrategy; 14 | import org.easydevelop.sharding.DataSourceSet; 15 | import org.easydevelop.sharding.ShardingRoutingDataSource; 16 | import org.easydevelop.sharding.annotation.ShardingContext; 17 | import org.easydevelop.sharding.aspect.ShardingAspect; 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.core.annotation.Order; 20 | import org.springframework.util.Assert; 21 | import org.springframework.util.StringUtils; 22 | 23 | /** 24 | * @author xudeyou 25 | */ 26 | @Aspect 27 | @Order(-1) 28 | public class SelectDataSourceAspect { 29 | 30 | 31 | @Autowired 32 | private ShardingRoutingDataSource routingDataSource; 33 | 34 | @Autowired 35 | private SpElHelper spElHelper; 36 | 37 | @Autowired 38 | private TransactionStatusHelper transactionStatusHelper; 39 | 40 | 41 | @Autowired 42 | private ReadOnlyDsSelectStrategy readOnlySelectStrategy; 43 | 44 | @Around("@annotation(selectDataSource)") 45 | public Object around(ProceedingJoinPoint jp,SelectDataSource selectDataSource) throws Throwable{ 46 | 47 | ShardingContext sharding = ShardingAspect.getShardingContext(); 48 | if(sharding == null){ 49 | throw new RuntimeException("the ancestor caller's class shold declare shardingContext"); 50 | } 51 | 52 | //get configurations 53 | String[] keyEls = getFinalKeyEls(selectDataSource, sharding); 54 | String strategy = getFinalStrategy(selectDataSource, sharding); 55 | String dsSetKey = getDsSetKey(selectDataSource, sharding); 56 | boolean forceMaster = selectDataSource.forceMaster(); 57 | 58 | //get parameters based on keyNames 59 | MethodSignature signature = (MethodSignature) jp.getSignature(); 60 | LinkedHashMap mapArgs = getArgsLinkedHashMap(signature.getParameterNames(),jp.getArgs()); 61 | 62 | //get sharding key values 63 | Object[] keyValues = getElsValues(keyEls,mapArgs); 64 | 65 | //calculate the sharding dataSource number 66 | DataSourceSet dsSet = routingDataSource.getDataSourcesByDsSetName(dsSetKey); 67 | int num = selectShardingPartition(strategy,keyValues,dsSet.getMasterDataSources().size()); 68 | 69 | 70 | //is transaction readOnly check 71 | boolean readonly = false; 72 | Boolean checkReadOnly = transactionStatusHelper.isMethodReadOnly(signature.getMethod(), jp.getTarget().getClass()); 73 | if(checkReadOnly != null){ 74 | readonly = checkReadOnly; 75 | } 76 | 77 | //select the slave dataSource,if null then use master 78 | Integer slaveNumber = null; 79 | if(!forceMaster && readonly && dsSet.getSlaveDataSources() != null){ 80 | int partitionSlaveCount = dsSet.getSlaveDataSources().get(num).size(); 81 | slaveNumber = readOnlySelectStrategy.select(num, partitionSlaveCount); 82 | } 83 | 84 | //check whether the sharding dataSource already set 85 | String currentDsSet = routingDataSource.getCurrentLookupDsSet(); 86 | Integer currentLookupDsSequence = routingDataSource.getCurrentLookupDsSequence(); 87 | boolean ancestorCallExist = false; 88 | if(currentDsSet != null || currentLookupDsSequence != null){ 89 | ancestorCallExist = true; 90 | if(!dsSetKey.equals(currentDsSet) || num != currentLookupDsSequence.intValue()){ 91 | throw new RuntimeException("To guarantee specificed Isolation-level can not switch datasource!"); 92 | } 93 | } 94 | 95 | //set the current lookup key 96 | if(!ancestorCallExist){ 97 | routingDataSource.setCurrentLookupKey(dsSetKey, num,slaveNumber); 98 | } 99 | 100 | //call with the transaction 101 | try { 102 | return jp.proceed(); 103 | } catch (Throwable e) { 104 | throw e; 105 | } finally{ 106 | if(!ancestorCallExist){ 107 | routingDataSource.setCurrentLookupKey(null, null, null); 108 | } 109 | } 110 | } 111 | 112 | 113 | private int selectShardingPartition(String strategyStr,Object[] metaData,int datasourceSize) { 114 | SelectDataSourceStrategy strategy = spElHelper.getValue(strategyStr); 115 | if(strategy == null){ 116 | throw new RuntimeException("can not find specifc Sharding strategy:" + strategyStr); 117 | } 118 | 119 | return strategy.select(metaData, datasourceSize); 120 | } 121 | 122 | 123 | private Object[] getElsValues(String[] keyEls, LinkedHashMap mapArgs) { 124 | Object[] result = new Object[keyEls.length]; 125 | for(int i = 0; i < keyEls.length; i++){ 126 | result[i] = spElHelper.getValue(keyEls[i], mapArgs); 127 | } 128 | return result; 129 | } 130 | 131 | 132 | 133 | 134 | private LinkedHashMap getArgsLinkedHashMap(String[] parameterNames, Object[] args) { 135 | 136 | Assert.isTrue(parameterNames.length == args.length,"the length should be the same!"); 137 | 138 | LinkedHashMap map = new LinkedHashMap<>(args.length); 139 | for(int i = 0; i < args.length; i++){ 140 | map.put(parameterNames[i], args[i]); 141 | } 142 | 143 | return map; 144 | } 145 | 146 | 147 | 148 | private String[] getFinalKeyEls(SelectDataSource shardingMethod, ShardingContext sharding) { 149 | String[] keyEls = shardingMethod.keyNameEls(); 150 | if(keyEls.length == 0){ 151 | keyEls = sharding.shardingKeyEls(); 152 | if(keyEls.length == 0){ 153 | throw new RuntimeException("Class annotation config error,can not find key sharding ELs!"); 154 | }else{ 155 | for(String keyEl:keyEls){ 156 | if(StringUtils.isEmpty(keyEl)){ 157 | throw new RuntimeException("key EL can not be null or empty!"); 158 | } 159 | } 160 | } 161 | } 162 | return keyEls; 163 | } 164 | 165 | private String getDsSetKey(SelectDataSource shardingMethod, ShardingContext sharding) { 166 | String dsSet = shardingMethod.dsSet(); 167 | if(StringUtils.isEmpty(dsSet)){ 168 | dsSet = sharding.dataSourceSet(); 169 | if(StringUtils.isEmpty(dsSet)){ 170 | throw new RuntimeException("Class annotation config error,can not find corresponding dbset setting!"); 171 | } 172 | } 173 | return dsSet; 174 | } 175 | 176 | private String getFinalStrategy(SelectDataSource shardingMethod, ShardingContext sharding) { 177 | String strategy = shardingMethod.strategy(); 178 | if(StringUtils.isEmpty(strategy)){ 179 | strategy = sharding.shardingStrategy(); 180 | if(StringUtils.isEmpty(strategy)){ 181 | throw new RuntimeException("Class annotation config error,can not find sharding strategy!"); 182 | } 183 | } 184 | return strategy; 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/main/java/org/easydevelop/mapreduce/aspect/MapReduceAspect.java: -------------------------------------------------------------------------------- 1 | package org.easydevelop.mapreduce.aspect; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.concurrent.Callable; 6 | import java.util.concurrent.ExecutionException; 7 | import java.util.concurrent.ExecutorService; 8 | import java.util.concurrent.Executors; 9 | import java.util.concurrent.Future; 10 | import java.util.concurrent.TimeUnit; 11 | import java.util.concurrent.TimeoutException; 12 | 13 | import org.aspectj.lang.ProceedingJoinPoint; 14 | import org.aspectj.lang.annotation.Around; 15 | import org.aspectj.lang.annotation.Aspect; 16 | import org.aspectj.lang.reflect.MethodSignature; 17 | import org.easydevelop.common.SpElHelper; 18 | import org.easydevelop.common.TransactionStatusHelper; 19 | import org.easydevelop.mapreduce.annotation.MapReduce; 20 | import org.easydevelop.mapreduce.strategy.ReduceStrategy; 21 | import org.easydevelop.readonly.ReadOnlyDsSelectStrategy; 22 | import org.easydevelop.sharding.DataSourceSet; 23 | import org.easydevelop.sharding.ShardingRoutingDataSource; 24 | import org.easydevelop.sharding.annotation.ShardingContext; 25 | import org.easydevelop.sharding.aspect.ShardingAspect; 26 | import org.springframework.beans.factory.annotation.Autowired; 27 | import org.springframework.beans.factory.annotation.Value; 28 | import org.springframework.core.annotation.Order; 29 | import org.springframework.util.StringUtils; 30 | 31 | /** 32 | * @author xudeyou 33 | */ 34 | @Aspect 35 | @Order(-1) 36 | public class MapReduceAspect { 37 | 38 | private ExecutorService executor = Executors.newCachedThreadPool(); 39 | 40 | @Value("${sharding.method.aggregation.timeout.seconds:10}") 41 | private int timeout; 42 | 43 | @Autowired 44 | private ShardingRoutingDataSource routingDataSource; 45 | 46 | @Autowired 47 | private TransactionStatusHelper transactionStatusHelper; 48 | 49 | @Autowired 50 | private ReadOnlyDsSelectStrategy readOnlySelectStrategy; 51 | 52 | @Autowired 53 | private SpElHelper spElHelper; 54 | 55 | @SuppressWarnings({ "rawtypes", "unchecked" }) 56 | @Around("@annotation(mapReduce)") 57 | public Object around(ProceedingJoinPoint jp,MapReduce mapReduce) throws Throwable{ 58 | 59 | //get Sharding annotation from method's class 60 | ShardingContext shardingContext = ShardingAspect.getShardingContext(); 61 | if(shardingContext == null){ 62 | throw new RuntimeException("Class annotation config error,can not find Sharding Annotation!"); 63 | } 64 | 65 | //get configurations 66 | String strategy = getFinalStrategy(mapReduce, shardingContext); 67 | String dsSetKey = getDsSetKey(mapReduce, shardingContext); 68 | boolean forceMaster = mapReduce.isForceMaster(); 69 | 70 | //check whether the sharding dataSource already set 71 | String currentDsSet = routingDataSource.getCurrentLookupDsSet(); 72 | Integer currentLookupDsSequence = routingDataSource.getCurrentLookupDsSequence(); 73 | if(currentDsSet != null || currentLookupDsSequence != null){ 74 | throw new RuntimeException("should not execute this method in another transaction!"); 75 | } 76 | 77 | //check result return type 78 | MethodSignature signature = (MethodSignature) jp.getSignature(); 79 | Class[] parameterTypes = signature.getParameterTypes(); 80 | ReduceResultHolder holder = null; 81 | boolean returnByResultHolder = false; 82 | for(int i = 0; i < parameterTypes.length; i++){ 83 | Class clazz = parameterTypes[i]; 84 | if(ReduceResultHolder.class.isAssignableFrom(clazz)){ 85 | holder = (ReduceResultHolder) jp.getArgs()[i]; 86 | returnByResultHolder = true; 87 | break; 88 | } 89 | } 90 | 91 | if(holder == null && returnByResultHolder){ 92 | throw new RuntimeException("you must pass in the ResultHolder instance!"); 93 | } 94 | 95 | Class returnType = signature.getReturnType(); 96 | if(returnType != void.class && returnByResultHolder){ 97 | throw new RuntimeException("you can only choose to return with RetunValue or with the ResultHolder"); 98 | } 99 | 100 | //generate Callable objects 101 | DataSourceSet dsSet = routingDataSource.getDataSourcesByDsSetName(dsSetKey); 102 | int partitionCount = dsSet.getMasterDataSources().size(); 103 | List> listFuture = new ArrayList<>(partitionCount); 104 | final ReduceResultHolder finalHolder = holder; 105 | if(finalHolder != null){ 106 | finalHolder.init(partitionCount); 107 | } 108 | 109 | for(int i = 0; i < partitionCount; i++){ 110 | final int k = i; 111 | Callable callable = new Callable() { 112 | @Override 113 | public Object call() throws Exception { 114 | if(finalHolder != null){ 115 | finalHolder.setShardingResultPosition(k); 116 | } 117 | 118 | //is transaction readOnly check 119 | boolean readonly = false; 120 | Boolean checkReadOnly = transactionStatusHelper.isMethodReadOnly(signature.getMethod(), jp.getTarget().getClass()); 121 | if(checkReadOnly != null){ 122 | readonly = checkReadOnly; 123 | } 124 | 125 | //select the slave dataSource,if null then use master 126 | Integer slaveNumber = null; 127 | if(!forceMaster && readonly && dsSet.getSlaveDataSources() != null){ 128 | int partitionSlaveCount = dsSet.getSlaveDataSources().get(k).size(); 129 | slaveNumber = readOnlySelectStrategy.select(k, partitionSlaveCount); 130 | } 131 | 132 | 133 | routingDataSource.setCurrentLookupKey(dsSetKey, k, slaveNumber); 134 | try { 135 | Object proceed = jp.proceed(); 136 | 137 | if(finalHolder != null && !finalHolder.isShardingResultSet()){ 138 | throw new RuntimeException("you should set the sharding result! Even though it's null!"); 139 | } 140 | 141 | return proceed; 142 | } catch (Exception e) { 143 | throw e; 144 | } catch (Throwable e) { 145 | throw new RuntimeException(e); 146 | } finally{ 147 | routingDataSource.setCurrentLookupKey(null, null, null); 148 | } 149 | } 150 | }; 151 | listFuture.add(executor.submit(callable)); 152 | } 153 | 154 | 155 | //sharding result is not at the returnValue,we decorate it 156 | if(returnByResultHolder){ 157 | List> orignFutures = listFuture; 158 | listFuture = new ArrayList<>(orignFutures.size()); 159 | 160 | for(int i = 0; i < orignFutures.size(); i++){ 161 | Future orign = orignFutures.get(i); 162 | listFuture.add(new ReduceHolderFutureWrapper<>(i, orign,finalHolder)); 163 | } 164 | } 165 | 166 | 167 | //get sharding and reduce result class type 168 | Class shardingResultClass = returnType; 169 | Class reduceResultClass = returnType; 170 | if(returnByResultHolder){ 171 | //if not returnByResultHolder, sharding result class will be the same with reduce class 172 | //else we get return type by resultHolder 173 | shardingResultClass = finalHolder.getShardingResultClass(); 174 | reduceResultClass = finalHolder.getReduceResultClass(); 175 | } 176 | 177 | 178 | //get and execute aggregation strategy 179 | ReduceStrategy reduceStrategy = getStrategy(strategy); 180 | if(reduceStrategy == null){ 181 | throw new RuntimeException("can not find specified reduceStrategy:" + strategy); 182 | } 183 | Object aggregationResult = reduceStrategy.reduce(listFuture,shardingResultClass,reduceResultClass); 184 | 185 | //check the return form 186 | if(returnByResultHolder){ 187 | holder.setResult(aggregationResult); 188 | return null; 189 | } else { 190 | return aggregationResult; 191 | } 192 | 193 | } 194 | 195 | private static class ReduceHolderFutureWrapper implements Future{ 196 | 197 | public ReduceHolderFutureWrapper(int shardingSeq,Future orign,ReduceResultHolder reduceResultHolder){ 198 | this.orign = orign; 199 | this.shardingSeq = shardingSeq; 200 | this.reduceResultHolder = reduceResultHolder; 201 | } 202 | 203 | private Future orign; 204 | private int shardingSeq; 205 | private ReduceResultHolder reduceResultHolder; 206 | 207 | @Override 208 | public boolean cancel(boolean mayInterruptIfRunning) { 209 | return orign.cancel(mayInterruptIfRunning); 210 | } 211 | 212 | @Override 213 | public boolean isCancelled() { 214 | return orign.isCancelled(); 215 | } 216 | 217 | @Override 218 | public boolean isDone() { 219 | return orign.isDone(); 220 | } 221 | 222 | @SuppressWarnings("unchecked") 223 | @Override 224 | public V get() throws InterruptedException, ExecutionException { 225 | orign.get();//wait till execute finished 226 | return (V) reduceResultHolder.getShardingResults().get(shardingSeq); 227 | } 228 | 229 | @SuppressWarnings("unchecked") 230 | @Override 231 | public V get(long timeout, TimeUnit unit) 232 | throws InterruptedException, ExecutionException, TimeoutException { 233 | orign.get(timeout, unit);//wait till execute finished 234 | return (V) reduceResultHolder.getShardingResults().get(shardingSeq); 235 | } 236 | 237 | } 238 | 239 | @SuppressWarnings("rawtypes") 240 | private ReduceStrategy getStrategy(String strategy) { 241 | return spElHelper.getValue(strategy); 242 | } 243 | 244 | private String getDsSetKey(MapReduce aggregationMethod, ShardingContext shardingContext) { 245 | String dsSet = aggregationMethod.dsSet(); 246 | if(StringUtils.isEmpty(dsSet)){ 247 | dsSet = shardingContext.dataSourceSet(); 248 | if(StringUtils.isEmpty(dsSet)){ 249 | throw new RuntimeException("Class annotation config error,can not find corresponding dbset setting!"); 250 | } 251 | } 252 | return dsSet; 253 | } 254 | 255 | private String getFinalStrategy(MapReduce mapReduce, ShardingContext shardingContext) { 256 | String strategy = mapReduce.reduceStrategy(); 257 | if(StringUtils.isEmpty(strategy)){ 258 | strategy = shardingContext.reduceStrategy(); 259 | if(StringUtils.isEmpty(strategy)){ 260 | throw new RuntimeException("Class annotation config error,can not find Aggregation strategy!"); 261 | } 262 | } 263 | return strategy; 264 | } 265 | } 266 | --------------------------------------------------------------------------------