事务(转账实例)


事务(以转账为例)

 

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

 

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是常量

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM