JDBCTemplate使用及事务管理实现
事务简单的理解就是在一个功能或者方法中,如果有不止一次的对数据库数据插入或者说更新操作,这些操作必须同时成功或者说同时失败,共存亡。假设一个有事物的业务场景,最近黄金价格在不断上涨,于是有人就会想把手里的闲钱购买黄金保值投资,我们就以客户和银行之间的黄金交易为场景。假设,现在有客户去银行自助服务机购买黄金,流程是这样的:客户输入购买黄金种类和数量,确定之后,银行系统开始查询黄金库存,减库存,查询客户账户金额,减去购买金额,但是这时候发现客户余额不足不能交易,但是前面黄金库存数量已经操作就会有问题。所以,上面的流程中减黄金库存和减客户账户金额这两个操作就是一个事务,必须同时成功或者是同时失败,才能保证系统的正常运行,不然银行黄金卖出去了没划到钱问题就打了。下面就以这个场景和MySQL数据库为基础,开始正文。
1.Spring中JDBCTemplate应用
首先,创建两个表:银行黄金信息表、银行客户表
CREATE TABLE bank_gold (
gold_type VARCHAR(50) NOT NULL COMMENT '黄金类型:金条;金块',
gold_num INT(11) NOT NULL DEFAULT '0' COMMENT '黄金库存数量',
gold_price INT(11) NOT NULL DEFAULT '0' COMMENT '黄金价格:元/条、元/块'
)
COMMENT='银行黄金信息表'
COLLATE='utf8_general_ci'
ENGINE=InnoDB;
CREATE TABLE `bank_user` (
`user_account` VARCHAR(50) NOT NULL COMMENT '储户账号',
`user_name` VARCHAR(50) NOT NULL COMMENT '储户名称',
`account_num` INT(11) NOT NULL DEFAULT '0' COMMENT '账户金额'
)
COMMENT='银行客户表'
COLLATE='utf8_general_ci'
ENGINE=InnoDB
;
然后,创建实体类(略)和数据库和实体类与数据结果的映射类,如下:(仅展示银行黄金信息表,银行客户表类似)
package com.me.spring;
import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet;
import java.sql.SQLException;
public class BankGoldMapper implements RowMapper { // 实现RowMapper映射接口
public BanKGold mapRow(ResultSet rs, int rowNum) throws SQLException {
BanKGold banKGold = new BanKGold();
banKGold.setGoldType(rs.getString("gold_type"));
banKGold.setGoldNum(rs.getInt("gold_num"));
banKGold.setGoldPrice(rs.getInt("gold_price"));
return banKGold;
}
}
再然后,创建对这两个表操作的Dao接口类,简单演示都是只有各一查询一个更新方法,放在一个接口:
Dao接口:
package com.me.spring;
public interface BankDao {
/**
* 查询黄金信息
*
* @param goldType 黄金类型:金条、金块
* @return BankGold 黄金信息实体类
* */
public BanKGold queryGoldInfo(String goldType);
/**
* 更新黄金库存
*
* @param goldType 黄金类型:金条、金块
* @param goldNum 购买后黄金库存
* @return
* */
public void updateGoldNum(String goldType, Integer goldNum);
/**
* 查询客户账户金额
*
* @param userAccount 客户账户
* @return Integer 客户账户金额
* */
public Integer queryAccountNum(String userAccount);
/**
* 更新客户账户金额
*
* @param userName 客户名称
* @return
* */
public void updateAccountNum(String userName, Integer accountNum);
}
Dao接口实现类:
package com.me.spring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class BankDaoImpl implements BankDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public BanKGold queryGoldInfo(String goldType) {
String sql = "select * from bank_gold where gold_type=?";
BanKGold banKGold = (BanKGold) jdbcTemplate.queryForObject(sql,new Object[]{goldType},new BankGoldMapper());
return banKGold;
}
public void updateGoldNum(String goldType, Integer goldNum) {
String sql = "update bank_gold set gold_num=? WHERE gold_type=?";
jdbcTemplate.update(sql, goldNum, goldType);
}
public Integer queryAccountNum(String userName) {
String sql = "select * from bank_user where user_name=?";
BankUser bankUser = (BankUser) jdbcTemplate.queryForObject(sql,new Object[]{userName},new BankUserMapper());
return bankUser.getAccountNum();
}
public void updateAccountNum(String userName, Integer accountNum) {
String sql = "update bank_user set account_num=? WHERE user_name=?";
jdbcTemplate.update(sql, accountNum, userName);
}
}
注:例子都是以注解方式实现
再然后,创建一个银行自助服务机Banker,目前只具有一个功能就是供客户自助购买黄金,如下:
package com.me.spring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import java.io.IOException;
@Repository
public class Banker {
@Autowired
private BankDaoImpl bankDaoImpl;
/**
* 客户购买黄金
*
* @param userName 客户名称
* @param goldType 购买黄金类型
* @param num 购买黄金数量
* @return
* */
public void buyGold(String userName, String goldType, Integer num) {
// 查询金条信息
BanKGold banKGold = bankDaoImpl.queryGoldInfo(goldType);
// 查询金条库存
Integer goldBarNum = banKGold.getGoldNum();
// 判断库存数量
if (goldBarNum > 0 && goldBarNum >= num){
bankDaoImpl.updateGoldNum(goldType, goldBarNum-num);
} else {
System.out.println("金条库存不足,无法购买!");
}
// 查询账户金额
Integer accountNum = bankDaoImpl.queryAccountNum(userName);
// 购买金条费用
Integer mon = banKGold.getGoldPrice()*num;
// 判断金额是否充足
if (num > 0 && accountNum >= mon){
bankDaoImpl.updateAccountNum(userName, accountNum - mon);
} else {
System.out.println("账户余额不足,无法购买!");
}
}
}
在使用JDBC时,配置不能少,如下:
<!--扫描注解声明-->
<context:component-scan base-package="com.me.*"></context:component-scan>
<!--配置数据源-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName">
<value>com.mysql.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost:3306/test?characterEncoding=utf8</value>
</property>
<property name="username">
<value>root</value>
</property>
<property name="password">
<value>******</value>
</property>
</bean>
<!--配置Spring的jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
注解扫描必不可少,要连接数据库的四大金刚驱动、地址、用户名及密码也不能少,最后配置的就是jdbcTemplate并引用数据源。
添加些数据银行黄金库存和一个客户,准备测试一下:
现在,Snow客户要买五根金条:
Banker banker = (Banker) context.getBean("banker");
banker.buyGold("Snow", "金条",5);
运行结果:
账户余额不足,无法购买!
数据库的修改,因为没有事务的管理,所有就是黄金库存减了,客户钱没少,其实交易失败的,所以接下来要实现事务管理,达到期望交易效果。
2.Spring事务管理实现
首先要加一些关于事务管理的bean,如下:
<!--数据源事务管理-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="defaultTransactionDefinition" class="org.springframework.transaction.support.DefaultTransactionDefinition"></bean>
在稍微修改一下Dao接口的实现类:
package com.me.spring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import java.io.IOException;
@Repository
public class Banker {
@Autowired
private BankDaoImpl bankDaoImpl;
@Autowired
private DefaultTransactionDefinition defaultTransactionDefinition; // 修改1:配置的bean
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager; // 修改2:配置的bean
public void buyGoldBar(String userName, String goldType, Integer num) throws DataAccessException {
// 修改3:获取事务的状态
TransactionStatus status = dataSourceTransactionManager.getTransaction(defaultTransactionDefinition);
try {
// 查询金条信息
BanKGold banKGold = bankDaoImpl.queryGoldInfo(goldType);
// 查询金条库存
Integer goldBarNum = banKGold.getGoldNum();
// 判断库存数量
if (goldBarNum > 0 && goldBarNum >= num){
bankDaoImpl.updateGoldNum(goldType, goldBarNum-num);
} else {
System.out.println("金条库存不足,无法购买!");
throw new Exception(); // 修改4:抛出异常
}
// 查询账户金额
Integer accountNum = bankDaoImpl.queryAccountNum(userName);
// 购买金条费用
Integer mon = banKGold.getGoldPrice()*num;
// 判断金额是否充足
if (num > 0 && accountNum >= mon){
bankDaoImpl.updateAccountNum(userName, accountNum - mon);
} else {
System.out.println("账户余额不足,无法购买!");
throw new Exception(); // 修改5:抛出异常
}
dataSourceTransactionManager.commit(status); // 修改6:提交一个事务,包含两个更新操作
} catch (Exception e){
dataSourceTransactionManager.rollback(status); // 修改7:交易异常,事务回滚
}
}
}
再运行测试,如果余额不足,黄金的库存数量就不会减少啦。
后记:Spring事务管理的实现方式有两种:编程式事务和声明式事务,时间有限声明式事务抽时间再说说;编程事务优点是更加的灵活,但是缺点是如果有大量的事务就比较麻烦,适合声明式事方式务实现,所以在实际应用中还需要结合实际选取实现方式。