Spring事务管理3

时间:2021-02-21 20:29:15

如何正确配置Spring事务管理已经清楚了,接下来就是如何使用了。此时有个疑问,如何将资源与Spring的事务管理同步起来,如果我们使用DataSource作为持久层,在没有Spring事务管理情况下,我们这样写代码

Connection connection = null;
try {
<span style="white-space:pre"></span>String sql = "insert into person_d (name) values ('hl') ";
connection = dataSource.getConnection();
connection.setAutoCommit(false);
PreparedStatement ps = connection.prepareStatement(sql);
ps.execute();
connection.commit();
} catch (SQLException e) {
try {
if (connection != null) {
connection.rollback();
connection.close();
}
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}

现在事务通过Spring控制了,我们就要用Spring提供的类来进行数据操做,以达到和Spring事务管理同步起来。

推荐的方式是使用Spring提供的模版,例如JdbcTemplate。还有一中方式是使用各种工具类,DataSourceUtils (for JDBC), EntityManagerFactoryUtils (for JPA),
SessionFactoryUtils (for Hibernate), PersistenceManagerFactoryUtils (for JDO)等,但是这种方式比较偏底层,脱离了Spring事务管理,完全由自己来控制事务也可以很好的工作,所以不推荐。还有另外一种方式是使用TransactionAwareDataSourceProxy类,它是DataSource的代理类,封装了DataSource并加入了事务管理。这个类最底层,几乎不可能用到,强烈建议不要使用此类。

使用模版例子

@Transactional()
public void insertFoo(Foo foo) {
String sql = "insert into person_d (name) values ('hl') ";
jdbcTemplate.execute(sql);//容器注入
}

使用工具类例子

@Transactional()
public void insertFoo(Foo foo) {
Connection connection = null;
try {
String sql = "insert into person_d (name) values ('hl') ";
connection = DataSourceUtils.getConnection(dataSource);//dataSource通过容器注入
connection.setAutoCommit(false);//可以不使用Spring事务管理,这也是不推荐使用这种方式的原因
PreparedStatement ps = connection.prepareStatement(sql);
ps.execute();
connection.commit();
} catch (SQLException e) {
try {
if (connection != null) {
connection.rollback();
connection.close();
}
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
}

下面我们看上面例子的配置。众所周知Spring事务管理有两种使用方式,一个是声明式一个是编程式。先来看看最被推荐的声明式。

定义一个服务接口

public interface FooService {
Foo getFoo(String fooName);
Foo getFoo(String fooName, String barName);
void insertFoo(Foo foo);
void updateFoo(Foo foo);
}

实现,

public class DefaultFooService implements FooService {
public Foo getFoo(String fooName) {
throw new UnsupportedOperationException();
}
public Foo getFoo(String fooName, String barName) {
throw new UnsupportedOperationException();
}
public void insertFoo(Foo foo) {
throw new UnsupportedOperationException();
}
public void updateFoo(Foo foo) {
throw new UnsupportedOperationException();
}
}

配置事务切入点

<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.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">
<!-- 要被加入事务的对象 -->
<bean id="fooService" class="org.chl.spring.transaction.service.impl.DefaultFooService" >
<property name="dataSource" ref="dataSource"></property>
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务的行为 相当于aop中的advice -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- the transactional semantics... -->
<tx:attributes>
<!-- 所有以get开头的方法只读 -->
<tx:method name="get*" read-only="true"/>
<!-- 其他的所有方法使用默认的 读写 RuntimeException异常回滚-->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- 定义切入点 -->
<aop:config>
<aop:pointcut id="fooServiceOperation" expression="execution( x.y.service.FooService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
</aop:config>
<!-- 数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
<property name="username" value="scott"/>
<property name="password" value="tiger"/>
</bean>
<!-- 事务管理 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
上面的例子,我们想要给fooService增加事务能力,下面写个测试类,测试一下

public final class Boot {
public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml");
FooService fooService = (FooService) ctx.getBean("fooService");
fooService.insertFoo (new Foo());
}
}
配置好日志系统,会得到如下日志

<!-- Spring 容器启动... -->
[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean fooService
with 0 common interceptors and 1 specific interceptors
<!-- DefaultFooService 被代理 -->
[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService]
<!-- ... insertFoo(..) 在代理上被调用 -->
[TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo
<!-- 事务... -->
[DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo]
[DataSourceTransactionManager] - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@a53de4]
for JDBC transaction
<!-- the insertFoo(..) 抛出异常... -->
[RuleBasedTransactionAttribute] - Applying rules to determine whether transaction should rollback on
java.lang.UnsupportedOperationException
[TransactionInterceptor] - Invoking rollback for transaction on x.y.service.FooService.insertFoo due to
throwable [java.lang.UnsupportedOperationException]
<!-- 事务回滚 -->
[DataSourceTransactionManager] - Rolling back JDBC transaction on Connection
[org.apache.commons.dbcp.PoolableConnection@a53de4]
[DataSourceTransactionManager] - Releasing JDBC Connection after transaction
[DataSourceUtils] - Returning JDBC Connection to DataSource
Exception in thread "main" java.lang.UnsupportedOperationException at
x.y.service.DefaultFooService.insertFoo(DefaultFooService.java:14)
<!-- AOP infrastructure stack trace elements removed for clarity -->
at $Proxy0.insertFoo(Unknown Source)
at Boot.main(Boot.java:11)
如果觉得xml配置事务麻烦的话,可以使用注解方式替代。

需要修改实现类,添加如下注解

@Transactional
public class DefaultFooService implements FooService {
Foo getFoo(String fooName);
Foo getFoo(String fooName, String barName);
void insertFoo(Foo foo);
void updateFoo(Foo foo);
}

xml配置文件也要修改

<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.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">
<!-- this is the service object that we want to make transactional -->
<bean id="fooService" class="org.chl.spring.transaction.service.impl.DefaultFooService" >
<property name="dataSource" ref="dataSource"></property>
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启事务注解支持-->
<tx:annotation-driven transaction-manager="txManager"/>
<!-- don't forget the DataSource -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost/test" />
<property name="username" value="root" />
<property name="password" value="root" />
</bean>
<!-- similarly, don't forget the PlatformTransactionManager -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- other <bean/> definitions here -->
</beans>


@Transactional可以写在一个接口上,接口的方法上(基于接口的动态代理,jdk代理),类上,类的public方法上。注意@Transactional注解只对public方法起作用。Spring不推荐将@Transactional写在接口里面。

在proxy模式中,在目标类中,如果一个没有事务的方法调用了该类中一个有事务的方法,那么有事务的方法也将不会起作用。因为只用通过代理调用的方法才能被拦截