概念
事务的特性
原子性:
事务是一个不可分割的工作的单位,要么都成功,要么都失败
一致性:
事务前后数据的完整性必须是一致的
隔离性:
一个事务执行时,不应受其他事务的影响(可以设置隔离级别)
持久性:
事务提交时,数据真正写入数据库,事务未提交,不修改数据库
事务的隔离级别
事务的隔离级别也分为四种,由低到高依次分别为:read uncommited(读未提交)、read commited(读提交)、read repeatable(读重复)、serializable(序列化),这四个级别可以逐个解决脏读、不可重复读、幻读这几类问题。
- read uncommited:是最低的事务隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。
- read commited:保证一个事物提交后才能被另外一个事务读取。另外一个事务不能读取该事物未提交的数据。
- repeatable read:这种事务隔离级别可以防止脏读,不可重复读。但是可能会出现幻象读。它除了保证一个事务不能被另外一个事务读取未提交的数据之外还避免了以下情况产生(不可重复读)。
- serializable:这是花费最高代价但最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读之外,还避免了幻象读。
- 脏读、不可重复读、幻象读概念说明:
-
- 脏读:指当一个事务正字访问数据,并且对数据进行了修改,而这种数据还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据还没有提交那么另外一个事务读取到的这个数据我们称之为脏数据。依据脏数据所做的操作肯能是不正确的。
- 不可重复读:指在一个事务内,多次读同一数据。在这个事务还没有执行结束,另外一个事务也访问该同一数据,那么在第一个事务中的两次读取数据之间,由于第二个事务的修改第一个事务两次读到的数据可能是不一样的,这样就发生了在一个事物内两次连续读到的数据是不一样的,这种情况被称为是不可重复读。
- 幻象读:一个事务先后读取一个范围的记录,但两次读取的纪录数不同,我们称之为幻象读(两次执行同一条 select 语句会出现不同的结果,第二次读会增加一数据行,并没有说这两次执行是在同一个事务中)
spring事务传播特性
事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:
-
- propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
- propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
- propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
- propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
- propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
- propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作
spring实现事务管理
1.编程实现
1.实例化事务管理类与事务操作模板类
<!--spring事务管理类-->
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--srping事务模板类-->
<bean name="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"></property>
</bean>
2.代码中
public void transfer(String from, String to, double money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
accountDao.reduce(from,money);
int i = 1/0;
accountDao.add(to,money);
}
});
}
2.生成代理对象的方式
1.xml配置
<!--spring事务管理器-->
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="proxyFactoryBean" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!--事务管理器-->
<property name="transactionManager" ref="transactionManager"></property>
<!--需要代理的对象-->
<property name="target" ref="accountDaoImpl"></property>
<!--事务的属性配置-->
<property name="transactionAttributes">
<props>
<!--
key: 填写方法名
save*:以save开头的方法
*:其他的方法
-->
<!--
value: 可以填写如下的值,用“,”分割
* PROPAGATION :事务的传播行为
* ISOLATION :事务的隔离级别
* readOnly :只读
* -Exception : 发生该异常回滚
* +Exception : 发生该异常不回滚
-->
<prop key="add*">PROPAGATION_REQUIRED,+java.lang.ArithmeticException</prop>
</props>
</property>
</bean>
这里是对 dao 层生成的代理对象,也可以为 service 层的实现类生成代理对象
2.代码中
public class AccountServiceImpl implements AccountService {
@Resource(name="proxyFactoryBean")
private AccountDao accountDao;
@Override
public void transfer(String from, String to, double money) {
accountDao.reduce(from,money);
int i = 1/0;
accountDao.add(to,money);
}
}
这里注入dao层对象时要注意注入代理对象
3.xml配置aspectJ
需要导入的jar包:aspectjweaver-1.8.9.jar spring-aspects-4.2.8.RELEASE.jar
1.xml配置内容
<!--spring事务管理器-->
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<!--配置切面-->
<aop:config>
<aop:pointcut id="cut1" expression="execution(* com.test.demo3.*.*(..))"></aop:pointcut>
<aop:advisor advice-ref="txAdvice" pointcut-ref="cut1"></aop:advisor>
</aop:config>
2.代码无需任何改变
4.基于注解的aspectJ
1.xml配置
<!--spring事务管理器-->
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
2.代码
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
@Override
public void transfer(String from, String to, double money) {
accountDao.reduce(from,money);
int i = 1/0;
accountDao.add(to,money);
}
}
在类上或者方法上加 @Transactional 注解 ,里面可以设置事务的属性
--------------------------------------------------------
测试过程中遇到的一个小坑
<!--引入配置文件-->
<context:property-placeholder location="jdbc.properties"></context:property-placeholder>
<!--实例化出c3p0连接池-->
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${url}"></property>
<property name="driverClassName" value="${driver.className}"></property>
<property name="username" value="${username}"></property>
<property name="password" value="${password}"></property>
</bean>
在spring的xml文件中,这样配置的数据库连接源
结果一直报错
java.sql.SQLException: Access denied for user '94426'@'localhost' (using password: YES)
很纳闷为什么是 user '94426'@'localhost' 而不是 user 'root'@'localhost'
后来百度 说 在系统中也有个username属性,这时系统变量覆盖了Properties中的值,这时取得username的值为系统的用户名Administrator,密码为properties中的password去查询数据库,此时用户名名和密码并不匹配就会报错。在Spring完成注入时是用 "${..}" 方式获取值完成注入的。而通过这种表达式也能直接获取到JVM系统属性..........
所以把 username 换一个词就好了