spring 多数据源事务问题

来源:互联网 发布:淘宝售假次数 编辑:程序博客网 时间:2024/05/21 16:59

spring整合mybatis,2个数据源,使用DynamicDataSource+aop,在方法调用之前根据方法上的注解来切换数据源,

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xmlns:context="http://www.springframework.org/schema/context"       xmlns:tx="http://www.springframework.org/schema/tx"       xmlns:aop="http://www.springframework.org/schema/aop"       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">   <context:property-placeholder location="classpath:db.properties"/>    <!--<bean id="captchaProducer" class="com.google.code.kaptcha.impl.DefaultKaptcha">-->        <!--<property name="config">-->            <!--<bean class="com.google.code.kaptcha.util.Config">-->                <!--<constructor-arg>-->                    <!--<props>-->                        <!--<prop key="kaptcha.border">yes</prop>-->                        <!--<prop key="kaptcha.border.color">105,179,90</prop>-->                        <!--<prop key="kaptcha.textproducer.font.color">blue</prop>-->                        <!--<prop key="kaptcha.image.width">250</prop>-->                        <!--<prop key="kaptcha.image.height">90</prop>-->                        <!--<prop key="kaptcha.textproducer.font.size">80</prop>-->                        <!--<prop key="kaptcha.session.key">code</prop>-->                        <!--<prop key="kaptcha.textproducer.char.length">4</prop>-->                        <!--<prop key="kaptcha.textproducer.font.names">瀹嬩綋,妤蜂綋,寰蒋闆呴粦</prop>-->                    <!--</props>-->                <!--</constructor-arg>-->            <!--</bean>-->        <!--</property>-->    <!--</bean>-->   <bean id="BasicDataSource" class="org.apache.commons.dbcp2.BasicDataSource">        <property name="url" value="${dbcp2.url}"/>        <property name="driverClassName" value="${dbcp2.driverClassName}"/>        <property name="username" value="${dbcp2.username}"/>        <property name="password" value="${dbcp2.password}"/>        <property name="initialSize" value="${dbcp2.initialSize}"/>        <property name="timeBetweenEvictionRunsMillis" value="${dbcp2.timeBetweenEvictionRunsMillis}"/>        <property name="poolPreparedStatements" value="${dbcp2.poolPreparedStatements}"/>        <property name="maxOpenPreparedStatements" value="${dbcp2.maxOpenPreparedStatements}"/>        <property name="removeAbandonedTimeout" value="${dbcp2.removeAbandonedTimeout}"/>        <property name="testOnBorrow" value="${dbcp2.testOnBorrow}"/>        <property name="testOnReturn" value="${dbcp2.testOnReturn}"/>        <property name="testWhileIdle" value="${dbcp2.testWhileIdle}"/>    </bean>    <bean id="BasicDataSource2" class="org.apache.commons.dbcp2.BasicDataSource">        <property name="url" value="${dbcp2.url}"/>        <property name="driverClassName" value="${dbcp2.driverClassName}"/>        <property name="username" value="${dbcp2.username}"/>        <property name="password" value="${dbcp2.password}"/>        <property name="initialSize" value="${dbcp2.initialSize}"/>        <property name="timeBetweenEvictionRunsMillis" value="${dbcp2.timeBetweenEvictionRunsMillis}"/>        <property name="poolPreparedStatements" value="${dbcp2.poolPreparedStatements}"/>        <property name="maxOpenPreparedStatements" value="${dbcp2.maxOpenPreparedStatements}"/>        <property name="removeAbandonedTimeout" value="${dbcp2.removeAbandonedTimeout}"/>        <property name="testOnBorrow" value="${dbcp2.testOnBorrow}"/>        <property name="testOnReturn" value="${dbcp2.testOnReturn}"/>        <property name="testWhileIdle" value="${dbcp2.testWhileIdle}"/>    </bean>    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">        <property name="dataSource" ref="dataSource" />    </bean>    <bean name="scannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />        <property name="basePackage" value="com.lutai.admin.*.dao"/>    </bean>    <bean id="sqlTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        <property name="dataSource" ref="dataSource" />    </bean>    <!--&lt;!&ndash; 浣跨敤annotation瀹氫箟浜嬪姟 &ndash;&gt;-->    <!--<aop:aspectj-autoproxy expose-proxy="true"/>-->    <!--<bean id="test" class="com.zheyue.authserver.controller.TestBean"></bean>-->    <!--<bean id="a" class="com.zheyue.authserver.controller.AspectTest"></bean>-->    <tx:annotation-driven transaction-manager="sqlTransactionManager" order="2"/>    <bean id="userService" class="com.zheyue.authserver.service.impl.UserServiceImpl"></bean>    <!-- 配置多数据源映射关系 -->    <bean id="dataSource" class="com.zheyue.authserver.datasource.DynamicDataSource">        <property name="targetDataSources">            <map key-type="java.lang.String">                <entry key="dataSource1" value-ref="BasicDataSource"></entry>                <entry key="dataSource2" value-ref="BasicDataSource2"></entry>            </map>        </property>        <!-- 默认目标数据源为你主库数据源 -->        <property name="defaultTargetDataSource" ref="BasicDataSource"/>    </bean>    <bean id="exchange" class="com.zheyue.authserver.datasource.DataSourceExchange"></bean>    <aop:config expose-proxy="true">        <aop:pointcut id="pointcut" expression="execution(* *.save*(..))"/>        <aop:advisor advice-ref="exchange" pointcut-ref="pointcut" order="1"/>    </aop:config></beans>
自定义注解

package com.zheyue.authserver.datasource;import java.lang.annotation.*;/** * Created by fd on 2016/10/18. */@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface DataSource {    String name() default DataSource.master;    public static String master = "BasicDataSource";    public static String slave = "BasicDataSource2";}
aop拦截

package com.zheyue.authserver.datasource;import org.aopalliance.intercept.MethodInterceptor;import org.aopalliance.intercept.MethodInvocation;/** * Created by fd on 2016/10/18. */public class DataSourceExchange implements MethodInterceptor {    public Object invoke(MethodInvocation invocation) throws Throwable {        System.out.println(invocation.getMethod().getAnnotation(DataSource.class));        System.out.println(invocation.getMethod().getDeclaredAnnotation(DataSource.class));        String dataSourceName = invocation.getMethod().getAnnotation(DataSource.class).name();        DataSourceHolder.setDataSource(dataSourceName);        System.out.println("使用数据源"+dataSourceName);//        try {//            invocation.proceed();//        } catch (Exception e) {//            e.printStackTrace();//        } finally {////        }        invocation.proceed();        return null;    }}
threadlocal

package com.zheyue.authserver.datasource;/** * Created by fd on 2016/10/18. */public class DataSourceHolder {    public static final ThreadLocal<String> dataSources = new ThreadLocal<String>();    public static void setDataSource(String dataSourceName) {        dataSources.set(dataSourceName);    }    public static String getDataSource() {        return (String)dataSources.get();    }    public static void clear() {        dataSources.remove();    }}
动态数据源

package com.zheyue.authserver.datasource;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/** * Created by fd on 2016/10/18. */public class DynamicDataSource extends AbstractRoutingDataSource {    @Override    protected Object determineCurrentLookupKey() {        return DataSourceHolder.getDataSource();    }}
业务

package com.zheyue.authserver.service.impl;import com.zheyue.authserver.datasource.DataSource;import com.zheyue.authserver.service.UserService;import org.springframework.aop.framework.AopContext;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;/** * Created by fd on 2016/10/18. */public class UserServiceImpl implements UserService {    public void save() {        System.out.println("开始执行sava()方法");        try {            UserService userService = (UserService) AopContext.currentProxy();            userService.save2();        } catch (Exception e) {            e.printStackTrace();        }        finally {        }    }    public void save2() {        System.out.println("开始执行sava2()方法");    }}
package com.zheyue.authserver.service;import com.zheyue.authserver.datasource.DataSource;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;/** * Created by fd on 2016/10/18. */public interface UserService {    @DataSource(name = "BasicDataSource")    @Transactional(propagation = Propagation.REQUIRED)    void save();    @DataSource(name = "BasicDataSource2")    @Transactional(propagation = Propagation.MANDATORY)    void save2();}
执行

public static void main(String[] args) {    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");    UserService userService = (UserService)applicationContext.getBean("userService");    try {        userService.save();    } catch (Exception e) {        e.printStackTrace();    }    finally {    }}

新建事务会从datasource获取一个连接connection,存在ConnectionHolder中,


事务的创建之后会把datasource作为key,connectionHolder作为value放进map中,然后将map放进名为resources的threadlocal中,也就是绑定当前线程了,

然后当你使用诸如propagation = Propagation.MANDATORY传播级别时,会取出当前线程绑定的connectionHolder,然后使用。

这就出现了一个问题

上面的动态数据源切换,对于某一个线程来说,新建一个事务是没有问题的,但是你这个线程需要使用2个数据源,如果重用事务,它使用的数据源就是之前的,

所以切换数据源也没有用了。。。

所以

1.一个线程就不要使用多个数据源了。。

2.或者不要重用事务,设置好隔离级别。

所以需要使用分布式事务???

0 0
原创粉丝点击