准备数据
准备两张数据表BOOKS和USERS,表结构如下:
这张表为BOOKS表,有书名、价格和库存。
这张表为USERS表,有用户名称和余额。
接下来,编写买书的方法,写一个书店的Dao接口:
package cn.net.bysoft.lesson8;
public interface BookShopDao {
// 根据id查询书的价格,要买书之前需要获得书的价格。
public int findBookPriceById(int id);
// 根据id更新书的库存,购买了该图书,库存要减少一本。
public void updateBookStockById(int id);
// 根据用户名称更新用户的余额,通过用户的名称,和购买的图书的价格,减少用户的余额。
public void updateUserAccountById(String name, int price);
}
该接口拥有三个方法:
- findBookPriceById:通过书的Id查找书的价格;
- updateBookStockById:通过书的Id更新书的库存;
- updateUserAccountById:根据用户名称更新用户的余额
Dao的实现类如下:
package cn.net.bysoft.lesson8;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int findBookPriceById(int id) {
// 通过书的Id查找书的价格。
String sql = "SELECT PRICE FROM BOOKS WHERE ID = ?";
return jdbcTemplate.queryForObject(sql, Integer.class, id);
}
@Override
public void updateBookStockById(int id) {
// 验证库存是否足够,通过书的Id查找到书的库存。
String sql2 = "SELECT STOCK FROM BOOKS WHERE ID = ?";
int stock = jdbcTemplate.queryForObject(sql2, Integer.class, id);
// 如果库存为0,则抛出异常,提示库存不足。
if (stock == 0)
throw new BookStockException("库存不足");
// 否则将库存减1。
String sql = "UPDATE BOOKS SET STOCK = STOCK - 1 WHERE ID = ?";
jdbcTemplate.update(sql, id);
}
@Override
public void updateUserAccountById(String name, int price) {
// 验证余额是否足够
String sql2 = "SELECT BALANCE FROM USERS WHERE NAME = ?";
int balance = jdbcTemplate.queryForObject(sql2, Integer.class, name);
// 如果余额不够要买的书的价格,则抛出异常,提示用户余额不足。
if (balance < price)
throw new UserAccountException("用户余额不足");
String sql = "UPDATE USERS SET BALANCE = BALANCE - ? WHERE NAME = ?";
jdbcTemplate.update(sql, price, name);
}
}
接下来编写Service的接口和类:
package cn.net.bysoft.lesson8;
public interface BookShopService {
// 购买图书,需要传递的参数为购买图书的用户名称和购买的图书Id。
public void purchase(String userName, int bookId);
}
package cn.net.bysoft.lesson8;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
@Autowired
private BookShopDao bookShopDao;
@Override
public void purchase(String userName, int bookId) {
// 获取书的单价。
int price = bookShopDao.findBookPriceById(bookId);
// 更新书的库存。
bookShopDao.updateBookStockById(bookId);
// 更新用户的余额。
bookShopDao.updateUserAccountById(userName, price);
}
}
接下来进行测试:
@Test
public void testBookShopService() {
// 测试,购买Jack余额是200,购买1号图书,108。
// 余额还剩92。
bookShopService.purchase("Jack", 1);
}
这时,如果在买一本则会出错。回到Service的方法:
首先,获得1号书的价格,108,接着库存减1,最后,当需要减少用户余额时会抛出异常,用户余额还有92,不够108。但是库存却减少了。测试一下,再次执行这个函数,库存减少了但是用户余额没有减少:
让我们把分布式事务加上,在Service的purchase方法上加入@Transactional注解:
package cn.net.bysoft.lesson8;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
@Autowired
private BookShopDao bookShopDao;
@Transactional(propagation=Propagation.REQUIRED)
@Override
public void purchase(String userName, int bookId) {
// 获取书的单价。
int price = bookShopDao.findBookPriceById(bookId);
// 更新书的库存。
bookShopDao.updateBookStockById(bookId);
// 更新用户的余额。
bookShopDao.updateUserAccountById(userName, price);
}
}
配置文件如下:
<?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:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd ">
<!-- 基于属性文件配置 -->
<context:property-placeholder location="classpath:db.properties" />
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
<property name="driverClass" value="${driverClass}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<context:component-scan base-package="cn.net.bysoft.lesson8">
</context:component-scan>
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
</beans>
在进行测试:
结果如上图,程序抛出异常,库存和余额并没有不正确。
可以从XML配置文件中看出,已经将datasource托管给了spring框架管理,spring框架来确定何时何地去commit。
这样,就再也不用在dao层的代码中加入业务逻辑了。