1. 什么是事物
事务就是一系列的动作,它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用。也可以这样说,把一些列的sql语句,组合成一个整体,要么所有的sql都执行成功,要么一句sql都不能被执行。
例如:银行转账,A给B转1000元,这时应有两个操作,A的账户减1000,B的账户加1000.要么都完成,要么都不完成,不然要么就是凭空多了1000,这样数据就会被破坏。
2. Spring 的声明式事务的使用(买衣服为例)
1. 创建数据表
2. 添加 jar 包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.4.RELEASE</version>
</dependency>
<!--JdbcTemplate-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.4.RELEASE</version>
</dependency>
<!--c3p0-->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.41</version>
</dependency>
3. Spring 配置文件
在 resources 下 创建 dbconfig.properties 用来存放连接数据库的数据源
jdbc.jdbcUrl=jdbc:mysql://localhost:3306/spring5
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.user=root
jdbc.password=123456
applicationContext.xml
<context:component-scan base-package="www.xq.spring"></context:component-scan>
<!-- 配置数据源 -->
<context:property-placeholder location="classpath:dbconfig.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="user" value="${jdbc.user}"></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>
4. 设计 bean 类
User类
public class User {
private int id;
private String useranme;
private double balance;
public User() {
super();
}
public User(int id, String useranme, double balance) {
super();
this.id = id;
this.useranme = useranme;
this.balance = balance;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUseranme() {
return useranme;
}
public void setUseranme(String useranme) {
this.useranme = useranme;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
Clothes 类
public class Clothes {
private int id;
private double price;
private double inventory;
public Clothes() {
super();
}
public Clothes(int id, double price, double inventory) {
super();
this.id = id;
this.price = price;
this.inventory = inventory;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public double getInventory() {
return inventory;
}
public void setInventory(double inventory) {
this.inventory = inventory;
}
}
5. 设计 dao 接口以及其实现类
UserDao
public interface UserDao {
//更新余额
public void upateBalance(int userid,double amount);
//获得余额
public double getBalance(int id);
}
UserDaoImpl
@Repository("userDao")
public class UserDaoImpl implements UserDao{
@Autowired
private JdbcTemplate jdbcTemplate;
public void upateBalance(int userid, double amount) {
// 同样考虑到余额不足情况
if(getBalance(userid) < amount) {
throw new RuntimeException("用户余额不足");
}
String sql = "UPDATE users SET balance = balance - ? WHERE id =?";
jdbcTemplate.update(sql, amount,userid);
}
public double getBalance(int id) {
String sql = "SELECT balance FROM users WHERE id=?";
return jdbcTemplate.queryForObject(sql,Double.class,id);
}
}
ClothesDao
public interface ClothesDao {
//获取单价
public double getPrice(int id);
//获取库存
public int getInventory(int id);
//更新库存
public void upadteInventory(int clothid,int amount) ;
}
ClothesDaoImpl
@Repository("clothesDao")
public class ClothesDaoImpl implements ClothesDao{
@Autowired
private JdbcTemplate jdbcTemplate;
public double getPrice(int id) {
String sql = "SELECT price FROM clothes WHERE id =?";
return jdbcTemplate.queryForObject(sql, Double.class, id);
}
public int getInventory(int id) {
String sql = "SELECT inventory FROM clothes WHERE id =?";
return jdbcTemplate.queryForObject(sql, Integer.class, id);
}
public void upadteInventory(int clothid, int amount) {
//考虑到库存不足的时候,抛出异常
if(getInventory(clothid) < amount) {
throw new RuntimeException("库存不足");
}
String sql = "UPDATE clothes SET inventory = inventory - ? WHERE id =?";
jdbcTemplate.update(sql, amount,clothid);
}
}
6. Service 层设计(买衣服)
PurchaseService
public interface PurchaseService {
public void purchaseClothes(int uid,int cid,int count);
}
PurchaseServiceImpl
@Service("purchaseService")
public class PurchaseServiceImpl implements PurchaseService {
@Autowired
private ClothesDao clothesDao;
@Autowired
private UserDao userDao;
public void purchaseClothes(int uid, int cid, int count) {
// 1. 通过衣服id获取到衣服的单价,计算出购买衣服的总金额
double sumAmount = clothesDao.getPrice(cid) * count;
// 2. 从衣服库存数中减去用户购买的数量
clothesDao.upadteInventory(cid,count);
// 3. 从用户的余额中扣除购买衣服的金额
userDao.upateBalance(uid, sumAmount);
}
}
7. 测试
public class TestTransactionManager {
@Test
public void testClothes() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
PurchaseService purchaseService = ctx.getBean(PurchaseService.class);
purchaseService.purchaseClothes(1, 1, 1);
}
}
假设数据表的数据是这样的:
users
clothes
执行两次上述方法:
users
clothes
后台
分析:
由于用户的余额只能买一本书,所以连续买两本书时,就会包用户余额不足的异常,但是库存却减少了两件,这是不符合现实生活的,所以我们就应该用事物来解决上述异常。