一、背景
1.最近在研究java的事务传播性,一直有个地方不太明白的地方,直到在在项目中有看到实际中怎么用到的,才恍然大悟,于是就搜了搜关于java中的事务何时开启,以什么方式开启的,事务的隔离级别以及事务的传播性,于是写下了这个博客来记录下自己的学习成果。
2.也参考了很多大佬的博客文章,最后会把参考的文献提供出来,以供大家参考和学习。如果那里做的不好,欢迎大家留言以及批评指正,废话不多说了,开始上干货。
二、关于如何开启事务
1.Spring的事务管理分为两类:
1.1.编程式事务管理(手动编写代码完成事务管理)
1.2.声明式事务管理(不需要手动编写代码,需要配置)
2.案例一(不加事务的时候)
2.1.创建表
CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(10) DEFAULT NULL,
`money` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
2.2.创建项目并引入Maven依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/c3p0/c3p0 -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!-- Spring 整合测试的包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
2.3.项目结构如下:
2.4.编写实体类
public class Account {
private Integer id;
private String name;
private Integer 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 Integer getMoney() {
return money;
}
public void setMoney(Integer money) {
this.money = money;
}
@Override
public String toString() {
return "Account [id=" + id + ", name=" + name + ", money=" + money + "]";
}
}
2.5.编写Dao层
/**
* 银行账户的相关操作 此处用了@see注释,在实现类可以继承接口中方法的注释内容
*
* @author hao
* @see AccountDaoImpl
*
*/
public interface IAccountDao {
/**
* 添加账户
*
* @param account 要添加的账户
*/
public void add(Account account);
/**
* 转出的方法
*
* @param from :转出的账户,打出钱
* @param money :要转账金额
*/
public void out(Account from, Integer money);
/**
* 转出的方法
*
* @param to 转入的账户,收到钱
* @param money 要转账金额
*/
public void in(Account to, Integer money);
/**
* 通过名字查询账户
* @param name 账户名
* @return
*/
public Account selectOneByName(String name);
}
2.5.1.实现类
public class AccountDaoImpl implements IAccountDao{
@Autowired
@Qualifier("jdbcTemplate")
private JdbcTemplate jdbcTemplate;
@Override
public void add(Account account) {
String sql = "insert into account values(null,?,?)";
jdbcTemplate.update(sql,account.getName(),account.getMoney());
}
@Override
public Account selectOneByName(String name) {
String sql = "select * from account where name = ?";
Account account = null;
try {//这里需要捕获结果集为空时的异常
account = jdbcTemplate.queryForObject(sql, new RowMapper<Account>() {
@Override
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account account = new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getInt("money"));
return account;
}
}, name);
} catch (EmptyResultDataAccessException e) {
e.printStackTrace();
return null;
}
return account;
}
public void out(Account from, Integer money) {
String sql = "update account set money = money-? where name =? ";
jdbcTemplate.update(sql, money,from.getName());
}
public void in(Account to, Integer money) {
String sql ="update account set money = money+? where name =?";
jdbcTemplate.update(sql,money,to.getName());
}
}
2.6.业务层
/**
*
* @author hao
* @see AccountServiceImpl
*/
public interface IAccountService {
/**
* 向数据库中添加用户
*
* @param account 要添加的用户对象
*/
public void addAccount(Account account);
/**
* 转账的方法
*
* @param from 转出的账户
* @param to 转入的账户
* @param money 转账金额
*/
public void transfer(Account from, Account to, Integer money);
/**
*
* @param name 需要查询的账户名
* @return
*/
public Account findAccountByName(String name);
}
2.6.1.实现类
/**
* 没有添加事务
* @author hao
*
*/
public class AccountServiceImpl implements IAccountService {
// 注入 accountDao 利用setter方法注入属性
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void addAccount(Account account) {
accountDao.add(account);
}
@Override
public Account findAccountByName(String name) {
Account account = accountDao.selectOneByName(name);
return account;
}
@Override
public void transfer(Account from, Account to, Integer money) {
accountDao.out(from, money);// 转出钱
int i= 1/0 //出现异常时
accountDao.in(to, money);// 收入钱
}
}
2.7.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"
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">
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置c3p0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 这里需要注入数据源 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 此处声明Dao层类,用注解的方式将jdbcTemplate注入到了accountDao中,也可用其他方式 -->
<bean