通过简单案例转账业务引出“事务”以及JAVAEE三层架构
标签: MVC
1、银行转账业务实现
第一版:
只用刚刚入门时候学到的C3p0来进行数据库操作,还是分为Servlet service dao,进行操作,如下所示。
Servlet代码
`
try{
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
//1、获取参数
String to = request.getParameter("to");
String from = request.getParameter("from");
double money = Double.parseDouble(request.getParameter("money"));
//2、调用Service层完成转账操作
AccountService service=new AccountServiceImpl();
service.account(to,from,money);
//3、回显信息 没抛异常,转账成功
response.getWriter().write("<h1>转账成功</h1>");
}catch(Exception e){
//转账失败
response.getWriter().write("<h1>转账失败</h1>");
e.printStackTrace();
}
}`
这里使用静态代理的装饰设计模式,采用接口来增强扩展性,接口不再展示。
Service代码
`AccountDao dao=new AccountDaoImpl();
//付钱 只让一个人扣钱
dao.payFor1(from,money);
int a=1/0;
//收钱
dao.save1(to,money);`
请记住我在这里使用了 int a=1/0;
具体情况下面再说
Dao代码
/**
* 第一版的 付钱
*/
@Override
public void payFor1(String from, double money) {
QueryRunner run = new QueryRunner(C3p0Utils.getDataSource());
String sql = "UPDATE account SET money=money-? WHERE NAME=?";
Object[] param = {money,from};
try {
run.update(sql,param);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
/**
* 第一版的 收钱
*/
@Override
public void save1(String to, double money) {
QueryRunner run = new QueryRunner(C3p0Utils.getDataSource());
String sql = "UPDATE account SET money=money+? WHERE NAME=?";
Object[] param = {money,to};
try {
run.update(sql,param);
} catch (SQLException e) {
throw new RuntimeException(e);
}
第一版模式已经开发完成,当运行的时候如果int a=1/0;
没有注销,你会发现执行到那段错误的代码之后,就会报错,不会继续执行,但是回数据库你会发现,执行错误代码之前的那段付款的代码已经执行,结果收款的代码没有执行,数据库中,一个地方签收了,另一个人的前却没有多。这要是在银行转账中出现,那银行可就了不得了。这就涉及到了事务,在MySQL中,事务默认开启,并且自动提交,例如:
UPDATE account SET money=money+1000 WHERE NAME='小红';
上面这句sql语句:在mysql中可以理解为执行了如下三句:
start transaction; //开启事务处理
UPDATE account SET money=money+1000 WHERE NAME='小红';
commit; //提交事务处理
但是如果关闭了MySQL的事务默认开启,则底层其实执行了如下两句:
START TRANSACTION;--开启
UPDATE account SET money=money-1000 WHERE NAME='小明';
第二版:加入事务控制
在Connection接口中,方法:setAutoCommit(boolean flag)
用来设置本次事务的自动提交策略。让操作共用一个事务处理。
setAutoCommit(true)
表示自动提交;
setAutoCommit(false)
表示手动提交;
介绍两个事务:
一个是commit(); 提交事务,事务提交之后,关闭事务。
另一个rollback();回滚事务,事务没提交成功回滚之后,也会关闭事务。
事务针对的业务处理模块,所以把事务控制添加到Service层上
Service代码
AccountDao dao=new AccountDaoImpl();
//1、保证转账在一个事务中,就必须保证转账操作用一个Connection对象
Connection con = C3p0Utils.getConnection();
//事务不会自动提交,而是手动提交
try {
con.setAutoCommit(false);
//付钱 只让一个人扣钱
dao.payFor(from,money,con);
int a=1/0;
//收钱
dao.save(to,money,con);
//没问题提交
con.commit();
} catch (Exception e) {
//有问题就回滚
try {
con.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
throw new RuntimeException(e);
} finally{
if(con!=null)
try {
con.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
Dao代码
/**
* 第二版的 付钱
*/
@Override
public void payFor(String from, double money,Connection con) {
QueryRunner run = new QueryRunner();
String sql = "UPDATE account SET money=money-? WHERE NAME=?";
Object[] param = {money,from};
try {
run.update(con,sql,param);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
/**
* 第二版的 收钱
*/
@Override
public void save(String to, double money,Connection con) {
QueryRunner run = new QueryRunner();
String sql = "UPDATE account SET money=money+? WHERE NAME=?";
Object[] param = {money,to};
try {
run.update(con,sql,param);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
你会发现第二版之后。业务就变得正常了;接下来进行代码的优化:
第三版 再加入事务控制的基础上,用DBUtils工具进行优化
AccountDao dao=new AccountDaoImpl();
//1、保证转账在一个事务中,就必须保证转账操作用一个Connection对象
Connection con = C3p0Utils.getConnection();
//事务不会自动提交,而是手动提交
try {
con.setAutoCommit(false);
//付钱 只让一个人扣钱
dao.payFor(from,money,con);
int a=1/0;
//收钱
dao.save(to,money,con);
//没问题提交
DbUtils.commitAndCloseQuietly(con);
} catch (Exception e) {
DbUtils.rollbackAndCloseQuietly(con);
throw new RuntimeException(e);
}
会发现采用DbUtils代码简介了很多。
DbUtils.commitAndCloseQuietly(con);//事务提交
DbUtils.rollbackAndCloseQuietly(con);//事务回滚
在C3p0Utiles工具类中,声明两个成员变量
private static DataSource datasource = new CombopoolDataSource();
private Static ThreadLocal<Connection> local = new ThreadLocal<Connection>()
//c3p0+DBUtils
//DBUtils使用时,需要获取dataSource对象
public static DataSource getDataSource(){
return dataSource;
}
/**
* 获取链接方法
*/
public static Connection getConnectionwithThread(){
Connection conn = local.get();
if(conn==null){
try {
conn=dataSource.getConnection();
local.set(conn);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
return conn;
}
Service代码优化
/**
* 转账操作 第三版 加入事务控制
*
* 用DBUtils工具进行优化
* 用ThreadLocal传递Connection对象
*
*/
@Override
public void account(String to, String from, double money) {
AccountDao dao=new AccountDaoImpl();
//1、保证转账在一个事务中,就必须保证转账操作用一个Connection对象
Connection con = C3p0Utils.getConnectionWithThreadLocal();
//事务不会自动提交,而是手动提交
try {
con.setAutoCommit(false);
//付钱 只让一个人扣钱
dao.payForWithThreadLocal(from,money);
int a=1/0;
//收钱
dao.saveWithThreadLocal(to,money);
//没问题提交
DbUtils.commitAndCloseQuietly(con);
} catch (Exception e) {
DbUtils.rollbackAndCloseQuietly(con);
throw new RuntimeException(e);
}
Dao层代码
@Override
public void payForWithThreadLocal(String from, double money) {
QueryRunner run = new QueryRunner();
String sql = "UPDATE account SET money=money-? WHERE NAME=?";
Object[] param = {money,from};
try {
run.update(C3p0Utils.getConnectionWithThreadLocal(),sql,param);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public void saveWithThreadLocal(String to, double money) {
QueryRunner run = new QueryRunner();
String sql = "UPDATE account SET money=money+? WHERE NAME=?";
Object[] param = {money,to};
try {
run.update(C3p0Utils.getConnectionWithThreadLocal(),sql,param);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
总结:从最基础的C3p0Utile获取Connection连接对象,对数据库进行操作,然后找到事务,根据对事务的需求找到Mysql事务的特点,默认开启,并执行完后,事务关闭特点,找到对事务开启和关闭的处理,发现了Connection中的con.setAutoCommit(false);这个方法可以关闭事务的自动提交改为手动提交,之后因为一些事务的提交和处理有好些异常要处理,采用Dbutils中的
DbUtils.commitAndCloseQuietly(con);和DbUtils.rollbackAndCloseQuietly(con);
来进行处理。为了保证转账在一个事务中,就必须保证转账操作用一个Connection对象。就想到了线程操作;找到然后根据缺点和需求,进行不断的代码改进和优化,得到最终的结果。