一、事务简介
事务(Transaction)是并发控制的单位,是用户定义的一个操作序列。这些操作要么都做,要么都不做,是一个不可分割的工作单位。通过事务,SQL Server能将逻辑相关的一组操作绑定在一起,以便服务器保持数据的完整性。
事务通常是以BEGIN TRANSACTION开始,以COMMIT或ROLLBACK结束。
COMMIT表示提交,即提交事务的所有操作。具体地说就是将事务中所有对数据库的更新写回到磁盘上的物理数据库中去,事务正常结束。
ROLLBACK表示回滚,即在事务运行的过程中发生了某种故障,事务不能继续进行,系统将事务中对数据库的所有以完成的操作全部撤消,滚回到事务开始的状态。
事务的特性(ACID特性)
原子性(Atomicity)
事务是数据库的逻辑工作单位,事务中包括的诸操作要么全做,要么全不做。
一致性(Consistency)
事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性(Isolation)
一个事务的执行不能被其他事务干扰。
持续性/永久性(Durability)
一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。
二、事务理解
实例
先建立数据库和表
里面的数据
假如张三向李四转账,就需要事务的一致性
不然张三转100元出去,可是,李四收不到,就成了
张三:400元,李四:500
所以事务一致性就是,张三转出钱,李四收到钱,数据库才会保存数据,不然就全部不执行,张三李四都还是500
三、写个银行转账的实例
jdbc.properties数据库名称
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db_bank
jdbc.username=root
jdbc.password=123456
Dao接口:BankDao.java
package com.java.dao;
public interface BankDao {
//转入
public void inMoney(int money,int userId);
//转出
public void outMoney(int money,int userId);
}
DaoImpl接口实现类:BankDaoImpl.java
package com.java.dao.impl;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import com.java.dao.BankDao;
public class BankDaoImpl implements BankDao {
//带命名参数的jdbc模板
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public void setNamedParameterJdbcTemplate(NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
}
@Override
public void inMoney(int money, int userId) {
// TODO Auto-generated method stub
String sql="update t_count set count=count+:money where userId=:userId";
MapSqlParameterSource sps=new MapSqlParameterSource();
sps.addValue("money", money);
sps.addValue("userId", userId);
namedParameterJdbcTemplate.update(sql,sps);
}
@Override
public void outMoney(int money, int userId) {
// TODO Auto-generated method stub
String sql="update t_count set count=count+:money where userId=:userId";
MapSqlParameterSource sps=new MapSqlParameterSource();
sps.addValue("money", money);
sps.addValue("userId", userId);
namedParameterJdbcTemplate.update(sql,sps);
}
}
Service接口:BankService.java
package com.java.service;
public interface BankService {
//这里是事务层,控制事务
/**
* A向B转账count元
* @param count
* @param userIdA
* @param userIdB
*/
public void transferAccounts(int count,int userIdA,int userIdB);
}
ServiceImpl接口实现类BankServiceImpl.java
package com.java.service.impl;
import com.java.dao.BankDao;
import com.java.service.BankService;
public class BankServiceImpl implements BankService{
//引入DAO
private BankDao bankDao;
public void setBankDao(BankDao bankDao) {
this.bankDao = bankDao;
}
@Override
public void transferAccounts(int count, int userIdA, int userIdB) {
//A出钱,B得钱
bankDao.outMoney(count, userIdA);
bankDao.inMoney(count, userIdB);
}
}
beans.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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 数据源,class的是dbcp连接池,所以要导入连接池JAR包 -->
<!--destroy-method是连接用完了,送回到连接池 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<context:property-placeholder location="jdbc.properties"/>
<!--带参数模板类-->
<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg ref="dataSource"></constructor-arg>
</bean>
<bean id="bankDao" class="com.java.dao.impl.BankDaoImpl">
<property name="namedParameterJdbcTemplate" ref="namedParameterJdbcTemplate"></property>
</bean>
<bean id="bankService" class="com.java.service.impl.BankServiceImpl">
<property name="bankDao" ref="bankDao"></property>
</bean>
</beans>
测试
T.java
package com.java.Test;
import java.util.List;
import org.junit.Test;
import org.junit.Before;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.java.service.BankService;
public class T {
private ApplicationContext ac;
@Before
public void setUp() throws Exception {
ac=new ClassPathXmlApplicationContext("beans.xml");
}
@Test
public void transferAccounts() {
BankService bankService=(BankService)ac.getBean("bankService");
//1出钱,2得钱
bankService.transferAccounts(50, 1, 2);
}
}
原本数据库
转账后
可是没有事务
当“得钱”的方法是一个没有的数据表
这样的话,一个用户转账扣钱了,但另一个却没有得到钱
报错了
再看看数据库,张三得了钱,李四却没有得钱,所以就需要事务
四、编程式事务管理(用的少)
首先在ServiceImpl接口实现类(事务层)引入事务模板
BankServiceImpl.java
package com.java.service.impl;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import com.java.dao.BankDao;
import com.java.service.BankService;
public class BankServiceImpl implements BankService{
//引入DAO
private BankDao bankDao;
//引入事务模板
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
public void setBankDao(BankDao bankDao) {
this.bankDao = bankDao;
}
//一个人转出多少钱,一个人多了多少钱
public void transferAccounts(final int count,final int userIdA,final int userIdB) {
//事务模板用法
//这样“给钱”和“出钱”就在同一个事物当中
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus arg0) {
bankDao.outMoney(count, userIdA);
bankDao.inMoney(count, userIdB);
}
});
}
}
在bean.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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 数据源,class的是dbcp连接池,所以要导入连接池JAR包 -->
<!--destroy-method是连接用完了,送回到连接池 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- jdbc事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 把事务管理器放到 事务模板(transactionTemplate)-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<constructor-arg ref="transactionManager"></constructor-arg>
</bean>
<context:property-placeholder location="jdbc.properties"/>
<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg ref="dataSource"></constructor-arg>
</bean>
<bean id="bankDao" class="com.java.dao.impl.BankDaoImpl">
<property name="namedParameterJdbcTemplate" ref="namedParameterJdbcTemplate"></property>
</bean>
<!-- 最后把事务模板注入到BankServiceImpl -->
<bean id="bankService" class="com.java.service.impl.BankServiceImpl">
<property name="bankDao" ref="bankDao"></property>
<property name="transactionTemplate" ref="transactionTemplate"></property>
</bean>
</beans>
这样事务就绑定了,就算系统中间步骤出现问题,也不会出现一个人出钱,另一个人却不得钱的情况
但是这样,事务代码侵入到业务逻辑代码,代码多了不好维护,所以我们要使用声明式事务管理
四、声明式事务管理
1.使用xml配置声明式事务(优先使用)
先写Bean.xml
引入头文件
配置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:context="http://www.springframework.org/schema/context"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- jdbc事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务通知 transaction-manager="事务管理器"-->
<!-- 对所有的方法事务管理 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- 配置事务切面 -->
<aop:config>
<!-- 配置切点,第一个*返回值,(..)代表方法可以有任意参数 -->
<aop:pointcut id="serviceMethod" expression="execution(* com.java.service.*.*(..))" />
<!-- 配置事务通知 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod"/>
</aop:config>
<context:property-placeholder location="jdbc.properties"/>
<!-- 带命名参数的jdbc模板 -->
<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg ref="dataSource"></constructor-arg>
</bean>
<bean id="bankDao" class="com.java.dao.impl.BankDaoImpl">
<property name="namedParameterJdbcTemplate" ref="namedParameterJdbcTemplate"></property>
</bean>
<bean id="bankService" class="com.java.service.impl.BankServiceImpl">
<property name="bankDao" ref="bankDao"></property>
</bean>
</beans>
BankServiceImpl.java
package com.java.service.impl;
import com.java.dao.BankDao;
import com.java.service.BankService;
public class BankServiceImpl implements BankService{
//引入DAO
private BankDao bankDao;
public void setBankDao(BankDao bankDao) {
this.bankDao = bankDao;
}
//一个人转出多少钱,一个人多了多少钱
public void transferAccounts(int count, int userIdA, int userIdB) {
//这样“给钱”和“出钱”就在同一个事物当中
bankDao.outMoney(count, userIdA);
bankDao.inMoney(count, userIdB);
}
}
2.使用注解配置声明式事务
BankServiceImpl.java
@Transactional注解
package com.java1234.service.impl;
import org.springframework.transaction.annotation.Transactional;
import com.java.dao.BankDao;
import com.java.service.BankService;
@Transactional
public class BankServiceImpl implements BankService{
private BankDao bankDao;
public void setBankDao(BankDao bankDao) {
this.bankDao = bankDao;
}
@Override
public void transferAccounts(int count, int userIdA, int userIdB) {
// TODO Auto-generated method stub
// TODO Auto-generated method stub
bankDao.outMoney(count, userIdA);
bankDao.inMoney(count, userIdB);
}
}
bean.xml
配置
<tx:annotation-driven transaction-manager="transactionManager"/>
<?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:context="http://www.springframework.org/schema/context"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- jdbc事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--置入事务管理器-->
<tx:annotation-driven transaction-manager="transactionManager"/>
<context:property-placeholder location="jdbc.properties"/>
<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg ref="dataSource"></constructor-arg>
</bean>
<bean id="bankDao" class="com.java1234.dao.impl.BankDaoImpl">
<property name="namedParameterJdbcTemplate" ref="namedParameterJdbcTemplate"></property>
</bean>
<bean id="bankService" class="com.java1234.service.impl.BankServiceImpl">
<property name="bankDao" ref="bankDao"></property>
</bean>
</beans>
完成注解配置声明式事务
优先使用使用xml配置声明式事务,不然大项目,每个service都要写注解
五、事务传播行为
配置事务通知