JDBC高级开发事务
文章目录
1 事务管理
1.1 概述:
事务指的是逻辑上的一组操作,组成这组操作的各个单元要么全都成功,要么全都失败.
事务作用:保证在一个事务中多次操作要么全都成功,要么全都失败.
事务特性(ACID):
原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性(Consistency)事务前后数据的完整性必须保持一致。
隔离性(Isolation)事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多 个并发事务之间数据要相互隔离。
持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发 生故障也不应该对其有任何影响。
1.2 mysql事务操作
sql语句 | 描述 |
---|---|
start transaction; | 开启事务 |
commit; | 提交事务 |
rollback; | 回滚事务 |
MYSQL中可以有两种方式进行事务的管理:
自动提交:MySql默认自动提交。及执行一条sql语句提交一次事务。
手动提交(两种方式):先开启,再提交
方式1:手动提交
start transaction;
update account set money=money-1000 where name='jack';
update account set money=money+1000 where name='rose';
commit;
--回滚
rollback;
方式2:自动提交,通过修改mysql全局变量“autocommit”进行控制
--查看事务
show variables like '%commit%';
-- 设置自动提交的参数为OFF: 0:OFF 1:ON
set autocommit = 0;
扩展:Oracle数据库事务不自动提交
1.3 JDBC事务操作
Connection对象的方法名 | 描述 |
---|---|
conn.setAutoCommit(false) | 开启事务 |
conn.commit() | 提交事务 |
conn.rollback() | 回滚事务 |
1.4 DBUtils事务操作
Connection对象的方法名 | 描述 |
---|---|
conn.setAutoCommit(false) | 开启事务 |
new QueryRunner() | 创建核心类,不设置数据源(手动管理连接) |
query(conn , sql , handler, params ) 或update(conn, sql , params) | 手动传递连接 |
DbUtils.commitAndClose(conn) 或DbUtils.rollbackAndClose(conn) | 提交并关闭连接回滚并关闭连接 |
1.5 分层思想
开发中,常使用分层思想
不同的层次结构分配不同的解决过程,各个层次间组成严密的封闭系统
不同层级结构彼此平等
分层的目的:解耦、可维护性、可扩展性、可重用性
不同层次,使用不同的包表示
com.itheima 公司域名倒写
com.itheima.dao dao层 (Data access Object)
com.itheima.service service层
com.itheima.domain javabean
com.itheima.utils 工具
1.6 ThreadLocal类
java.lang.ThreadLocal 该类提供了线程局部 (thread-local) 变量,用于在当前线程中共享数据。
如图:
2. 转账案例分层代码实现(事务)
2.1 web层:
package com01.itheima.web;
import java.util.Scanner;
import com02.itheima.service.AccountService;
/*
* Web层:
* 1:获取用户输入的数据
* 2:调用Service层方法,并传入用户输入的数据
*/
public class AccountWeb {
@SuppressWarnings("resource")
public static void main(String[] args) {
//1:获取用户输入的数据
Scanner sc = new Scanner(System.in);
System.out.println("请输入源账户名:");
String fromName = sc.nextLine();
System.out.println("请输入目标账户名:");
String toName = sc.nextLine();
System.out.println("请输入转账金额:");
double money = Double.parseDouble(sc.nextLine());
//2:调用Service层方法,并传入用户输入的数据
AccountService service = new AccountService();
boolean bl = service.transAccount(fromName,toName,money);
if(bl){
System.out.println("转账成功!");
}else{
System.out.println("转账失败!");
}
}
}
2.2 service层:
package com02.itheima.service;
import java.sql.Connection;
import java.sql.SQLException;
import com03.itheima.dao.AccountDao;
import com04.itheima.utils.ConnectionManager;
import com04.itheima.utils.MyC3P0Utils;
public class AccountService {
public boolean transAccount(String fromName, String toName, double money){
try {
AccountDao dao = new AccountDao();
//开启事务
ConnectionManager.begin();
//判断余额
double findMoney = dao.findMoney(fromName);
if(findMoney >= money){
int i = dao.fromAccount(fromName, money);
int j = dao.toAccount(toName, money);
if(i > 0 && j > 0){
//t提交事务
ConnectionManager.commit();
return true;
}
}else{
System.out.println("余额不足");
}
} catch (Exception e) {
try {
//回滚事务
ConnectionManager.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
return false;
}
}
2.3 dao层:
package com03.itheima.dao;
import java.sql.Connection;
import java.sql.SQLException;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import com04.itheima.utils.ConnectionManager;
import com04.itheima.utils.MyC3P0Utils;
/*
* 优化的目的:
* 1:不让Service层给Dao层传Connection对象,让Dao层自己去获取Connection对象
* 2:必须保证Dao层Connection和Serivice的Connection是同一个
*/
public class AccountDao {
public int fromAccount(String fromName, double money) throws SQLException{
QueryRunner qr = new QueryRunner();
int i = qr.update(ConnectionManager.getConnection(),"update account set money = money - ? where aname = ?",money,fromName);
return i;
}
public int toAccount( String toName, double money) throws SQLException{
QueryRunner qr = new QueryRunner();
int i = qr.update(ConnectionManager.getConnection(),"update account set money = money + ? where aname = ?",money,toName);
return i;
}
public double findMoney(String fromName) throws SQLException{
QueryRunner qr = new QueryRunner();
double money = (double)qr.query(ConnectionManager.getConnection(),"select money from account where aname = ?", new ScalarHandler(),fromName);
return money;
}
}
2.4 Utils层:
MyC3P0Utils:
package com04.itheima.utils;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
/*
* C3P0连接池的工具类:
* 创建连接池:
* 1:准备一个配置文件:xxx.xml文件 (写四大信息)
* 2:创建CombopooledDataSource类对象
*/
public class MyC3P0Utils {
/*
* 1:在bin目录中找到xml文件,解析xml文件
* 2:获取四大信息:
* 1)注册驱动
* 2)创建一个连接池,并存放连接15个连接
*/
private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
//封装一个方法,对外提供dataSource
public static DataSource getDataSource(){
return dataSource;
}
//封装一个方法,让别人从连接池中获取一个连接
public static Connection getConnection() throws SQLException{
return dataSource.getConnection();
}
}
连接管理器:ConnectionManager:
package com04.itheima.utils;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import org.apache.commons.dbutils.DbUtils;
public class ConnectionManager {
// 定义一个集合,这个集合是存Connection对象
// private static HashMap<Thread, Connection> map= new HashMap<>();
private static ThreadLocal<Connection> tl = new ThreadLocal<>();
public static Connection getConnection() throws SQLException {
// 第一个调用该 方法,集合中内容是空的
// Connection conn = map.get(Thread.currentThread());
Connection conn = tl.get();
if (conn == null) {
// 如果集合中没有连接,则获取一个连接存入Map
conn = MyC3P0Utils.getConnection();
// map.put(Thread.currentThread(), conn);
tl.set(conn);
}
return conn;
}
// 开启事务
public static void begin() throws SQLException {
ConnectionManager.getConnection().setAutoCommit(false);
}
// 提交事务
public static void commit() throws SQLException {
DbUtils.commitAndClose(ConnectionManager.getConnection());
}
// 回滚事务
public static void rollback() throws SQLException {
DbUtils.rollbackAndClose(ConnectionManager.getConnection());
}
}
3. 事务总结:
3.1 并发访问问题
如果不考虑隔离性,事务存在3中并发访问问题。
-
脏读:一个事务读到了另一个事务未提交的数据.
-
不可重复读:一个事务读到了另一个事务已经提交(update)的数据。引发另一个事务,在事务中的多次查询结果不一致。
-
虚读 /幻读:一个事务读到了另一个事务已经提交(insert)的数据。导致另一个事务,在事务中多次查询的结果不一致。
3.2 隔离级别:解决问题
l数据库规范规定了4种隔离级别,分别用于描述两个事务并发的所有情况。
-
read uncommitted 读未提交,一个事务读到另一个事务没有提交的数据。
a) 存在:3个问题(脏读、不可重复读、虚读)。
b) 解决:0个问题
-
read committed 读已提交,一个事务读到另一个事务已经提交的数据。
a) 存在:2个问题(不可重复读、虚读)。
b) 解决:1个问题(脏读)
-
repeatable read :可重复读,在一个事务中读到的数据始终保持一致,无论另一个事务是否提交。
a) 存在:1个问题(虚读)。
b) 解决:2个问题(脏读、不可重复读)
-
serializable 串行化,同时只能执行一个事务,相当于事务中的单线程。
a) 存在:0个问题。
b) 解决:3个问题(脏读、不可重复读、虚读)
安全和性能对比
安全性:serializable > repeatable read > read committed > read uncommitted
性能 : serializable < repeatable read < read committed < read uncommitted
常见数据库的默认隔离级别:
MySql:repeatable read
Oracle:read committed
3.3 演示
- 查询数据库的隔离级别
show variables like '%isolation%';
或
select @@tx_isolation;
- 设置数据库的隔离级别
set session transaction isolation level 级别字符串
级别字符串:read uncommitted、read committed、repeatable read、serializable
例如:set session transaction isolation level read uncommitted;
数据库的默认隔离级别:
MySql:repeatable read
Oracle:read committed
3.3 演示
- 查询数据库的隔离级别
show variables like '%isolation%';
或
select @@tx_isolation;
- 设置数据库的隔离级别
set session transaction isolation level 级别字符串
级别字符串:read uncommitted、read committed、repeatable read、serializable
例如:set session transaction isolation level read uncommitted;