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 事务实现方式有哪些等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)