通过简单案例转账业务引出“事务”以及JAVAEE三层架构

通过简单案例转账业务引出“事务”以及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对象。就想到了线程操作;找到然后根据缺点和需求,进行不断的代码改进和优化,得到最终的结果。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值