JDBC常见知识点(三)
导读{
}
【1】事务实例: 银行转账(经典题目)
【1.1】如何使用JDBC处理事务?
(1)在数据库操作中,一项事务是由一条或多条操作数据库的SQL语句组成的一个不可分割的工作单元
(2)只有当事务中的所有操作都正常完成,整个事务才能被提交到数据库中,如果有一项操作没有完成,则整个事务会被撤销
例如 银行的转账业务
【1.2】通过 Connection 接口中提供了三个方法来针对 JDBC 处理事物。
其中有三个方法:
setAutoCommit(boolean autoCommit)// 设置自动提交 我们需要将这个方法设置为 false 不自动提交
//开启事务
public static void startTransaction(){
try {
//获取连接
Connection conn = getConnection();
//开启事务
conn.setAutoCommit(false);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
commit() // 提交 当我们的事务完成的时候,提交
//提交事务
public static void commit(){
try {
//从集合tc中得到一个连接
Connection conn = tc.get();
if(conn != null){
conn.commit();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
rollback() // 回滚 当我们的事务出现异常的时候,回滚
//回滚事务
public static void rollback(){
try {
//从集合tc中得到一个连接
Connection conn = tc.get();
if(conn != null){
conn.rollback();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
【1.3】银行转账的业务逻辑
用户 zs 要向 ls 的账户上转200 元钱,该转账业务需要分成两步:
第一步:zs 的账户上需要减少 200 元钱
第二步:ls 的账户上需要增加 200 元钱
这两步操作需要都成功,事务才成功。也就是说,要么都执行成功,要么都没有执行成功。
那么就会出现两种结果,一种是commit() 执行成功,要么就需要执行 rollback () ;
[1.4]介绍ThreadLocaL类
(1)为什么使用ThreadLocaL 类?
Web应用程序,
多用户并发访问
。一个客户端访问时,服务器启动一个线程,比如转账,在这个线程中独立完成这个事务操作,另外一个客户端也来访问时,服务器也启动另外一个线程,在另外一个线程中独立完成转账事
务操作。
要想多线程中,每个事务执行成功,必须保证每个线程有独立的Connection对象。
(2)ThreadLocaL类介绍:
ThreadLocaL
类:在一个线程中记录本地变量。我们可以将一个Connection对象放到这个ThreadLocaL集合中,只要是这个线程中的对象都可以共享这个Connection对象,线程结束后,删除这个Connection对象。这样
保证:一个事务一个连接。
TreadLoacl本质上是一个Map,键是当前线程对象,值可以是我们的当前连接对象。
//创建一个ThreadLoacl对象,用当前线程作为key
private static ThreadLocal<Connection> tc = new ThreadLocal<Connection>();
代码演示:在使用数据源(DataSource )时,如果是业务处理,使用C3P0 时,不需要拿 数据源对象。
Java Web应用开发的三层架构:
1、表示层(Web层):jsp和servlet
2、业务逻辑层(Service层):XXXService,事务操作是在service层完成的。
3、数据访问层(dao层):XXXDao
按照编写正常代码演示:
bean-->Utils ---> Dao--->service--->Servlet
先看下数据库表: test数据库中 account 表 三个字段 id 主键自动递增,name 姓名 , money 账户的金额 double 类型
Account.java
package cn.edu.aynu.rjxy.bean;
public class Account {
private int id;
private String name;
private double money;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
JDBCUtils.java
package cn.edu.aynu.rjxy.utils;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class JDBCUtils {
//创建一个ThreadLoacl对象,用当前线程作为key
private static ThreadLocal<Connection> tc = new ThreadLocal<Connection>();
//从c3p0-config.xml文件中读取默认配置创建数据库连接池对象
private static DataSource ds = new ComboPooledDataSource();
//得到数据库连接池对象
public static DataSource getDataSource(){
return ds;
}
//从连接池中获取连接
public static Connection getConnection() throws Exception{
//从集合tc中获取一个Connection
Connection conn = tc.get();
if(conn == null){
conn = ds.getConnection();
//将conn存放到集合tc中
tc.set(conn);
System.out.println("首次创建连接:"+conn);
}
return conn;
}
//开启事务
public static void startTransaction(){
try {
//获取连接
Connection conn = getConnection();
//开启事务
conn.setAutoCommit(false);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//提交事务
public static void commit(){
try {
//从集合tc中得到一个连接
Connection conn = tc.get();
if(conn != null){
conn.commit();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//回滚事务
public static void rollback(){
try {
//从集合tc中得到一个连接
Connection conn = tc.get();
if(conn != null){
conn.rollback();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//关闭连接释放资源
public static void close(){
//从集合tc中得到一个连接
Connection conn = tc.get();
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
//从集合tc中移除连接对象
tc.remove();
conn = null;
}
}
}
}
AccountDao,java
package cn.edu.aynu.rjxy.dao;
import java.sql.Connection;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import cn.edu.aynu.rjxy.bean.Account;
import cn.edu.aynu.rjxy.utils.JDBCUtils;
public class AccountDao {
//根据账户名查询账户信息
public Account find(String name) throws Exception{
QueryRunner run = new QueryRunner(); // 处理事务,不需要传 数据源对象
Connection conn = JDBCUtils.getConnection();
System.out.println("find()得到连接:"+conn);
String sql = "select * from account where name=?";
Account account = (Account)run.query(conn, sql, new BeanHandler(Account.class), new Object[]{name});
return account;
}
//转账(可能转入也可能转出)
public void update(Account account) throws Exception {
QueryRunner run = new QueryRunner();
Connection conn = JDBCUtils.getConnection();
System.out.println("update()得到连接:"+conn);
String sql = "update account set money=? where name=?";
run.update(conn, sql, new Object[]{account.getMoney(),account.getName()});
}
}
AccountService.java 转账的业务逻辑
package cn.edu.aynu.rjxy.service;
import cn.edu.aynu.rjxy.bean.Account;
import cn.edu.aynu.rjxy.dao.AccountDao;
import cn.edu.aynu.rjxy.utils.JDBCUtils;
public class AccountService {
//转账事务
public static void transfer(String fromName,String toName,double balance){
try {
//开启事务
JDBCUtils.startTransaction();
AccountDao dao = new AccountDao();
//查询两个账户的金额
Account accountFrom = dao.find(fromName);
Account accountTo = dao.find(toName);
//判断是否可以转账
if(balance<accountFrom.getMoney()){
//可以转账
//设置转出账户的金额
accountFrom.setMoney(accountFrom.getMoney()-balance);
//设置转入账户的金额
accountTo.setMoney(accountTo.getMoney()+balance);
//执行数据库更改操作
dao.update(accountFrom);
dao.update(accountTo);
//提交事务
JDBCUtils.commit();
System.out.println("事务提交成功");
}else{
System.out.println("转出账户金额不足");
}
} catch (Exception e) {
//回滚事务
JDBCUtils.rollback();
System.out.println("事务提交失败");
e.printStackTrace();
}finally{
//释放资源
JDBCUtils.close();
}
}
}
测试转账是否能够完成
import cn.edu.aynu.rjxy.service.AccountService;
public class TestTransfer {
/**
* @param args
*/
public static void main(String[] args) {
//实现从zs账户上转账200到ls账户上
AccountService.transfer("zs", "ls", 200);
}
}
演示事务处理成功情况:
演示错误情况,将 zs 用户名写成 zss
可以看到,事务没有执行成功。
源码:https://github.com/gengzi/files/tree/master/%E9%93%B6%E8%A1%8C%E8%BD%AC%E8%B4%A6%EF%BC%88%E6%A1%88%E4%BE%8B%EF%BC%89
数据库:
https://github.com/gengzi/files/tree/master/%E9%93%B6%E8%A1%8C%E8%BD%AC%E8%B4%A6%EF%BC%88%E6%A1%88%E4%BE%8B%EF%BC%89