转账事务控制分析
<?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-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<context:component-scan base-package="com.test"></context:component-scan>
<!-- 配置QueryRunner -->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!-- 注入数据源 -->
<!-- <constructor-arg index="0" ref="dataSource"></constructor-arg> -->
</bean>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/xiaonei"></property>
<property name="user" value="root"></property>
<property name="password" value="tingwei"></property>
</bean>
<!-- 配置Connection的工具类 -->
<bean id="connectionUtil" class="com.test.ConnectionUtil">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事物管理器 -->
<bean id="txManager" class="com.test.TransactionManager">
<property name="connectionUtil" ref="connectionUtil"></property>
</bean>
</beans>
package com.test;
import java.io.Serializable;
public class Stu implements Serializable{
private String name;
private Float score;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Float getScore() {
return score;
}
public void setScore(Float score) {
this.score = score;
}
}
package com.test;
import java.sql.Connection;
import javax.sql.DataSource;
import com.sun.java.swing.plaf.windows.WindowsBorders.DashedBorder;
/**
* 连接工具类,用于从数据源中获取一个连接,并实现线程的绑定
*/
public class ConnectionUtil {
private ThreadLocal<Connection> tl =new ThreadLocal<Connection>();
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void setTl(ThreadLocal<Connection> tl) {
this.tl = tl;
}
/**
* 获取当前线程上的连接
* @return Connection
*/
public Connection getThreadConnetion(){
try {
// 1.先从ThreadLocal上获取
Connection conn = tl.get();
//2.判断当前线程是否有连接
if(conn == null){
//3.从数据源中获取一个连接,并且存入ThreadLocal中
conn = dataSource.getConnection();
tl.set(conn);
}
//4.返回当前线程上的连接
return conn;
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException(e);
}
}
/**
* 把连接和线程解绑
*
*/
public void removeConnection(){
tl.remove();
}
}
package com.test;
/**
* 和事物管理相关的工具类,包含开启事务、提交事务、回滚事物、释放连接
* @author Administrator
*
*/
public class TransactionManager {
private ConnectionUtil connectionUtil;
/**
* 开启事物
*
*/
public void begainTransaction(){
try {
connectionUtil.getThreadConnetion().setAutoCommit(false);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
/**
* 提交事务
*
*/
public void commit(){
try {
connectionUtil.getThreadConnetion().commit();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
/**
* 回滚事物
*
*/
public void rollback(){
try {
connectionUtil.getThreadConnetion().rollback();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
/**
* 释放连接
*
*/
public void release(){
try {
connectionUtil.getThreadConnetion().close(); //没关闭,而是还给连接池
connectionUtil.removeConnection();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
public void setConnectionUtil(ConnectionUtil connectionUtil) {
this.connectionUtil = connectionUtil;
}
}
package com.test;
import java.util.List;
public interface IStuService{
/**
* 查询所有
*/
List<Stu> findAllStu();
Stu findStuByName(String name);
void saveStu(Stu stu);
void updateStu(Stu stu);
void deleteStu(String name);
/**
* 转账
* @param sourceName
* @param targetName
* @param money
*/
void transfer(String sourceName,String targetName,Float money);
}
package com.test;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service("stuService")
public class StuServiceImp implements IStuService{
@Autowired
private IStuDao stuDao;
@Autowired
private TransactionManager txManager;
public void setStuDao(IStuDao stuDao) {
this.stuDao = stuDao;
}
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
public void transfer(String sourceName, String targetName, Float money) {
// TODO Auto-generated method stub
try {
txManager.begainTransaction();
//1.根据名称查出要转账的账户
Stu source = stuDao.findStuByName(sourceName);
//2.根据名称查出转入的账户
Stu target = stuDao.findStuByName(targetName);
//3.转出账户减钱
source.setScore(source.getScore()-money);
//4.转入账户加钱
target.setScore(target.getScore()+money);
//5.更新转出账户
stuDao.updateStu(source);
//6.更新转入账户
//会导致事物的一致性,转账会失败,针对这个问题怎么解决呢?
int i=1/0;
stuDao.updateStu(target);
txManager.commit();
System.out.println("转账成功,转账后双方余额");
System.out.println(source.getScore());
System.out.println(target.getScore());
} catch (Exception e) {
// TODO: handle exception
txManager.rollback();
throw new RuntimeException(e);
}
txManager.release();
}
@Override
public void deleteStu(String name) {
// TODO Auto-generated method stub
try {
//1.开启事务
txManager.begainTransaction();
//2.执行操作
stuDao.deleteStu(name);
//3.提交事务
txManager.commit();
//4.返回结果
} catch (Exception e) {
// TODO: handle exception
//5.回滚操作
txManager.rollback();
throw new RuntimeException(e); //产生错误后程序不再执行
}finally{
//6.释放连接
txManager.release();
}
}
@Override
public List<Stu> findAllStu() {
List<Stu> stus = null;
// TODO Auto-generated method stub
try {
//1.开启事务
txManager.begainTransaction();
//2.执行操作
stus = stuDao.findAllStu();
//3.提交事务
txManager.commit();
//4.返回结果
} catch (Exception e) {
// TODO: handle exception
//5.回滚操作
txManager.rollback();
throw new RuntimeException(e); //产生错误后程序不再执行
}finally{
//6.释放连接
txManager.release();
}
return stus;
}
@Override
public Stu findStuByName(String name) {
// TODO Auto-generated method stub
// TODO Auto-generated method stub
Stu stu=null;
try {
//1.开启事务
txManager.begainTransaction();
//2.执行操作
stu = stuDao.findStuByName(name);
//3.提交事务
txManager.commit();
//4.返回结果
} catch (Exception e) {
// TODO: handle exception
//5.回滚操作
txManager.rollback();
throw new RuntimeException(e); //产生错误后程序不再执行
}finally{
//6.释放连接
txManager.release();
}
return stu;
}
@Override
public void saveStu(Stu stu) {
// TODO Auto-generated method stub
stuDao.saveStu(stu);
try {
//1.开启事务
txManager.begainTransaction();
//2.执行操作
stuDao.saveStu(stu);
//3.提交事务
txManager.commit();
//4.返回结果
} catch (Exception e) {
// TODO: handle exception
//5.回滚操作
txManager.rollback();
throw new RuntimeException(e); //产生错误后程序不再执行
}finally{
//6.释放连接
txManager.release();
}
}
@Override
public void updateStu(Stu stu) {
// TODO Auto-generated method stub
try {
//1.开启事务
txManager.begainTransaction();
//2.执行操作
stuDao.updateStu(stu);
//3.提交事务
txManager.commit();
//4.返回结果
} catch (Exception e) {
// TODO: handle exception
//5.回滚操作
txManager.rollback();
throw new RuntimeException(e); //产生错误后程序不再执行
}finally{
//6.释放连接
txManager.release();
}
}
}
package com.test;
import java.util.List;
public interface IStuDao{
List<Stu> findAllStu();
Stu findStuByName(String name);
void saveStu(Stu stu);
void updateStu(Stu stu);
void deleteStu(String name);
}
package com.test;
import java.sql.Connection;
import java.util.List;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
@Repository("stuDao")
public class StuDaoImp implements IStuDao{
@Autowired
private QueryRunner runner;
@Autowired
private ConnectionUtil connectionUtil;
public void setConnectionUtil(ConnectionUtil connectionUtil) {
this.connectionUtil = connectionUtil;
}
public StuDaoImp(QueryRunner runner){
this.runner = runner;
}
public StuDaoImp(){
}
@Override
public void deleteStu(String name) {
// TODO Auto-generated method stub
try {
runner.update(connectionUtil.getThreadConnetion(),"delete from stu where id=?", name);
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException(e);
}
}
@Override
public List<Stu> findAllStu() {
// TODO Auto-generated method stub
try {
return runner.query(connectionUtil.getThreadConnetion(),"select * from stu", new BeanListHandler<Stu>(Stu.class));
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException(e);
}
}
@Override
public Stu findStuByName(String name) {
// TODO Auto-generated method stub
try {
List<Stu> stus = runner.query(connectionUtil.getThreadConnetion(),"select * from stu where sname = ?", new BeanListHandler<Stu>(Stu.class),name);
if(stus == null || stus.size() == 0){
return null;
}
if(stus.size()>1){
throw new RuntimeException("结果集 不唯一,数据有问题");
}
System.out.println(stus.get(0).getName());
return stus.get(0);
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException(e);
}
}
@Override
public void saveStu(Stu stu) {
// TODO Auto-generated method stub
try {
runner.update(connectionUtil.getThreadConnetion(),"insert into stu(sname,score)values(?,?)",stu.getName(),stu.getScore());
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException(e);
}
}
@Override
public void updateStu(Stu stu) {
// TODO Auto-generated method stub
try {
runner.update(connectionUtil.getThreadConnetion(),"update stu set sname=?,score=? where sname=?",stu.getName(),stu.getScore(),stu.getName());
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException(e);
}
}
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
}
/**
* 转账事务控制分析
*/
package com.test;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import sun.print.resources.serviceui;
public class Test{
public static void main(String[] args){
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取bean对象
IStuService stuService = (IStuService)ac.getBean("stuService",IStuService.class);
// List<Stu> stus= stuService.findAllStu();
// for(Stu stu:stus){
// System.out.println(stu.getName()+" "+stu.getScore());
// }
// Stu stu = (Stu)stuService.findStuByName("aaa"); 为什么得到的stu name为null
// Stu stu = new Stu();
// stu.setName("bbb");stu.setScore(98f);
// stuService.saveStu(stu); 亲测有效
// stuService.updateStu(stu); 亲测有效
// System.out.println(stu.getName()+" "+stu.getScore());
stuService.transfer("aaa", "bbb", 10f); //后台测试转账能成功,只是存不进去,原因在于取得的名字是null,update的时候存不进去
}
}
三月 26, 2020 9:52:12 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@12922f6: display name [org.springframework.context.support.ClassPathXmlApplicationContext@12922f6]; startup date [Thu Mar 26 21:52:12 CST 2020]; root of context hierarchy 三月 26, 2020 9:52:12 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions INFO: Loading XML bean definitions from class path resource [bean.xml] 三月 26, 2020 9:52:12 下午 org.springframework.context.support.AbstractApplicationContext obtainFreshBeanFactory INFO: Bean factory for application context [org.springframework.context.support.ClassPathXmlApplicationContext@12922f6]: org.springframework.beans.factory.support.DefaultListableBeanFactory@1383eb 三月 26, 2020 9:52:12 下午 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1383eb: defining beans [stuDao,stuService,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,runner,dataSource,connectionUtil,txManager]; root of factory hierarchy 三月 26, 2020 9:52:12 下午 com.mchange.v2.log.MLog INFO: MLog clients using java 1.4+ standard logging. 三月 26, 2020 9:52:13 下午 com.mchange.v2.c3p0.C3P0Registry banner INFO: Initializing c3p0-0.9.2.1 [built 20-March-2013 11:16:28 +0000; debug? true; trace: 10] 三月 26, 2020 9:52:13 下午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource getPoolManager INFO: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, dataSourceName -> 1hge161a96evwyt15tul8v|5b784b, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> 1hge161a96evwyt15tul8v|5b784b, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/xiaonei, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 15, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 3, numHelperThreads -> 3, preferredTestQuery -> null, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ] null Exception in thread "main" java.lang.RuntimeException: java.lang.ArithmeticException: / by zero at com.test.StuServiceImp.transfer(StuServiceImp.java:50) at com.test.Test.main(Test.java:29) Caused by: java.lang.ArithmeticException: / by zero at com.test.StuServiceImp.transfer(StuServiceImp.java:39) ... 1 more null
这样做虽然控制了事务,但是代码显得非常冗余,如何解决请看下回分解。