├── DataSourceAdvice ├── DataSourceSwitcher ├── DynamicDataSource.java ├── README.md └── application-data-source.xml /DataSourceAdvice: -------------------------------------------------------------------------------- 1 | import java.lang.reflect.Method; 2 | import java.text.MessageFormat; 3 | import java.util.List; 4 | 5 | import org.apache.log4j.Logger; 6 | import org.springframework.aop.AfterReturningAdvice; 7 | import org.springframework.aop.MethodBeforeAdvice; 8 | 9 | /** 10 | * @author aelns 11 | * 12 | */ 13 | public class DataSourceAdvice implements MethodBeforeAdvice, AfterReturningAdvice { 14 | 15 | private DataSourceSwitcher switcher; 16 | 17 | private Logger logger = Logger.getLogger(DataSourceAdvice.class); 18 | 19 | private List readMethodNames; 20 | 21 | public List getReadMethodNames() { 22 | return readMethodNames; 23 | } 24 | 25 | public void setReadMethodNames(List readMethodNames) { 26 | this.readMethodNames = readMethodNames; 27 | } 28 | 29 | /** 30 | * before calling the service method, automatically switching data source 31 | */ 32 | public void before(Method method, Object[] args, Object target) 33 | throws Throwable { 34 | if (isReadMethod(method.getName())) { 35 | switcher.setSlave(); 36 | } else { 37 | switcher.setMaster(); 38 | } 39 | if (logger.isDebugEnabled()) { 40 | logger.debug(MessageFormat.format( 41 | "Point cut: {0}-{1} switch data source to {2}", 42 | target.getClass().getName(), method.getName(), switcher.getDataSource())); 43 | } 44 | } 45 | 46 | /** 47 | * @param methodName 48 | * @return 49 | */ 50 | private boolean isReadMethod(String methodName) { 51 | for (String name : readMethodNames) { 52 | if (methodName.startsWith(name)) { 53 | return true; 54 | } 55 | } 56 | return false; 57 | } 58 | 59 | public void afterReturning(Object arg0, Method method, Object[] args, 60 | Object target) throws Throwable { 61 | } 62 | 63 | /** 64 | * @param method 65 | * @param args 66 | * @param target 67 | * @param e 68 | * @throws Throwable 69 | */ 70 | public void afterThrowing(Method method, Object[] args, Object target, 71 | Exception e) throws Throwable { 72 | if (e instanceof java.sql.SQLException) { 73 | logger.error("Occur error, will switch to master datasource"); 74 | logger.error(e, e); 75 | switcher.setMaster(); 76 | } 77 | } 78 | 79 | public DataSourceSwitcher getSwitcher() { 80 | return switcher; 81 | } 82 | 83 | public void setSwitcher(DataSourceSwitcher switcher) { 84 | this.switcher = switcher; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /DataSourceSwitcher: -------------------------------------------------------------------------------- 1 | import java.util.List; 2 | import java.util.concurrent.atomic.AtomicInteger; 3 | 4 | /** 5 | * 6 | * @author aelns 7 | * 8 | */ 9 | public class DataSourceSwitcher { 10 | 11 | private ThreadLocal contextHolder = new ThreadLocal(); 12 | 13 | private AtomicInteger index = new AtomicInteger(0); 14 | 15 | private List slaves; 16 | 17 | public void setDataSource(String dataSource) { 18 | contextHolder.set(dataSource); 19 | } 20 | 21 | public void setMaster() { 22 | clearDataSource(); 23 | setDataSource("master"); 24 | } 25 | 26 | public void setSlave() { 27 | clearDataSource(); 28 | if (index.get() >= slaves.size()) { 29 | index.set(0); 30 | } 31 | setDataSource(slaves.get(index.get() % slaves.size())); 32 | index.addAndGet(1); 33 | } 34 | 35 | public String getDataSource() { 36 | return (String) contextHolder.get(); 37 | } 38 | 39 | public void clearDataSource() { 40 | contextHolder.remove(); 41 | } 42 | 43 | public List getSlaves() { 44 | return slaves; 45 | } 46 | 47 | public void setSlaves(List slaves) { 48 | this.slaves = slaves; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /DynamicDataSource.java: -------------------------------------------------------------------------------- 1 | import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; 2 | 3 | /** 4 | * 5 | * @author aelns 6 | * 7 | */ 8 | public class DynamicDataSource extends AbstractRoutingDataSource { 9 | 10 | private DataSourceSwitcher switcher; 11 | 12 | @Override 13 | protected Object determineCurrentLookupKey() { 14 | return switcher.getDataSource(); 15 | } 16 | 17 | public DataSourceSwitcher getSwitcher() { 18 | return switcher; 19 | } 20 | 21 | public void setSwitcher(DataSourceSwitcher switcher) { 22 | this.switcher = switcher; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # separate-read-and-write 2 | 基于Spring AOP的数据库读写分离实现, 这里演示使用的是Mybatis + MySQL + Spring AOP 3 | 4 | 对于多个数据源简单的实现为逐个轮询,如果有需要可以自己可以实现其他的算法 5 | -------------------------------------------------------------------------------- /application-data-source.xml: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | classpath*:/com/test/dao/mapper/*.xml 33 | classpath*:/com/test/dao/customize/*.xml 34 | 35 | 36 | 37 | 38 | 39 | 41 | 42 | 43 | 44 | 45 | 46 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | slave1 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | clientEncoding=utf-8 90 | 91 | 92 | true 93 | false 94 | false 95 | SELECT NOW() FROM dual 96 | 1 97 | 30000 98 | 20 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | clientEncoding=utf-8 117 | 118 | 119 | true 120 | false 121 | false 122 | SELECT NOW() FROM dual 123 | 1 124 | 30000 125 | 20 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | query 170 | use 171 | get 172 | count 173 | find 174 | list 175 | load 176 | search 177 | populate 178 | login 179 | 180 | 181 | 182 | 183 | 184 | 187 | 188 | 189 | --------------------------------------------------------------------------------