事务(以转账为例)
事务:
就是一件完整的事情,包含多个操作单元,这些操作要么全部成功,要么全部失败.
例如:转账,包含转出操作和转入操作.
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是常量