spring的事务使用有几种方式注解式事务如何配置和使用

spring的事务使用有几种方式注解式事务如何配置和使用,第1张

Spring提供的事务管理可以分为两类:编程式的和声明式的。

编程式的,比较灵活,但是代码量大,存在重复的代码比较多;声明式的比编程式的更灵活方便。

1、传统使用JDBC的事务管理

以往使用JDBC进行数据 *** 作,使用DataSource,从数据源中得到Connection,我们知道数据源是线程安全的,而连接不是线程安全的,所以对每个请求都是从数据源中重新取出一个连接。一般的数据源由容器进行管理,包括连接池。例如TOMCAT,WEBSPHERE,WEBLOGIC等这些J2EE商业容器都提供了这个功能。

以往的我们使用JDBC在写代码时,事务管理可能会是这样:

Connection conn = null;

try{

conn = DBConnectionFactorygetConnection;

connsetAutoCommit(false);

//do something

conncommit(); //commit transcation

}catch(Exception e){

connrollback();

}

finally{

try{

connclose();

} catch(SQLException se){ //do sth}

//close ResultSet,PreparedStatement,Connection

//notice:Maybe ocurr Exception when u close rs,pstmt,conn

}

按照以往的思路来写代码,代码量比较长,而且容易疏忽,忘掉一些try/catch,引发一些异常无法catch,虽然有时候我们会写DBTool类,来关闭这些资源,并且保证在关闭这些资源时,不向外抛异常,但是这样做会导致额外的麻烦。

2、Spring提供的编程式的事务处理

Spring提供了几个关于事务处理的类:TransactionDefinition //事务属性定义

TranscationStatus //代表了当前的事务,可以提交,回滚。

PlatformTransactionManager这个是spring提供的用于管理事务的基础接口,其下有一个实现的抽象类AbstractPlatformTransactionManager,我们使用的事务管理类例如DataSourceTransactionManager等都是这个类的子类。

我们使用编程式的事务管理流程可能如下:

(1) 声明数据源。

(2) 声明一个事务管理类,例如:DataSourceTransactionManager,HibernateTransactionManger,JTATransactionManager等

(3) 在我们的代码中加入事务处理代码:

TransactionDefinition td = new TransactionDefinition();

TransactionStatus ts = transactionManagergetTransaction(td);

try{

//do sth

transactionManagercommit(ts);

}catch(Exception e){transactionManagerrollback(ts);}

使用Spring提供的事务模板TransactionTemplate:

void add()

{

transactionTemplateexecute( new TransactionCallback(){

pulic Object doInTransaction(TransactionStatus ts)

{ //do sth}

}

}

TransactionTemplate也是为我们省去了部分事务提交、回滚代码;定义事务模板时,需注入事务管理对象。

3、Spring声明式事务处理

Spring声明式事务处理也主要使用了IoC,AOP思想,提供了TransactionInterceptor拦截器和常用的代理类TransactionProxyFactoryBean,可以直接对组件进行事务代理。

使用TransactionInterceptor的步骤:

(1)定义数据源,事务管理类

(2)定义事务拦截器,例如:

<bean id = "transactionInterceptor"

class="orgspringframeworktransactioninterceptorTransactionInterceptor">

<property name="transactionManager"><ref bean="transactionManager"/></property>

<property name="transactionAttributeSource">

<value>

comtestUserManagerr=PROPAGATION_REQUIRED

</value>

</property>

</bean>

(3)为组件声明一个代理类:ProxyFactoryBean

<bean id="userManager" class="orgspringframeworkaopframeworkProxyFactoryBean">

<property name="proxyInterfaces"><value>comtestUserManager</value></property>

<property name="interceptorNames">

<list>

<idref local="transactionInterceptor"/>

</list>

</property>

</bean>

使用TransactionProxyFactoryBean:

<bean id="userManager"

class="orgspringframeworktransactioninterceptorTransactionProxyFactoryBean">

<property name="transactionManager"><ref bean="transactionManager"/></property>

<property name="target"><ref local="userManagerTarget"/></property>

<property name="transactionAttributes">

<props>

<prop key="insert">PROPAGATION_REQUIRED</prop>

<prop key="update">PROPAGATION_REQUIRED</prop>

<prop key="">PROPAGATION_REQUIRED,readOnly</prop>

</props>

</property>

</bean>

TransactionProxyFactoryBean只是为组件的事务代理,如果我们要给组件添加一些业务方面的验证等,可以使用TransactionTemplate加拦截器方式,为组件添加多个拦截器,spring AOP中提供了三类Advice,即前增强,后增强,抛出异常时的增强,可以灵活使用。

两种Spring事务管理方式:编程式、声明式。

Spring提供两种方式的编程式事务管理,分别是:使用TransactionTemplate和直接使用PlatformTransactionManager。

1TransactionTempale采用和其他Spring模板,如JdbcTempalte和HibernateTemplate一样的方法。它使用回调方法,把应用程序从处理取得和释放资源中解脱出来。如同其他模板,TransactionTemplate是线程安全的。代码片段:

Object result = ttexecute(new TransactionCallback(){

public Object doTransaction(TransactionStatus status){

updateOperation();

return resultOfUpdateOperation();

}

});

使用TransactionCallback()可以返回一个值。如果使用TransactionCallbackWithoutResult则没有返回值。

2也可以使用PlatformTransactionManager直接管理事务。简单地通过一个bean引用给你的bean传递一个你使用的 PlatformTransaction对象。然后,使用TransactionDefinition和TransactionStatus对象就可以发起、回滚、提交事务。如下片段:

DefaultTransactionDefinition def= new DefaultTransactionDefinition(); //new 一个事务 defsetPropagationBehavior(TransactionDefinitionPROPAGATION_REQUIRED); // 初始化事务,参数定义事务的传播类型; TransactionStatus status = transactionManagergetTransaction(def); //获得事务状态 try{ …………… transactionManagercommit(status); //提交事务; }catch(…){ transactionManagerrollback(status); //回滚事务; }

Spring也提供声明式事务管理。这是通过AOP实现的。大多数Spring用户选择声明式事务管理,这是最少影响应用代码的选择,因而这是和非侵入性的轻量级容器的观念是一致的。

1)通常通过TransactionProxyFactoryBean设置Spring事务代理。需要一个目标对象包装在事务代理中。这个目标对象一般是一个普通Javabean。当我们定义TransactionProxyFactoryBean时,必须提供一个相关的 PlatformTransactionManager的引用和事务属性。事务属性含有事务定义。例如:

<bean id="transactionService"class="orgspringframework transactioninterceptorTransactionProxyFactoryBean">

<property name="transactionManager">

<ref local="transactionManager"/>

property>

<property name="target">

<ref local="transactionServiceControl"/>

property>

<property name="transactionAttributes">

<props>

<prop key=”insert”>PROPAGATION_REQUIRED,-MyCheckedExceptionprop>

<prop key=”update”>PROPAGATION_REQUIREDprop>

<prop key=””>PROPAGATION_REQUIRED,readOnlyprop>

props>

property>

bean>

事务代理会实现目标对象的接口:这里是属性名是target的引用。id是transactionServiceControl。(使用CGLIB也可以实现具体类的代理。只要设置proxyTargetClass属性为true即可。如果目标对象没有实现任何接口,这将自动设置该属性为true。通常,我们希望面向接口编程。)使用proxyInterfaces属性来限定事务代理来代理指定接口也是可以。 也可以通过从orgspringframeworkaopframeworkProxyConfig继承或所有AOP代理工厂共享的属性来定制 TransactionProxyFactoryBean行为。

然后,说说属性名是transactionAttributes意义:

这里的transactionAttributes属性是定义在 orgspringframeworktransactioninterceptorNameMathTransactionAttributeSource 中的属性格式设置。这个包括通配符的方法名称映射是很直观的,如”insert”。注意insert的映射的值包括回滚规则。”- MyCheckException”指定如果方法抛出MyCheckException或它的子类,事务会自动回滚。可以用逗号分隔多个回滚规则。“-” 前缀强制回滚,“+”前缀指定提交(这允许即使抛出unchecked异常时也可以提交事务)。“PROPAGATION_REQUIRED”指定事务传播范围。

TransactionProxyFactoryBean允许你通过“preInterceptors”和 “postInterceptors”属性设置前或后的拦截 *** 作。可以设置任意数量的前和后通过,它们的类型可以是Advistor(切入点),MethodInterceptor或被当前Spring配置支持的通知类型。例如:ThrowAdvice,AfterReturningAdvice或BeforeAdvice。这些通知必须支持实例共享模式。如果你需要高级 AOP特性 *** 作事务,通过orgspringframeworkaopframeworkProxyFactoryBean,而不是 TransactionProxyFactory实用代理创建者。

2)另一种声明方式:BeanNameAutoProxyCreator

使用TransactionProxyFactoryBean当事务代理包装对象,你可以完全控制代理。如果需要用一致方式包装大量bean。使用一个 BeanFactoryPostProcessor的一个实现,BeanNameAutoProxyCreator,可以提供另外一种方法。(Spring中,一旦ApplicationContext读完它的初始化信息,它将初始化所有实现BeanPostProcessor接口的 bean,并且让它们后处理ApplicationContext中所有其他的bean。所以使用这种机制,正确配置的 BeanNameAutoProxyCreator可以用来后处理所有ApplicationContext中所有其他的bean),并且把它们用事务代理包装起来。真正生成的事务代理和使用TransactionProxyFactoryBean生成的基本一致。

最后,总结一下Spring的优点:

◆为不同的事务API提供一致的编程模型,如JTA、JDBC、Hibernate、iBATIS数据库层JDO

◆提供比大多数事务API更简单的、易于使用的编程式事务管理API

◆整合Spring数据访问抽象

◆支持Spring声明式事务管理

第一种方式:每个Bean都有一个代理

<bean id="sessionFactory"

class="orgspringframeworkormhibernate3LocalSessionFactoryBean">

<property name="configLocation" value="classpath:hibernatecfgxml" />

<property name="configurationClass" value="orghibernatecfgAnnotationConfiguration" />

</bean>

<!-- 定义事务管理器(声明式的事务) -->

<bean id="transactionManager"

class="orgspringframeworkormhibernate3HibernateTransactionManager">

<property name="sessionFactory" ref="sessionFactory" />

</bean>

<!-- 配置DAO -->

<bean id="userDaoTarget" class="comblueskyspringdaoUserDaoImpl">

<property name="sessionFactory" ref="sessionFactory" />

</bean>

<bean id="userDao"

class="orgspringframeworktransactioninterceptorTransactionProxyFactoryBean">

<!-- 配置事务管理器 -->

<property name="transactionManager" ref="transactionManager" />

<property name="target" ref="userDaoTarget" />

<property name="proxyInterfaces" value="comblueskyspringdaoGeneratorDao" />

<!-- 配置事务属性 -->

<property name="transactionAttributes">

<props>

<prop key="">PROPAGATION_REQUIRED</prop>

</props>

</property>

</bean>

</beans>

第二种方式:所有Bean共享一个代理基类

<bean id="sessionFactory"

class="orgspringframeworkormhibernate3LocalSessionFactoryBean">

<property name="configLocation" value="classpath:hibernatecfgxml" />

<property name="configurationClass" value="orghibernatecfgAnnotationConfiguration" />

</bean>

<!-- 定义事务管理器(声明式的事务) -->

<bean id="transactionManager"

class="orgspringframeworkormhibernate3HibernateTransactionManager">

<property name="sessionFactory" ref="sessionFactory" />

</bean>

<bean id="transactionBase"

class="orgspringframeworktransactioninterceptorTransactionProxyFactoryBean"

lazy-init="true" abstract="true">

<!-- 配置事务管理器 -->

<property name="transactionManager" ref="transactionManager" />

<!-- 配置事务属性 -->

<property name="transactionAttributes">

<props>

<prop key="">PROPAGATION_REQUIRED</prop>

</props>

</property>

</bean>

<!-- 配置DAO -->

<bean id="userDaoTarget" class="comblueskyspringdaoUserDaoImpl">

<property name="sessionFactory" ref="sessionFactory" />

</bean>

<bean id="userDao" parent="transactionBase" >

<property name="target" ref="userDaoTarget" />

</bean>

</beans>

第三种方式:使用拦截器

<bean id="sessionFactory"

class="orgspringframeworkormhibernate3LocalSessionFactoryBean">

<property name="configLocation" value="classpath:hibernatecfgxml" />

<property name="configurationClass" value="orghibernatecfgAnnotationConfiguration" />

</bean>

<!-- 定义事务管理器(声明式的事务) -->

<bean id="transactionManager"

class="orgspringframeworkormhibernate3HibernateTransactionManager">

<property name="sessionFactory" ref="sessionFactory" />

</bean>

<bean id="transactionInterceptor"

class="orgspringframeworktransactioninterceptorTransactionInterceptor">

<property name="transactionManager" ref="transactionManager" />

<!-- 配置事务属性 -->

<property name="transactionAttributes">

<props>

<prop key="">PROPAGATION_REQUIRED</prop>

</props>

</property>

</bean>

<bean class="orgspringframeworkaopframeworkautoproxyBeanNameAutoProxyCreator">

<property name="beanNames">

<list>

<value>Dao</value>

</list>

</property>

<property name="interceptorNames">

<list>

<value>transactionInterceptor</value>

</list>

</property>

</bean>

<!-- 配置DAO -->

<bean id="userDao" class="comblueskyspringdaoUserDaoImpl">

<property name="sessionFactory" ref="sessionFactory" />

</bean>

</beans>

第四种方式:使用tx标签配置的拦截器

<bean id="sessionFactory"

class="orgspringframeworkormhibernate3LocalSessionFactoryBean">

<property name="configLocation" value="classpath:hibernatecfgxml" />

<property name="configurationClass" value="orghibernatecfgAnnotationConfiguration" />

</bean>

<!-- 定义事务管理器(声明式的事务) -->

<bean id="transactionManager"

class="orgspringframeworkormhibernate3HibernateTransactionManager">

<property name="sessionFactory" ref="sessionFactory" />

</bean>

<tx:advice id="txAdvice" transaction-manager="transactionManager">

<tx:attributes>

<tx:method name="" propagation="REQUIRED" />

</tx:attributes>

</tx:advice>

<aop:config>

<aop:pointcut id="interceptorPointCuts"

expression="execution( comblueskyspringdao())" />

<aop:advisor advice-ref="txAdvice"

pointcut-ref="interceptorPointCuts" />

</aop:config>

</beans>

第五种方式:全注

<tx:annotation-driven transaction-manager="transactionManager"/>

<bean id="sessionFactory"

class="orgspringframeworkormhibernate3LocalSessionFactoryBean">

<property name="configLocation" value="classpath:hibernatecfgxml" />

<property name="configurationClass" value="orghibernatecfgAnnotationConfiguration" />

</bean>

<!-- 定义事务管理器(声明式的事务) -->

<bean id="transactionManager"

class="orgspringframeworkormhibernate3HibernateTransactionManager">

<property name="sessionFactory" ref="sessionFactory" />

</bean>

</beans>

获取session 使用session的方法 开启事务 *** 作 提交事务

//开启事务

sessionbeginTransaction();

User user = new User();

usersetName("张三");

usersetPassword("123");

usersetCreateTime(new Date());

usersetExpireTime(new Date());

//保存数据

sessionsave(user);

//提交事务

sessiongetTransaction()commit();

可以给所有读的方法添加一个参数,来控制读从库还是主库。

2、数据源如何路由?

spring-jdbc 包中提供了一个抽象类:AbstractRoutingDataSource,实现了javaxsqlDataSource接口,我们用这个类来作为数据源类,重点是这个类可以用来做数据源的路由,可以在其内部配置多个真实的数据源,最终用哪个数据源,由开发者来决定。

AbstractRoutingDataSource中有个map,用来存储多个目标数据源

private Map<Object, DataSource> resolvedDataSources;

比如主从库可以这么存储

resolvedDataSourcesput("master",主库数据源);

resolvedDataSourcesput("salave",从库数据源);

AbstractRoutingDataSource中还有抽象方法determineCurrentLookupKey,将这个方法的返回值作为key到上面的resolvedDataSources中查找对应的数据源,作为当前 *** 作db的数据源

protected abstract Object determineCurrentLookupKey();

3、读写分离在哪控制?

读写分离属于一个通用的功能,可以通过spring的aop来实现,添加一个拦截器,拦截目标方法的之前,在目标方法执行之前,获取一下当前需要走哪个库,将这个标志存储在ThreadLocal中,将这个标志作为AbstractRoutingDataSourcedetermineCurrentLookupKey()方法的返回值,拦截器中在目标方法执行完毕之后,将这个标志从ThreadLocal中清除。

3、代码实现

31、工程结构图

32、DsType

表示数据源类型,有2个值,用来区分是主库还是从库。

package comjavacode2018readwritesplitbase;

public enum DsType {

MASTER, SLAVE;

}

33、DsTypeHolder

内部有个ThreadLocal,用来记录当前走主库还是从库,将这个标志放在dsTypeThreadLocal中

package comjavacode2018readwritesplitbase;

public class DsTypeHolder {

private static ThreadLocal<DsType> dsTypeThreadLocal = new ThreadLocal<>();

public static void master() {

dsTypeThreadLocalset(DsTypeMASTER);

}

public static void slave() {

dsTypeThreadLocalset(DsTypeSLAVE);

}

public static DsType getDsType() {

return dsTypeThreadLocalget();

}

public static void clearDsType() {

dsTypeThreadLocalremove();

}

}

34、IService接口

这个接口起到标志的作用,当某个类需要启用读写分离的时候,需要实现这个接口,实现这个接口的类都会被读写分离拦截器拦截。

package comjavacode2018readwritesplitbase;

//需要实现读写分离的service需要实现该接口

public interface IService {

}

35、ReadWriteDataSource

读写分离数据源,继承ReadWriteDataSource,注意其内部的determineCurrentLookupKey方法,从上面的ThreadLocal中获取当前需要走主库还是从库的标志。

package comjavacode2018readwritesplitbase;

import orgspringframeworkjdbcdatasourcelookupAbstractRoutingDataSource;

import orgspringframeworklangNullable;

public class ReadWriteDataSource extends AbstractRoutingDataSource {

@Nullable

@Override

protected Object determineCurrentLookupKey() {

return DsTypeHoldergetDsType();

}

}

36、ReadWriteInterceptor

读写分离拦截器,需放在事务拦截器前面执行,通过@1代码我们将此拦截器的顺序设置为IntegerMAX_VALUE - 2,稍后我们将事务拦截器的顺序设置为IntegerMAX_VALUE - 1,事务拦截器的执行顺序是从小到达的,所以,ReadWriteInterceptor会在事务拦截器orgspringframeworktransactioninterceptorTransactionInterceptor之前执行。

由于业务方法中存在相互调用的情况,比如service1m1中调用service2m2,而service2m2中调用了service2m3,我们只需要在m1方法执行之前,获取具体要用哪个数据源就可以了,所以下面代码中会在第一次进入这个拦截器的时候,记录一下走主库还是从库。

下面方法中会获取当前目标方法的最后一个参数,最后一个参数可以是DsType类型的,开发者可以通过这个参数来控制具体走主库还是从库。

package comjavacode2018readwritesplitbase;

import orgaspectjlangProceedingJoinPoint;

import orgaspectjlangannotationAround;

import orgaspectjlangannotationAspect;

import orgaspectjlangannotationPointcut;

import orgspringframeworkcoreannotationOrder;

import orgspringframeworkstereotypeComponent;

import javautilObjects;

@Aspect

@Order(IntegerMAX_VALUE - 2) //@1

@Component

public class ReadWriteInterceptor {

@Pointcut("target(IService)")

public void pointcut() {

}

//获取当前目标方法的最后一个参数

private Object getLastArgs(final ProceedingJoinPoint pjp) {

Object[] args = pjpgetArgs();

if (ObjectsnonNull(args) && argslength > 0) {

return args[argslength - 1];

} else {

return null;

}

}

@Around("pointcut()")

public Object around(final ProceedingJoinPoint pjp) throws Throwable {

//判断是否是第一次进来,用于处理事务嵌套

boolean isFirst = false;

try {

if (DsTypeHoldergetDsType() == null) {

isFirst = true;

}

if (isFirst) {

Object lastArgs = getLastArgs(pjp);

if (DsTypeSLAVEequals(lastArgs)) {

DsTypeHolderslave();

} else {

DsTypeHoldermaster();

}

}

return pjpproceed();

} finally {

//退出的时候,清理

if (isFirst) {

DsTypeHolderclearDsType();

}

}

}

}

37、ReadWriteConfiguration

spring配置类,作用

1、@3:用来将comjavacode2018readwritesplitbase包中的一些类注册到spring容器中,比如上面的拦截器ReadWriteInterceptor

2、@1:开启spring aop的功能

3、@2:开启spring自动管理事务的功能,@EnableTransactionManagement的order用来指定事务拦截器orgspringframeworktransactioninterceptorTransactionInterceptor顺序,在这里我们将order设置为IntegerMAX_VALUE - 1,而上面ReadWriteInterceptor的order是IntegerMAX_VALUE - 2,所以ReadWriteInterceptor会在事务拦截器之前执行。

package comjavacode2018readwritesplitbase;

import orgspringframeworkcontextannotationComponentScan;

import orgspringframeworkcontextannotationConfiguration;

import orgspringframeworkcontextannotationEnableAspectJAutoProxy;

import orgspringframeworktransactionannotationEnableTransactionManagement;

@Configuration

@EnableAspectJAutoProxy //@1

@EnableTransactionManagement(proxyTargetClass = true, order = IntegerMAX_VALUE - 1) //@2

@ComponentScan(basePackageClasses = IServiceclass) //@3

public class ReadWriteConfiguration {

}

38、@EnableReadWrite

这个注解用来开启读写分离的功能,@1通过@Import将ReadWriteConfiguration导入到spring容器了,这样就会自动启用读写分离的功能。业务中需要使用读写分离,只需要在spring配置类中加上@EnableReadWrite注解就可以了。

package comjavacode2018readwritesplitbase;

import orgspringframeworkcontextannotationImport;

import javalangannotation;

@Target(ElementTypeTYPE)

@Retention(RetentionPolicyRUNTIME)

@Documented

@Import(ReadWriteConfigurationclass) //@1

public @interface EnableReadWrite {

}

4、案例

读写分离的关键代码写完了,下面我们来上案例验证一下效果。

41、执行sql脚本

下面准备2个数据库:javacode2018_master(主库)、javacode2018_slave(从库)

2个库中都创建一个t_user表,分别插入了一条数据,稍后用这个数据来验证走的是主库还是从库。

DROP DATABASE IF EXISTS javacode2018_master;

CREATE DATABASE IF NOT EXISTS javacode2018_master;

USE javacode2018_master;

DROP TABLE IF EXISTS t_user;

CREATE TABLE t_user (

id INT PRIMARY KEY AUTO_INCREMENT,

name VARCHAR(256) NOT NULL DEFAULT ''

COMMENT '姓名'

);

INSERT INTO t_user (name) VALUE ('master库');

DROP DATABASE IF EXISTS javacode2018_slave;

CREATE DATABASE IF NOT EXISTS javacode2018_slave;

USE javacode2018_slave;

DROP TABLE IF EXISTS t_user;

CREATE TABLE t_user (

id INT PRIMARY KEY AUTO_INCREMENT,

name VARCHAR(256) NOT NULL DEFAULT ''

COMMENT '姓名'

);

INSERT INTO t_user (name) VALUE ('slave库');

42、spring配置类

@1:启用读写分离

masterDs()方法:定义主库数据源

slaveDs()方法:定义从库数据源

dataSource():定义读写分离路由数据源

后面还有2个方法用来定义JdbcTemplate和事务管理器,方法中都通过@Qualifier(“dataSource”)限定了注入的bean名称为dataSource:即注入了上面dataSource()返回的读写分离路由数据源。

package comjavacode2018readwritesplitdemo1;

import comjavacode2018readwritesplitbaseDsType;

import comjavacode2018readwritesplitbaseEnableReadWrite;

import comjavacode2018readwritesplitbaseReadWriteDataSource;

import orgspringframeworkbeansfactoryannotationQualifier;

import orgspringframeworkcontextannotationBean;

import orgspringframeworkcontextannotationComponentScan;

import orgspringframeworkcontextannotationConfiguration;

import orgspringframeworkjdbccoreJdbcTemplate;

import orgspringframeworkjdbcdatasourceDataSourceTransactionManager;

import orgspringframeworktransactionPlatformTransactionManager;

import javaxsqlDataSource;

import javautilHashMap;

import javautilMap;

@EnableReadWrite //@1

@Configuration

@ComponentScan

public class MainConfig {

//主库数据源

@Bean

public DataSource masterDs() {

orgapachetomcatjdbcpoolDataSource dataSource = new orgapachetomcatjdbcpoolDataSource();

dataSourcesetDriverClassName("commysqljdbcDriver");

dataSourcesetUrl("jdbc:mysql://localhost:3306/javacode2018_mastercharacterEncoding=UTF-8");

dataSourcesetUsername("root");

dataSourcesetPassword("root123");

dataSourcesetInitialSize(5);

return dataSource;

}

//从库数据源

@Bean

public DataSource slaveDs() {

orgapachetomcatjdbcpoolDataSource dataSource = new orgapachetomcatjdbcpoolDataSource();

dataSourcesetDriverClassName("commysqljdbcDriver");

dataSourcesetUrl("jdbc:mysql://localhost:3306/javacode2018_slavecharacterEncoding=UTF-8");

dataSourcesetUsername("root");

dataSourcesetPassword("root123");

dataSourcesetInitialSize(5);

return dataSource;

}

//读写分离路由数据源

@Bean

public ReadWriteDataSource dataSource() {

ReadWriteDataSource dataSource = new ReadWriteDataSource();

//设置主库为默认的库,当路由的时候没有在datasource那个map中找到对应的数据源的时候,会使用这个默认的数据源

dataSourcesetDefaultTargetDataSource(thismasterDs());

//设置多个目标库

Map<Object, Object> targetDataSources = new HashMap<>();

targetDataSourcesput(DsTypeMASTER, thismasterDs());

targetDataSourcesput(DsTypeSLAVE, thisslaveDs());

dataSourcesetTargetDataSources(targetDataSources);

return dataSource;

}

//JdbcTemplate,dataSource为上面定义的注入读写分离的数据源

@Bean

public JdbcTemplate jdbcTemplate(@Qualifier("dataSource") DataSource dataSource) {

return new JdbcTemplate(dataSource);

}

//定义事务管理器,dataSource为上面定义的注入读写分离的数据源

@Bean

public PlatformTransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource) {

return new DataSourceTransactionManager(dataSource);

}

}

43、UserService

这个类就相当于我们平时写的service,我是为了方法,直接在里面使用了JdbcTemplate来 *** 作数据库,真实的项目 *** 作db会放在dao里面。

getUserNameById方法:通过id查询name。

insert方法:插入数据,这个内部的所有 *** 作都会走主库,为了验证是不是查询也会走主库,插入数据之后,我们会调用thisuserServicegetUserNameById(id, DsTypeSLAVE)方法去执行查询 *** 作,第二个参数故意使用SLAVE,如果查询有结果,说明走的是主库,否则走的是从库,这里为什么需要通过thisuserService来调用getUserNameById?

thisuserService最终是个代理对象,通过代理对象访问其内部的方法,才会被读写分离的拦截器拦截。

package comjavacode2018readwritesplitdemo1;

import comjavacode2018readwritesplitbaseDsType;

import comjavacode2018readwritesplitbaseIService;

import orgspringframeworkbeansfactoryannotationAutowired;

import orgspringframeworkjdbccoreJdbcTemplate;

import orgspringframeworkstereotypeComponent;

import orgspringframeworktransactionannotationPropagation;

import orgspringframeworktransactionannotationTransactional;

import javautilList;

@Component

public class UserService implements IService {

@Autowired

private JdbcTemplate jdbcTemplate;

@Autowired

private UserService userService;

@Transactional(propagation = PropagationSUPPORTS, readOnly = true)

public String getUserNameById(long id, DsType dsType) {

String sql = "select name from t_user where id=";

List<String> list = thisjdbcTemplatequeryForList(sql, Stringclass, id);

return (list != null && listsize() > 0) listget(0) : null;

}

//这个insert方法会走主库,内部的所有 *** 作都会走主库

@Transactional

public void insert(long id, String name) {

Systemoutprintln(Stringformat("插入数据{id:%s, name:%s}", id, name));

thisjdbcTemplateupdate("insert into t_user (id,name) values (,)", id, name);

String userName = thisuserServicegetUserNameById(id, DsTypeSLAVE);

Systemoutprintln("查询结果:" + userName);

}

}

44、测试用例

package comjavacode2018readwritesplitdemo1;

import comjavacode2018readwritesplitbaseDsType;

import orgjunitBefore;

import orgjunitTest;

import orgspringframeworkcontextannotationAnnotationConfigApplicationContext;

public class Demo1Test {

UserService userService;

@Before

public void before() {

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

contextregister(MainConfigclass);

contextrefresh();

thisuserService = contextgetBean(UserServiceclass);

}

@Test

public void test1() {

Systemoutprintln(thisuserServicegetUserNameById(1, DsTypeMASTER));

Systemoutprintln(thisuserServicegetUserNameById(1, DsTypeSLAVE));

}

@Test

public void test2() {

long id = SystemcurrentTimeMillis();

Systemoutprintln(id);

thisuserServiceinsert(id, "张三");

}

}

test1方法执行2次查询,分别查询主库和从库,输出:

master库

slave库

是不是很爽,由开发者自己控制具体走主库还是从库。

test2执行结果如下,可以看出查询到了刚刚插入的数据,说明insert中所有 *** 作都走的是主库。

1604905117467

插入数据{id:1604905117467, name:张三}

查询结果:张三

5、案例源码

以上就是关于spring的事务使用有几种方式注解式事务如何配置和使用全部的内容,包括:spring的事务使用有几种方式注解式事务如何配置和使用、Spring针对事务处理提供哪两种事务编程模式。、spring 事务实现方式有哪些等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/9468204.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-28
下一篇 2023-04-28

发表评论

登录后才能评论

评论列表(0条)

保存