事务的特性
- 原子性(Atomicity):事务时一个原子操作,是不可割分的最小操作单位,要么同时成功,要么同时失败。
- 一致性(Consistency):事务操作前后,数据总量不变。
- 隔离性(Isolation):多个事务之间,相互独立。
- 持久性(Durability):当事务提交或者回滚,数据库会持久化的保存数据。
事务的隔离级别
- 脏读:一个事务读取到另一个事务没有提交的数据。
- 不可重复读:在同一个事务中,两次读取到的数据不一样。(oracle的默认隔离级别)
- 幻读:当一个事务(T1)读取几行记录后,另一个并发事务(T2)插入一些记录时,幻读就发生了。在后来的查询中,第一个事务 (T1)就会发现一些原来没有的额外记录。(mysql的默认隔离级别)
在理想状态下,事务之间将完全隔离,从而可以防止这些问题发生。然而,完全隔离会影响性能,因为隔离会涉及到锁定在数据库中的记录(甚至时锁表)。完全隔离要求事务相互等待来完成工作,会阻塞。因此,可以根据业务场景选择不同的隔离级别。
隔离级别 | 含义 |
ISOLATION_DEFAULT | 使用数据库默认的隔离级别 |
ISOLATION_READ_UNCOMMITTED | 允许读取尚未提交的更改。可能导致脏读,不可重复读,幻读 |
ISOLATION_READ_COMMITTED | 允许从已经提交的并发事务读取,可防止脏读,但可能发生不可重复读或者幻读 |
ISOLATION_REPEATABLE_READ | 对相同字段的多次读取结果时一致的。可能发生幻读 |
ISOLATION_SERIALIZABLE | 完全服从ACID的隔离级别,可以解决所有问题,但效率会很低 |
在工作中使用过的三种声明式事务:
-
基于TransactionProxyFactoryBean
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kpwang</groupId>
<artifactId>spring_01tran_proxyFactoryBean</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.3</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
</project>
application.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">
<context:component-scan base-package="com.kpwang"/>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="oracle.jdbc.driver.OracleDriver"/>
<property name="jdbcUrl" value="jdbc:oracle:thin:@192.168.80.130:1521:orcl"/>
<property name="user" value="kpwang"/>
<property name="password" value="ww920321"/>
</bean>
<bean id="accountDao" class="com.kpwang.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--事务代理工厂-->
<!--生成事务代理对象-->
<bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="txManager"/>
<property name="target" ref="accountService"/>
<property name="transactionAttributes">
<props>
<prop key="transter*">PROPAGATION_REQUIRED,-Exception</prop><!--默认只有运行时异常才会回滚-->
</props>
</property>
</bean>
</beans>
dao
public interface IAccountDao {
void updateAccount(Account account);
List<Account> findAccountByName(Serializable accountName);
}
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
public void updateAccount(Account account) {
this.getJdbcTemplate().update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
public List<Account> findAccountByName(Serializable accountName) {
return this.getJdbcTemplate().query("select * from account where name=?",new AccountRowMapper(),accountName);
}
}
service
public interface IAccountService {
//转账
void transter(String sourceName,String targetName,Float money);
}
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
public void transter(String sourceName, String targetName, Float money) {
System.out.println("transfer......");
Account source = accountDao.findAccountByName(sourceName).get(0);
Account target = accountDao.findAccountByName(targetName).get(0);
if (source.getMoney()<money){
throw new RuntimeException("账号余额不足");
}else {
source.setMoney(source.getMoney()-money);
}
target.setMoney(target.getMoney()+money);
accountDao.updateAccount(source);
int i=1/0;
accountDao.updateAccount(target);
}
}
domain
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setMoney(Float money) {
this.money = money;
}
public Float getMoney() {
return money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money='" + money + '\'' +
'}';
}
}
public class AccountRowMapper implements RowMapper<Account>,Serializable {
public Account mapRow(ResultSet resultSet, int i) throws SQLException {
Account account=new Account();
account.setId(resultSet.getInt("id"));
account.setName(resultSet.getString("name"));
account.setMoney(resultSet.getFloat("money"));
return account;
}
}
junit
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:application.xml")
public class IAccountServiceTest {
@Autowired
@Qualifier("accountServiceProxy")//注入代理对象
private IAccountService accountService;
@Test
public void transter() {
accountService.transter("aaa","bbb",250f);
}
}
初始数据:
事务回滚
-
基于AspectjAOP
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kpwang</groupId>
<artifactId>spring_02tran_aspectjAOP</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.3</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
</project>
application.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/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">
<context:component-scan base-package="com.kpwang"/>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="oracle.jdbc.driver.OracleDriver"/>
<property name="jdbcUrl" value="jdbc:oracle:thin:@192.168.80.130:1521:orcl"/>
<property name="user" value="kpwang"/>
<property name="password" value="ww920321"/>
</bean>
<bean id="accountDao" class="com.kpwang.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务通知-->
<tx:advice id="txManager" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="tran*" propagation="REQUIRED" read-only="false" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="pt1" expression="execution(* com.kpwang.service.impl.*.*(..))"/>
<aop:advisor advice-ref="txManager" pointcut-ref="pt1"/>
</aop:config>
</beans>
dao
public interface IAccountDao {
void updateAccount(Account account);
List<Account> findAccountByName(Serializable accountName);
}
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
public void updateAccount(Account account) {
this.getJdbcTemplate().update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
public List<Account> findAccountByName(Serializable accountName) {
return this.getJdbcTemplate().query("select * from account where name=?",new AccountRowMapper(),accountName);
}
}
service
public interface IAccountService {
//转账
void transter(String sourceName, String targetName, Float money);
}
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
public void transter(String sourceName, String targetName, Float money) {
System.out.println("transfer......");
Account source = accountDao.findAccountByName(sourceName).get(0);
Account target = accountDao.findAccountByName(targetName).get(0);
if (source.getMoney()<money){
throw new RuntimeException("账号余额不足");
}else {
source.setMoney(source.getMoney()-money);
}
target.setMoney(target.getMoney()+money);
accountDao.updateAccount(source);
int i=1/0;
accountDao.updateAccount(target);
}
}
实体类
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setMoney(Float money) {
this.money = money;
}
public Float getMoney() {
return money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money='" + money + '\'' +
'}';
}
}
public class AccountRowMapper implements RowMapper<Account>,Serializable {
public Account mapRow(ResultSet resultSet, int i) throws SQLException {
Account account=new Account();
account.setId(resultSet.getInt("id"));
account.setName(resultSet.getString("name"));
account.setMoney(resultSet.getFloat("money"));
return account;
}
}
junit
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:application.xml")
public class IAccountServiceTest {
@Autowired
private IAccountService accountService;
@Test
public void transter() {
accountService.transter("aaa","bbb",250f);
}
}
初始数据:
事务回滚
-
基于@Transactional
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kpwang</groupId>
<artifactId>spring_03tran_transactional</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.3</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
</project>
application.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"
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">
<context:component-scan base-package="com.kpwang"/>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="oracle.jdbc.driver.OracleDriver"/>
<property name="jdbcUrl" value="jdbc:oracle:thin:@192.168.80.130:1521:orcl"/>
<property name="user" value="kpwang"/>
<property name="password" value="ww920321"/>
</bean>
<bean id="accountDao" class="com.kpwang.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
dao
public interface IAccountDao {
void updateAccount(Account account);
List<Account> findAccountByName(Serializable accountName);
}
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
public void updateAccount(Account account) {
this.getJdbcTemplate().update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
public List<Account> findAccountByName(Serializable accountName) {
return this.getJdbcTemplate().query("select * from account where name=?",new AccountRowMapper(),accountName);
}
}
service
public interface IAccountService {
//转账
void transter(String sourceName, String targetName, Float money);
}
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
@Transactional(rollbackFor = Exception.class)
public void transter(String sourceName, String targetName, Float money) {
System.out.println("transfer......");
Account source = accountDao.findAccountByName(sourceName).get(0);
Account target = accountDao.findAccountByName(targetName).get(0);
if (source.getMoney()<money){
throw new RuntimeException("账号余额不足");
}else {
source.setMoney(source.getMoney()-money);
}
target.setMoney(target.getMoney()+money);
accountDao.updateAccount(source);
int i=1/0;
accountDao.updateAccount(target);
}
}
实体类
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setMoney(Float money) {
this.money = money;
}
public Float getMoney() {
return money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money='" + money + '\'' +
'}';
}
}
public class AccountRowMapper implements RowMapper<Account>,Serializable {
public Account mapRow(ResultSet resultSet, int i) throws SQLException {
Account account=new Account();
account.setId(resultSet.getInt("id"));
account.setName(resultSet.getString("name"));
account.setMoney(resultSet.getFloat("money"));
return account;
}
}
junit
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:application.xml")
public class IAccountServiceTest {
@Autowired
private IAccountService accountService;
@Test
public void transter() {
accountService.transter("aaa","bbb",250f);
}
}
初始数据:
事务回滚