<!-- 数据源(主库) --> <bean id="dataSource_main" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driverclass}" /> <property name="jdbcUrl" value="${jdbc.url}" /> <property name="user" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <property name="maxPoolSize" value="${c3p0.pool.size.max}" /> <property name="minPoolSize" value="${c3p0.pool.size.min}" /> <property name="initialPoolSize" value="${c3p0.pool.size.ini}" /> <property name="acquireIncrement" value="${c3p0.pool.size.increment}" /> </bean> <!-- 数据源(从库) --> <bean id="dataSource_1" parent="dataSource_main"> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/lsn_surveypark0909_1" /> </bean> <!-- 数据源路由器 --> <bean id="dataSource_router" class="cn.itcast.surveypark.datasource.SurveyparkDataSourceRouter"> <property name="targetDataSources"> <map> <entry key="odd" value-ref="dataSource_main" /> <entry key="even" value-ref="dataSource_1" /> </map> </property> <property name="defaultTargetDataSource" ref="dataSource_main" /> </bean>
<!-- aop事务配置 --> <aop:config> <!-- 事务切入点 --> <aop:pointcut expression="execution(* *..*Service.*(..))" id="txPointcut"/> <!-- 日志切入点 --> <aop:pointcut expression="(execution(* *..*Service.save*(..)) or execution(* *..*Service.update*(..)) or execution(* *..*Service.delete*(..)) or execution(* *..*Service.batch*(..)) or execution(* *..*Service.new*(..))) and !bean(logService)" id="loggerPointcut"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" order="1"/> <!-- 配置日志切面 --> <aop:aspect id="loggerAspect" ref="logger" order="0"> <aop:around method="record" pointcut-ref="loggerPointcut"/> </aop:aspect> </aop:config>
/** * 调查令牌,绑定到当前的线程,传播到数据源路由器.进行分库判断 */ public class SurveyToken { private Survey currentSurvey ; private static ThreadLocal<SurveyToken> t = new ThreadLocal<SurveyToken>(); public Survey getCurrentSurvey() { return currentSurvey; } public void setCurrentSurvey(Survey currentSurvey) { this.currentSurvey = currentSurvey; } /** * 将令牌对象绑定到当前线程 */ public static void bindingToken(SurveyToken token){ t.set(token); } /** * 从当前线程取得绑定的令牌对象 */ public static SurveyToken getCurrentToken(){ return t.get() ; } /** * 解除令牌的绑定 */ public static void unbindToken(){ t.remove() ; } }
主要思想是自定义一个方法继承AbstractRoutingDataSource,在保存答案方法前绑定令牌到当前线程,然后用debug模式看在哪里会调用数据路由器,需等答案保存完毕立刻
解除令牌的绑定,然后log保存就不会受影响(因为从库没有log表)。所以要注意2个切面配置顺序,可以看图
特别注意: 由于分库会有并发情况,需要把令牌的绑定到当前线程,然后取也是在同一个线程取。
//绑定令牌到当前线程 SurveyToken token = new SurveyToken(); token.setCurrentSurvey(getCurrentSurvey()); SurveyToken.bindingToken(token); //TODO 答案入库 surveyService.saveAnswers(processAnswers());
/** * 自定义数据源路由器,根据id来判断是保存在哪个库中,实际工作中情况肯定比这复杂。 */ public class SurveyparkDataSourceRouter extends AbstractRoutingDataSource { protected Object determineCurrentLookupKey() { SurveyToken token = SurveyToken.getCurrentToken(); if(token != null){ int id = token.getCurrentSurvey().getId(); //解除绑定 SurveyToken.unbindToken(); return (id % 2) == 0?"even":"odd" ; } return null; } }