编程式事务控制相关对象
编程式事务控制三大对象
PlatFormTransactionManager、TransactionDefinition、TransactionStatus
编程式事务控制含义
手动代码API的方式控制事务 例如jdbc的connection:
connection.setAutoCommit(false) 、 connection.commit 、 connection.callBack
PlatFormTransactionManager接口:平台事务管理器
PlatFormTransactionManager是接口,不同的Dao层(jdbc,mybatis,hibernate等),有不同的实现类。是spring的平台事务管理器。常用方法有:
Transaction getTransaction(TransactionDefination defination)获取事务的状态信息。
void commit(TransactionStatus status)提交事务
void rollback(TransactionStatus status)回滚事务
TransactionDefinition:事务的定义对象,内部维护事务的相关参数。
int getIsolationLevel()获取事务的隔离级别
int getPropogationBehavior()获取事务的传播行为
int getTimeout()获取超时时间
boolean isReadOnly()是否只读
事务隔离级别
设置隔离级别,可解决事务并发产生的问题,如脏读、不可重复读和虚读。
ISOLATION_DEFAULT
ISOLATION_READ_UNCOMMITTED
ISOLATION_READ_COMMITTED解决脏读
ISOLATION_REPEATABLE_READ解决不可重复读
ISOLATION_SERIALIZABLE全能解决,性能较低
事务传播行为
解决业务方法在调用业务方法时,他们之间事务统一性问题:如:业务层A方法调用业务层B方法时,可能出现事务的统一或重复问题。
1、REQUIRED:如果当前没有事务,就新建一个事务, 如果已经存在一个事务中, 加入到这个事务中。一般的选择(默认值)。举例如:A业务方法调用B业务方法,B业务方法看A业务方法有没有事务,如果A没有,B就新建一个,如果A有,B就加入到这个事务中。
2、SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)。举例如:A业务方法调用B业务方法,B业务方法看A业务方法有没有事务,如果A有事务,B就一起用,如果A没有事务,B也不创建事务了,以非事务方式运行。
3、MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常。举例如:A业务方法调用B业务方法,B业务方法看A业务方法有没有事务,如果A有事务,B就使用当前事务,如果A没有事务,B就抛异常。
4、REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
注:事务挂起的含义:Spring中事务挂起的含义是,需要新事务时,将现有的connection1保存起来(它还有尚未提交的事务),然后创建connection2,connection2提交、回滚、关闭完毕后,再把connection1取出来,完成提交、回滚、关闭等动作,保存connection1的动作称之为事务挂起。
5、NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
6、NEVER:以非事务方式运行,如果当前存在事务,抛出异常。
7、NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行REQUIRED类似的操作。
8、超时时间:默认值是-1,没有超时限制。可设置超时时间,如果有,以秒为单位进行设置。
9、是否只读:建议查询时设置为只读。查询可以只读,增删改就得写了。
TransactionStatus:事务状态信息对象
boolean hasSavepoint() 是否存储回滚点。
boolean isCompleted() 事务是否完成。
boolean isNewTransaction() 是否是新事物。
boolean isRollbackOnly() 事务是否回滚。
注:状态不需要配置。
PlatFormTransactionManager平台事务管理器+TransactionDefinition事务的定义对象=TransactionStatus事务状态信息对象。
基于XML的声明式事务控制
Spring的声明式事务顾名思义就是采用声明的方式来处理事务。这里所说的声明,就是指在配置文件中声明,用在Spring配置文件中声明式的处理事务来代替代码式的处理事务。
声明式事务处理的作用
事务管理不侵入开发的组件。具体来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如此,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话,也只需要在定义文件中重新配置即可。
也就是解耦的。事务管理是属于系统层面的,不是业务逻辑的一部分。这样通过配置,业务逻辑和事务管理松耦合了,互不影响,仅在运行期间功能整合一起,是AOP思想的体现。切点是业务逻辑,通知是事务管理。
在不需要事务管理的时候,只要在设定文件上修改一下, 即可移去事务管理服务,无需改变代码重新编译,这样维护起来极其方便。
创建mavenWeb项目,pom文件:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.8</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
</dependencies>
resource下创建jdbc.properties文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/jdbc
jdbc.username=root
jdbc.password=root123
resource下applicationContext.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: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:component-scan base-package="com.kdy"/><!--开启组件扫描-->
<context:property-placeholder location="classpath:jdbc.properties"/><!--引入配置文件,使用el获取-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/><!--引入数据源,需要它的connection对象事务控制,提交和回滚-->
</bean>
<!--通知 事务的增强-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--isolation隔离级别、propagation传播行为、timeout超时时间、readonly是否只读-->
<!--对目标类中的某些名称的方法进行事务处理的增强-->
<tx:method name="*"/>
<!--<tx:method name="*" isolation="DEFAULT" propagation="REQUIRED" timeout="-1" read-only="false"/>
<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
<tx:method name="save" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
<tx:method name="findAll" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/>
<tx:method name="update*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>-->
<!--以update开头的通配符的写法-->
</tx:attributes>
</tx:advice>
<!--配置事务的aop织入,也可抽取切点点表达式出来后引用-->
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.kdy.service.impl.*.*(..))"></aop:advisor>
</aop:config>
</beans>
配置中的tx:attributes:
它是设置事务的属性信息的,相当于编程式事务控制中的TransactionDefinition事务的定义对象,内部维护事务的相关参数。
methods代表对哪些方法使用事务控制,这里是对所有的方法进行事务控制,这里还能配置事务定义对象的属性,比如隔离级别,超时处理,只读等。
com.kdy.domain中Account
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
private String name;
private double money;
}
com.kdy.dao.AccountDaoImpl
@Repository
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void out(String outMan, double money) {
jdbcTemplate.update("update account set money = money - ? where name = ?",money,outMan);
}
@Override
public void in(String inMan, double money) {
jdbcTemplate.update("update account set money = money + ? where name = ?",money,inMan);
}
}
com.kdy.service中AccountServiceImpl
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public void transfer(String outMan, String inMan, double money) {
/*
* 传统解决方式,使用事务代码嵌入:
* 在service业务层使用try-catch将两个事务包裹起来,
* 在前面开启事务,当无异常就commit提交事务,当有异常就rollback回滚事务。
* 这种方式比较繁琐,所有涉及到事务的地方都需一个个的处理。
* */
//开启事务
accountDao.out(outMan,money);
int i = 1/0;//制造异常错误
accountDao.in(inMan,money);
//提交事务
}
}
test目录下com.kdy.test的AccountTest
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountTest {
@Autowired
private AccountService accountService;
@Test
public void test1(){
accountService.transfer("tom","jerry",500);
}
}
使用spirng-test就可用不用newClassPathXMLApplicaitonContext的方式创建app对象了。
运行测试用例即可。
基于注解的声明式事务控制
还是上面项目案例,删除applicationContext.xml中的事务增强<tx:advice>和织入<aop:config>
保留平台事务管理器的bean,并加上事务的注解驱动
修改后的applicationContext.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: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:component-scan base-package="com.kdy"/><!--开启组件扫描-->
<context:property-placeholder location="classpath:jdbc.properties"/><!--引入配置文件,使用el获取-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/><!--引入数据源,需要它的connection对象事务控制,提交和回滚-->
</bean>
<!--事务的注解驱动-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
在业务类com.kdy.serviceAccountServiceImpl中加上@Transaction注解
注解使用在类上,那么该类下的所有方法都使用同一套注解参数配置。
使用在方法上,不同的方法可以采用不同的事务参数配置。
@Service
@Transactional(isolation = Isolation.REPEATABLE_READ)//可在类上加上声明事务控制注解
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
//可在方法上加上声明事务控制注解
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
public void transfer(String outMan, String inMan, double money) {
/*
* 传统解决方式,使用事务代码嵌入:
* 在service业务层使用try-catch将两个事务包裹起来,
* 在前面开启事务,当无异常就commit提交事务,当有异常就rollback回滚事务。
* 这种方式比较繁琐,所有涉及到事务的地方都需一个个的处理。
* */
//开启事务
accountDao.out(outMan,money);
int i = 1/0;//制造异常错误
accountDao.in(inMan,money);
//提交事务
}
}