事务(转账实例)

事务(以转账为例)

 

事务:
  就是一件完整的事情,包含多个操作单元,这些操作要么全部成功,要么全部失败.
  例如:转账,包含转出操作和转入操作.

 

mysql中的事务:
        mysql中事务默认是自动提交,一条sql语句就是一个事务.
        开启手动事务方式
            方式1:关闭自动事务.(了解)
                set autocommit = off;
            方式2:手动开启一个事务.(理解)
                start transaction;-- 开启一个事务
                commit;-- 事务提交
                rollback;-- 事务回滚
        扩展:
            oracle中事务默认是手动的,必须手动提交才可以.
    java中的事务:
        Connection接口的api:★
            setAutoCommit(false);//手动开启事务
            commit():事务提交
            rollback():事务回滚
        
        扩展:了解 Savepoint还原点
            void rollback(Savepoint savepoint) :还原到那个还原点
            Savepoint setSavepoint() :设置还原点
例如:创建数据库和表
            create database hjh;
            use hjh;
            create table account(
                name varchar(20),
                money int
            );
            
            insert into account values('hejh','1000');
            insert into account values('swy','1000');
            
        完成 hejh给swy转500;
            update account set money = money - 100 where name='';
            update account set money = money + 100 where name='swy';

转账案例:

步骤分析:
    1.数据库和表
    2.新建一个项目 transfer
    3.导入jar包和工具类
        驱动 jdbcUtils
        c3p0及其配置文件和工具类
        dbutils
    4.新建一个account.jsp 表单
    5.accountservlet:
        接受三个参数
        调用accountservice.account方法完成转账操作
        打印信息
    6.account方法中:
        使用jdbc不考虑事务
        调用dao完成转出操作
        调用dao完成转入操作
    7.dao中

代码实现:

 

  web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
      id="WebApp_ID" version="3.1"> <servlet> <servlet-name>AccountServlet</servlet-name> <servlet-class>com.hjh.servlet.AccountServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>AccountServlet</servlet-name> <url-pattern>/account</url-pattern> </servlet-mapping> </web-app>

  account.jsp 

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>转账页面</title>
</head>
<body>
    <form action="${pageContext.request.contextPath }/account" method="post">
        <table border="1" width="400">
            <tr>
                <td>付款人:</td>
                <td><input type="text" name="fromUser"/></td>
            </tr>
            <tr>
                <td>收款人:</td>
                <td><input type="text" name="toUser"/></td>
            </tr>
            <tr>
                <td>转账金额:</td>
                <td><input type="text" name="money"/></td>
            </tr>
            <tr>
                <td colspan="2"><input type="submit" value="转账"/></td>
            </tr>
        </table>
    </form>
</body>
</html>
AccountServlet.java
package com.hjh.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.SQLException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.hjh.service.AccountService;

/**
 * 转账案例
 */
public class AccountServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //设置编码
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html; charset=utf-8");
        //获取输出流
        PrintWriter w = response.getWriter();
        
        //接收jsp页面传来的三个参数
        String fromUser = request.getParameter("fromUser");
        String toUser = request.getParameter("toUser");
        int money = Integer.parseInt(request.getParameter("money"));
                
        //调用AccountService的transterAccount(fromUser,toUser,money)方法
        try {
            new AccountService().transterAccount(fromUser,toUser,money);
        } catch (Exception e) {
            e.printStackTrace();
            w.println("转账失败");
            return;
        }
        
        //打印提示信息
        w.print("转账成功");
    }

  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}

  AccountService.java

package com.hjh.service;

import java.sql.SQLException;
import com.hjh.dao.AccountDao;

public class AccountService {
    public void transterAccount(String fromUser, String toUser, int money) throws Exception {
        
        AccountDao dao = new AccountDao();
        
        //转出方,出钱
        dao.accountFrom(fromUser,money);
                
        //转入方,进钱
        dao.accountTo(toUser,money);
    }
}

  AccountDao.java

package com.hjh.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.hjh.utils.JDBCUtil;

public class AccountDao {

    //出账
    public void accountFrom(String fromUser, int money) throws SQLException {
        Connection conn = null;
        PreparedStatement st  = null;
        
        try {
            //获取连接
            conn = JDBCUtil.getConnection();
            //编写sql
            String sql="update account set money = money - ?  where name = ?";
            //获取sql语句执行者
            st = conn.prepareStatement(sql);
            //设置sql参数
            st.setInt(1, money);
            st.setString(2, fromUser);
            //执行sql
            int i = st.executeUpdate();
            System.out.println("转出钱成功"+i);
            
        } catch (SQLException e) {
            e.printStackTrace();
            throw e;
        }finally {
             JDBCUtil.closeResourse(conn, st);
        }
        
    }
    
    //入账
    public void accountTo(String toUser, int money) throws SQLException {
        Connection conn = null;
        PreparedStatement st  = null;
        
        try {
            //获取连接
            conn = JDBCUtil.getConnection();
            //编写sql
            String sql="update account set money = money + ?  where name = ?";
            //获取sql语句执行者
            st = conn.prepareStatement(sql);
            //设置sql参数
            st.setInt(1, money);
            st.setString(2, toUser);
            //执行sql
            int i = st.executeUpdate();
            System.out.println("转入钱成功"+i);
            
        } catch (SQLException e) {
            e.printStackTrace();
            throw e;
        }    finally {
             JDBCUtil.closeResourse(conn, st);
        }
    }
}

  JDBCUtil.java

package com.hjh.utils;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class JDBCUtil {
    final static String driver = "com.mysql.jdbc.Driver";
    final static String url = "jdbc:mysql://localhost/hjh?useUnicode=true&characterEncoding=UTF-8";
    final static String user  = "root";
    final static String password = "root";
    
    Connection conn = null;
    PreparedStatement ps = null;
    Statement st = null;
    ResultSet rs = null;
        
    /**获取连接*/
    public static Connection getConnection() throws SQLException  {
        Connection    conn = null;
        try {
            //注册驱动
            Class.forName(driver);
            //获取连接
            conn = DriverManager.getConnection(url, user, password);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }                    
        return conn;    
    }
    
    /**关闭资源closeResourse(conn,st)*/
    public static void closeResourse(Connection conn,Statement st) {
        try {
            if(st!=null) {
                st.close();
            }else {
                st = null;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if(conn!=null) {
                conn.close();
            }else {
                conn = null;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    /**关闭资源closeResourse(conn,ps)*/
    public static void closeResourse(Connection conn,PreparedStatement ps) {
        try {
            if(ps!=null) {
                ps.close();
            }else {
                ps = null;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if(conn!=null) {
                conn.close();
            }else {
                conn = null;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    /**关闭资源closeResourse(rs)*/
    public static void closeResourse(ResultSet rs) {
        try {
            if(rs!=null) {
                rs.close();
            }else {
                rs = null;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

  启动项目,进行转账,不发生异常的时候,转账是成功的

 转账前:

  转账后:

 

一旦出现异常,钱飞了.

  模拟断电:

   accountService,java中加一个异常,模拟断电的场景:

package com.hjh.service;

import java.sql.SQLException;
import com.hjh.dao.AccountDao;

public class AccountService {
    public void transterAccount(String fromUser, String toUser, int money) throws Exception {
        
        AccountDao dao = new AccountDao();
        
        //转出方,出钱
        dao.accountFrom(fromUser,money);
        
        int i=3/0;
        
        //转入方,进钱
        dao.accountTo(toUser,money);
    }
}

  转账前:

  转账但是中途发生断电后,转账失败,但是在查询数据库之后发现,转出方的钱被扣了,但是转入方的钱不变,这样就会造成很重大的影响:

 

 

 

一旦出现异常,钱飞了.
    要想避免这事情,必须添加事务,在service添加事务.
    为了保证所有的操作在一个事务中,必须保证使用的是同一个连接
    在service层我们获取了连接,开启了事务.如何dao层使用此连接呢????
方法1:
            向下传递参数.注意连接应该在service释放

 

  accountDao.java

package com.hjh.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.hjh.utils.JDBCUtil;

public class AccountDao {

    //出账
    public void accountFrom(Connection conn, String fromUser, int money) throws SQLException {
        PreparedStatement st  = null;
        
        try {    
            //编写sql
            String sql="update account set money = money - ?  where name = ?";
            //获取sql语句执行者
            st = conn.prepareStatement(sql);
            //设置sql参数
            st.setInt(1, money);
            st.setString(2, fromUser);
            //执行sql
            int i = st.executeUpdate();
            System.out.println("转出钱成功"+i);    
        } catch (SQLException e) {
            //e.printStackTrace();
            throw e;
        }finally {
             JDBCUtil.closeStatement(st);
        }
    }
    
    //入账
    public void accountTo(Connection conn, String toUser, int money) throws SQLException {
        PreparedStatement st  = null;
        
        try {
            //编写sql
            String sql="update account set money = money + ?  where name = ?";
            //获取sql语句执行者
            st = conn.prepareStatement(sql);
            //设置sql参数
            st.setInt(1, money);
            st.setString(2, toUser);
            //执行sql
            int i = st.executeUpdate();
            System.out.println("转入钱成功"+i);
            
        } catch (SQLException e) {
            //e.printStackTrace();
            throw e;
        }    finally {
             JDBCUtil.closePreparedStatement(st);
        }
    }
}

  accountService.java

package com.hjh.service;

import java.sql.Connection;
import com.hjh.dao.AccountDao;
import com.hjh.utils.JDBCUtil;

public class AccountService {
    public void transterAccount(String fromUser, String toUser, int money) throws Exception  {
        Connection conn = null;
        AccountDao dao = new AccountDao();
        
            //开启事务
            try {
                conn = JDBCUtil.getConnection();
                conn.setAutoCommit(false);//设置事务为手动提交
                
                //转出方,出钱
                dao.accountFrom(conn,fromUser,money);
                
                int i=3/0;
                
                //转入方,进钱
                dao.accountTo(conn,toUser,money);
                //事务提交
                conn.commit();
            } catch (Exception e) {
                //e.printStackTrace();
                //事务回滚
                conn.rollback();
                throw e;
            }finally {
                JDBCUtil.closeConnection(conn);
            }
    }
}

  accountServlet.java

package com.hjh.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.hjh.service.AccountService;

/**
 * 转账案例
 *         解决中途断电问题:
 *     方法1:
            向下传递参数.注意连接应该在service释放
 */
public class AccountServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //设置编码
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html; charset=utf-8");
        //获取输出流
        PrintWriter w = response.getWriter();
        
        //接收jsp页面传来的三个参数
        String fromUser = request.getParameter("fromUser");
        String toUser = request.getParameter("toUser");
        int money = Integer.parseInt(request.getParameter("money"));
                
        //调用AccountService的transterAccount(fromUser,toUser,money)方法
        try {
            new AccountService().transterAccount(fromUser,toUser,money);
        } catch (Exception e) {
            e.printStackTrace();
            w.println("转账失败");
            return;
        }
        
        //打印提示信息
        w.print("转账成功");
    }

  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}

  JDBCUtil.java

package com.hjh.utils;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class JDBCUtil {
    final static String driver = "com.mysql.jdbc.Driver";
    final static String url = "jdbc:mysql://localhost/hjh?useUnicode=true&characterEncoding=UTF-8";
    final static String user  = "root";
    final static String password = "root";
    
    Connection conn = null;
    PreparedStatement ps = null;
    Statement st = null;
    ResultSet rs = null;
        
    /**获取连接*/
    public static Connection getConnection() throws SQLException  {
        Connection    conn = null;
        try {
            //注册驱动
            Class.forName(driver);
            //获取连接
            conn = DriverManager.getConnection(url, user, password);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }                    
        return conn;    
    }
    
    public static void closeConnection_Statement_ResultSet(Connection conn,Statement st,ResultSet rs) {    
        closeResultSet(rs);
        closeStatement(st);
        closeConnection(conn);    
    }
    
    public static void closeConnection_Statement(Connection conn,Statement st) {    
        closeStatement(st);
        closeConnection(conn);    
    }
    public static void closeConnection_PreparedStatement_ResultSet(Connection conn,PreparedStatement ps,
                                                      ResultSet rs) { closeResultSet(rs); closePreparedStatement(ps); closeConnection(conn); }
public static void closeConnection_PreparedStatement(Connection conn,PreparedStatement ps) { closePreparedStatement(ps); closeConnection(conn); } /**关闭资源 closeStatement((st)*/ public static void closeStatement(Statement st) { try { if(st!=null) { st.close(); }else { st = null; } } catch (SQLException e) { e.printStackTrace(); } } /**关闭资源closeResourse(conn,ps)*/ public static void closeResourse(Connection conn,PreparedStatement ps) { closePreparedStatement(ps); closeConnection(conn); } public static void closePreparedStatement(PreparedStatement ps) { try { if(ps!=null) { ps.close(); }else { ps = null; } } catch (SQLException e) { e.printStackTrace(); } } public static void closeConnection(Connection conn) { try { if(conn!=null) { conn.close(); }else { conn = null; } } catch (SQLException e) { e.printStackTrace(); } } /**关闭资源closeResourse(rs)*/ public static void closeResultSet(ResultSet rs) { try { if(rs!=null) { rs.close(); }else { rs = null; } } catch (SQLException e) { e.printStackTrace(); } } }

  account.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>转账页面</title>
</head>
<body>
    <form action="${pageContext.request.contextPath }/account" method="post">
        <table border="1" width="400">
            <tr>
                <td>付款人:</td>
                <td><input type="text" name="fromUser"/></td>
            </tr>
            <tr>
                <td>收款人:</td>
                <td><input type="text" name="toUser"/></td>
            </tr>
            <tr>
                <td>转账金额:</td>
                <td><input type="text" name="money"/></td>
            </tr>
            <tr>
                <td colspan="2"><input type="submit" value="转账"/></td>
            </tr>
        </table>
    </form>
</body>
</html>

   accountService.java的这一行代码注释掉,即转入和转出这2个步骤中是连贯的,不存在异常中断的,转账能成功

    int i=3/0;

 

    accountService.java的这一行代码不注释,转出操作完成,扣款;发生异常被捕捉,事务回滚到转账操作之前,金额不变,转账失败

 int i=3/0;

 

方法2:
            可以将connection对象绑定当前线程上
            jdk中有一个ThreadLocal类,
            ThreadLocal 实例通常是类中的 private static 字段,
            它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。 
ThreadLocal的方法:
		构造:
			new ThreadLocal()
		set(Object value):将内容和当前线程绑定
		Object get():获取和当前线程绑定的内容
		remove():将当前线程和内容解绑
	内部维护了map集合
		map.put(当前线程,内容);
		map.get(当前线程)
		map.remove(当前线程)

  

 

   AccountDao.java

package com.hjh.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import com.hjh.utils.DataSourseUtils;

public class AccountDao {

    //出账
    public void accountFrom( String fromUser, int money) throws Exception {
        Connection conn = null;
        PreparedStatement st  = null;
        
        try {    
            conn = DataSourseUtils.getConnection();
            //编写sql
            String sql="update account set money = money - ?  where name = ?";
            //获取sql语句执行者
            st = conn.prepareStatement(sql);
            //设置sql参数
            st.setInt(1, money);
            st.setString(2, fromUser);
            //执行sql
            int i = st.executeUpdate();
            System.out.println("转出钱成功"+i);    
        } catch (SQLException e) {
            e.printStackTrace();
            throw e;
        }finally {
            DataSourseUtils.closeStatement(st);
        }
    }
    
    //入账
    public void accountTo( String toUser, int money) throws Exception {
        Connection conn = null;
        PreparedStatement st  = null;
        
        try {
            conn = DataSourseUtils.getConnection();
            //编写sql
            String sql="update account set money = money + ?  where name = ?";
            //获取sql语句执行者
            st = conn.prepareStatement(sql);
            //设置sql参数
            st.setInt(1, money);
            st.setString(2, toUser);
            //执行sql
            int i = st.executeUpdate();
            System.out.println("转入钱成功"+i);
        } catch (SQLException e) {
            //e.printStackTrace();
            throw e;
        }    finally {
            DataSourseUtils.closeStatement(st);
        }
    }
}

   AccountService.java

package com.hjh.service;

import com.hjh.dao.AccountDao;
import com.hjh.utils.DataSourseUtils;

public class AccountService {
    public void transterAccount(String fromUser, String toUser, int money) throws Exception  {
        AccountDao dao = new AccountDao();
        
            try {
                //开启事务
                DataSourseUtils.startTransaction();
                
                //转出方,出钱
                dao.accountFrom(fromUser,money);
                //int i=3/0;
                //转入方,进钱
                dao.accountTo(toUser,money);
                
                //事务提交
                DataSourseUtils.commitAndClose();
            } catch (Exception e) {
                e.printStackTrace();
                //事务回滚
                DataSourseUtils.rollbackAndClose();
                throw e;
            }
    }
}

   AccountServlet.java

package com.hjh.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.hjh.service.AccountService;

/**
 * 转账案例
 *         解决中途断电问题:
 *     方法1:
            向下传递参数.注意连接应该在service释放
 */
public class AccountServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //设置编码
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html; charset=utf-8");
        //获取输出流
        PrintWriter w = response.getWriter();
        
        //接收jsp页面传来的三个参数
        String fromUser = request.getParameter("fromUser");
        String toUser = request.getParameter("toUser");
        int money = Integer.parseInt(request.getParameter("money"));
                
        //调用AccountService的transterAccount(fromUser,toUser,money)方法
        try {
            new AccountService().transterAccount(fromUser,toUser,money);
        } catch (Exception e) {
            e.printStackTrace();
            w.println("转账失败");
            return;
        }
        
        //打印提示信息
        w.print("转账成功");
    }

  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}

  DataSourseUtils.java

package com.hjh.utils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;

public class DataSourseUtils {
    //建立连接池ds
    private static ComboPooledDataSource ds =     new ComboPooledDataSource();
    //将connection绑定在当前线程中
    private static ThreadLocal<Connection> tl = new ThreadLocal<>();
    
    //获取数据源
    public static DataSource getDataSourse() {
        return ds;
    }
    
    //获取连接,从当前线程中获取
    public static Connection getConnection() throws Exception {
        Connection conn = tl.get();
        if(conn==null) {
            //第一次获取,创建一个连接和当前线程绑定在一起
            conn =ds.getConnection();
            //绑定
            tl.set(conn);
        }
        return conn;
    }
    
    //获取连接,开启事务
    public static void startTransaction() throws Exception {
        getConnection().setAutoCommit(false);
    }
    
    //事务提交|解除绑定
    public static void commitAndClose() {
        try {
            Connection conn = getConnection();
            //提交事务
            conn.commit();
            //解除绑定
            tl.remove();    
            //释放资源
            closeConnection(conn);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    //事务回滚
        public static void rollbackAndClose() {
            try {
                Connection conn = getConnection();
                //提交事务
                conn.rollback();
                //释放资源
                closeConnection(conn);
                //解除绑定
                tl.remove();    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }    
    
    
    
    //释放资源connection
    public static void closeConnection(Connection conn) {
        try {
            if(conn!=null) {
                conn.close();
                //并和当前线程解绑
                tl.remove();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        conn = null;
    }
    //释放资源Statement
        public static void closeStatement(Statement st) {
            try {
                if(st!=null) {
                    st.close();
                }else {
                    st = null;
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }    
        /**释放资源closePreparedStatement*/
        public static void closePreparedStatement(PreparedStatement ps) {
            try {
                if(ps!=null) {
                    ps.close();
                }else {
                    ps = null;
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }        
    //释放资源Connection ,Statement 
    public static void close2Resourse(Connection conn,Statement st) {
        closeStatement(st);
        closeConnection(conn);
    }
    /**释放资源closeResourse(conn,ps)*/
    public static void close2Resourse(Connection conn,PreparedStatement ps) {
        closePreparedStatement(ps);
        closeConnection(conn);
    }
    /**释放资源closeResourse(rs)*/
    public static void closeResourse(ResultSet rs) {
        try {
            if(rs!=null) {
                rs.close();
            }else {
                rs = null;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }    
}

   web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation=
    "http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1"> <servlet> <servlet-name>AccountServlet</servlet-name> <servlet-class>com.hjh.servlet.AccountServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>AccountServlet</servlet-name> <url-pattern>/account</url-pattern> </servlet-mapping> </web-app>

  account.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>转账页面</title>
</head>
<body>
    <form action="${pageContext.request.contextPath }/account" method="post">
        <table border="1" width="400">
            <tr>
                <td>付款人:</td>
                <td><input type="text" name="fromUser"/></td>
            </tr>
            <tr>
                <td>收款人:</td>
                <td><input type="text" name="toUser"/></td>
            </tr>
            <tr>
                <td>转账金额:</td>
                <td><input type="text" name="money"/></td>
            </tr>
            <tr>
                <td colspan="2"><input type="submit" value="转账"/></td>
            </tr>
        </table>
    </form>
</body>
</html>
DButils:
    1.创建queryrunner
    2.编写sql
    3.执行sql
QueryRunner:
    构造:
        new QueryRunner(DataSource ds):自动事务
        new QueryRunner():手动事务
    常用方法:
        update(Connection conn,String sql,Object ... params):执行的cud操作
        query(Connection conn....):执行查询操作
    注意:
        一旦使用手动事务,调用方法的时候都需要手动传入connection,并且需要手动关闭连接

  accountDao.java

package com.hjh.dao;

import org.apache.commons.dbutils.QueryRunner;
import com.hjh.utils.DataSourseUtils;

public class AccountDao {

    //出账
    public void accountFrom( String fromUser, int money) throws Exception {
        QueryRunner qr = new QueryRunner();
        
            //编写sql
            String sql="update account set money = money - ?  where name = ?";
            //执行sql
            qr.update(DataSourseUtils.getConnection(),sql,money,fromUser);
            System.out.println("转入钱成功");    
    }
    
    //入账
    public void accountTo( String toUser, int money) throws Exception {
        QueryRunner qr = new QueryRunner();
        
        //编写sql
        String sql="update account set money = money + ?  where name = ?";
        //执行sql
        qr.update(DataSourseUtils.getConnection(),sql,money,toUser);
        System.out.println("转出钱成功");    
    }
}

  accountService.java

package com.hjh.service;

import com.hjh.dao.AccountDao;
import com.hjh.utils.DataSourseUtils;

public class AccountService {
    public void transterAccount(String fromUser, String toUser, int money) throws Exception  {
        AccountDao dao = new AccountDao();
        
            try {
                //开启事务
                DataSourseUtils.startTransaction();
                
                //转出方,出钱
                dao.accountFrom(fromUser,money);
                //int i=3/0;
                //转入方,进钱
                dao.accountTo(toUser,money);
                
                //事务提交
                DataSourseUtils.commitAndClose();
            } catch (Exception e) {
                e.printStackTrace();
                //事务回滚
                DataSourseUtils.rollbackAndClose();
                throw e;
            }
    }
}

  accountServlet.java

package com.hjh.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.hjh.service.AccountService;

/**
 * 转账案例
 *         解决中途断电问题:
 *     方法1:
            dbutils
 */
public class AccountServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //设置编码
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html; charset=utf-8");
        //获取输出流
        PrintWriter w = response.getWriter();
        
        //接收jsp页面传来的三个参数
        String fromUser = request.getParameter("fromUser");
        String toUser = request.getParameter("toUser");
        int money = Integer.parseInt(request.getParameter("money"));
                
        //调用AccountService的transterAccount(fromUser,toUser,money)方法
        try {
            new AccountService().transterAccount(fromUser,toUser,money);
        } catch (Exception e) {
            e.printStackTrace();
            w.println("转账失败");
            return;
        }
        
        //打印提示信息
        w.print("转账成功");
    }

  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}

  DataSourceUtils.java

package com.hjh.utils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;

public class DataSourseUtils {
    //建立连接池ds
    private static ComboPooledDataSource ds =     new ComboPooledDataSource();
    //将connection绑定在当前线程中
    private static ThreadLocal<Connection> tl = new ThreadLocal<>();
    
    //获取数据源
    public static DataSource getDataSourse() {
        return ds;
    }
    
    //获取连接,从当前线程中获取
    public static Connection getConnection() throws Exception {
        Connection conn = tl.get();
        if(conn==null) {
            //第一次获取,创建一个连接和当前线程绑定在一起
            conn =ds.getConnection();
            //绑定
            tl.set(conn);
        }
        return conn;
    }
    
    //获取连接,开启事务
    public static void startTransaction() throws Exception {
        getConnection().setAutoCommit(false);
    }
    
    //事务提交|解除绑定
    public static void commitAndClose() {
        try {
            Connection conn = getConnection();
            //提交事务
            conn.commit();
            //解除绑定
            tl.remove();    
            //释放资源
            closeConnection(conn);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    //事务回滚
        public static void rollbackAndClose() {
            try {
                Connection conn = getConnection();
                //提交事务
                conn.rollback();
                //释放资源
                closeConnection(conn);
                //解除绑定
                tl.remove();    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }    
    
    
    
    //释放资源connection
    public static void closeConnection(Connection conn) {
        try {
            if(conn!=null) {
                conn.close();
                //并和当前线程解绑
                tl.remove();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        conn = null;
    }
    //释放资源Statement
        public static void closeStatement(Statement st) {
            try {
                if(st!=null) {
                    st.close();
                }else {
                    st = null;
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }    
        /**释放资源closePreparedStatement*/
        public static void closePreparedStatement(PreparedStatement ps) {
            try {
                if(ps!=null) {
                    ps.close();
                }else {
                    ps = null;
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }        
    //释放资源Connection ,Statement 
    public static void close2Resourse(Connection conn,Statement st) {
        closeStatement(st);
        closeConnection(conn);
    }
    /**释放资源closeResourse(conn,ps)*/
    public static void close2Resourse(Connection conn,PreparedStatement ps) {
        closePreparedStatement(ps);
        closeConnection(conn);
    }
    /**释放资源closeResourse(rs)*/
    public static void closeResourse(ResultSet rs) {
        try {
            if(rs!=null) {
                rs.close();
            }else {
                rs = null;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }    
}

  c3p0-config.xml

<c3p0-config>
    <!-- 默认配置,如果没有指定则使用这个配置 -->
    <default-config>
        <!-- 基本配置 -->
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost/hjh?characterEncoding=UTF-8 </property>
        <property name="user">root</property>
        <property name="password">root</property>
    
        <!--扩展配置-->
        <property name="checkoutTimeout">30000</property>
        <property name="idleConnectionTestPeriod">30</property>
        <property name="initialPoolSize">10</property>
        <property name="maxIdleTime">30</property>
        <property name="maxPoolSize">100</property>
        <property name="minPoolSize">10</property>
        <property name="maxStatements">200</property>
    </default-config> 
    
    
    <!-- 命名的配置 -->
    <named-config name="XXX">
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/xxxx</property>
        <property name="user">root</property>
        <property name="password">1234</property>
        
        
        <!-- 如果池中数据连接不够时一次增长多少个 -->
        <property name="acquireIncrement">5</property>
        <property name="initialPoolSize">20</property>
        <property name="minPoolSize">10</property>
        <property name="maxPoolSize">40</property>
        <property name="maxStatements">20</property>
        <property name="maxStatementsPerConnection">5</property>
    </named-config>
</c3p0-config> 

  account.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>转账页面</title>
</head>
<body>
    <form action="${pageContext.request.contextPath }/account" method="post">
        <table border="1" width="400">
            <tr>
                <td>付款人:</td>
                <td><input type="text" name="fromUser"/></td>
            </tr>
            <tr>
                <td>收款人:</td>
                <td><input type="text" name="toUser"/></td>
            </tr>
            <tr>
                <td>转账金额:</td>
                <td><input type="text" name="money"/></td>
            </tr>
            <tr>
                <td colspan="2"><input type="submit" value="转账"/></td>
            </tr>
        </table>
    </form>
</body>
</html>

 

 

事务总结:

事务的特性:★★★
        ACID
        原子性:事务里面的操作单元不可切割,要么全部成功,要么全部失败
        一致性:事务执行前后,业务状态和其他业务状态保持一致.
        隔离性:一个事务执行的时候最好不要受到其他事务的影响
        持久性:一旦事务提交或者回滚.这个状态都要持久化到数据库中
    不考虑隔离性会出现的读问题★★
        脏读:在一个事务中读取到另一个事务没有提交的数据
        不可重复读:在一个事务中,两次查询的结果不一致(针对的update操作)
        虚读(幻读):在一个事务中,两次查询的结果不一致(针对的insert操作)
    通过设置数据库的隔离级别来避免上面的问题(理解)
        read uncommitted      读未提交    上面的三个问题都会出现
        read committed      读已提交    可以避免脏读的发生
        repeatable read        可重复读    可以避免脏读和不可重复读的发生
        serializable        串行化        可以避免所有的问题
    
    了解
        演示脏读的发生:
            将数据库的隔离级别设置成 读未提交
                set session transaction isolation level read uncommitted;
            查看数据库的隔离级别
                select @@tx_isolation;
        避免脏读的发生,将隔离级别设置成  读已提交
            set session transaction isolation level read committed;
            不可避免不可重复读的发生.
        
        避免不可重复读的发生 经隔离级别设置成 可重复读
            set session transaction isolation level  repeatable read;
            
        演示串行化 可以避免所有的问题
            set session transaction isolation level  serializable;
            锁表的操作.
    四种隔离级别的效率
        read uncommitted>read committed>repeatable read>serializable
    四种隔离级别的安全性
        read uncommitted<read committed<repeatable read<serializable
开发中绝对不允许脏读发生.
        mysql中默认级别:repeatable read
        oracle中默认级别:read committed
    java中控制隔离级别:(了解)
        Connection的api
            void setTransactionIsolation(int level) 
                level是常量

 

原文地址:https://www.cnblogs.com/hejh/p/11088127.html