银行转账!张三转1000块到李四的账户,这其实需要两条SQL语句:
给张三的账户减去1000元
给李四的账户加上1000元
如果在第一条SQL语句执行成功后,在执行第二条SQL语句之前,程序被中断了(可能是抛出了某个异常,也可能是其他什么原因),那么李四的账户没有加上10000元,而张三却减去了10000元,这肯定是不行的!
你现在可能已经知道什么是事务了吧!事务中的多个操作,要么同时成功,要么同时失败!不可能存在成功一半的情况!也就是说给张三的账户减去10000元如果成功了,那么给李四的账户加上10000元的操作也必须是成功的;否则给张三减去10000元,以及给李四加上10000元都是失败的!
在默认情况下,每执行一条增、删、改SQL语句,都是一个单独的事务。如果需要在一个事务中包含多条SQL语句,那么需要开启事务和结束事务
结束事务:
Connection的三个方法与事务相关:
setAutoCommit(boolean)
:设置是否为自动提交事务,如果true(默认值就是true)表示自动提交,也就是每条执行的SQL语句都是一个单独的事务,如果设置false,那么就相当于开启了事务了commit()
:提交结束事务rollback()
:回滚结束事务事务一致性代码示例
测试用表,用于新建表
drop table ACCOUNT cascade constraints;
/*==============================================================*/
/* Table: ACCOUNT */
/*==============================================================*/
create table ACCOUNT (
id NUMBER(10),
name VARCHAR(20),
sal NUMBER(10)
);
代码
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.InputStream;
import java.io.Reader;
import java.sql.*;
public class TestJDBC {
public static void main(String[] args) {
/*定义url*/
String url = "jdbc:oracle:thin:@127.0.0.1:1521:orcl"; //@后面是主机ip+端口号+实例名,在此均采用默认
/*用户名与密码*/
String username ="scott" ;
String password ="tiger";
/*获取连接Connection*/
Connection conn = null ;
/*定义数据库的sql执行对象*/
// Statement stmt = null ;
PreparedStatement pstmt = null ;
PreparedStatement pstmt1 = null ;
/*定义查询语句返回的结果集*/
ResultSet rs = null ;
/*更新操作*/
String sql = "update account t set t.sal = t.sal- ? where t.id = ?" ;
String sql1 = "update account t set t.sal = t.sal+ ? where t.id = ?" ;
try {
/*注册驱动*/
Class.forName("oracle.jdbc.OracleDriver");
conn = DriverManager.getConnection(url, username, password);
/*开启事务*/
conn.setAutoCommit(false);
pstmt = conn.prepareStatement(sql) ;
pstmt1 = conn.prepareStatement(sql1) ;
pstmt.setInt(1,10000);
pstmt.setInt(2,1);
int count = pstmt.executeUpdate();
System.out.println(count);
/*模拟异常*/
if (true)
throw new Exception() ;
pstmt1.setInt(1,1000);
pstmt1.setInt(2,2);
int count1 = pstmt1.executeUpdate();
/*如果没有异常,事务提交*/
conn.commit();
} catch (Exception e) {
e.printStackTrace();
try {
/*异常情况下事务回滚*/
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}finally { /*关闭资源*/
try {
if (rs!=null)//防止空指针
rs.close();
if (conn!=null) //防止空指针
conn.close();
if (pstmt!=null)//防止空指针
pstmt.close();
if (pstmt1!=null)//防止空指针
pstmt1.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
时间 | 转账事务A | 取款事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 取出500元把余额改为500元 | |
T5 | 查看账户余额为500元(脏读) |
时间 | 统计金额事务A | 转账事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 统计总存款数为10000元 | |
T4 | 新增一个存款账户,存款为100元 | |
T5 | 提交事务 | |
T6 | 再次统计总存款数为10100元 |
时间 | 取款事务A | 转账事务B |
---|---|---|
T1 | 开始事务 | |
T | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 取出100元,把余额改为900元 | |
T6 | 提交事务 | |
T7 | 查询账户余额为900元(与T4读取的一不一致) |
不可重复读与虚读有些相似,都是两次查询的结果不同。后者是查询到了另一个事务已提交的新插入数据,而前者是查询到了另一个事务已提交的更新数据
隔离级别 | 脏读 | 不可重复读 | 虚读 |
---|---|---|---|
READ UNCOMMITTED | 允许 | 允许 | 允许 |
READ COMMITTED | 不允许 | 允许 | 允许 |
REPEATABLE READ | 不允许 | 不允许 | 允许 |
SERIALIZABLE | 不允许 | 不允许 | 不允许 |
1 SERIALIZABLE(串行化)
当数据库系统使用SERIALIZABLE隔离级别时,一个事务在执行过程中完全看不到其他事务对数据库所做的更新。当两个事务同时操作数据库中相同数据时,如果第一个事务已经在访问该数据,第二个事务只能停下来等待,必须等到第一个事务结束后才能恢复运行。因此这两个事务实际上是串行化方式运行。
2 REPEATABLE READ(可重复读)
当数据库系统使用REPEATABLE READ隔离级别时,一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,但是不能看到其他事务对已有记录的更新。
3 READ COMMITTED(读已提交数据)
当数据库系统使用READ COMMITTED隔离级别时,一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,而且还能看到其他事务已经提交的对已有记录的更新。
4 READ UNCOMMITTED(读未提交数据)
当数据库系统使用READ UNCOMMITTED隔离级别时,一个事务在执行过程中可以看到其他事务没有提交的新插入的记录,而且还能看到其他事务没有提交的对已有记录的更新。
你可能会说,选择SERIALIZABLE,因为它最安全!没错,它是最安全,但它也是最慢的!而且也最容易产生死锁。四种隔离级别的安全性与性能成反比!最安全的性能最差,最不安全的性能最好!
MySQL的默认隔离级别为REPEATABLE READ
Oracle数据库支持READ COMMITTED 和 SERIALIZABLE这两种事务隔离级别。不支持脏读
Oracle的默认隔离级别是READ COMMITTED
con. setTransactionIsolation(int level)
参数可选值如下:
Connection.TRANSACTION_READ_UNCOMMITTED
Connection.TRANSACTION_READ_COMMITTED
Connection.TRANSACTION_REPEATABLE_READ
Connection.TRANSACTION_SERIALIZABLE
事务总结:
事务的特性:ACID
事务开始边界与结束边界:开始边界(con.setAutoCommit(false))
,结束边界(con.commit()或con.rollback())
事务的隔离级别: READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE。多个事务并发执行时才需要考虑并发事务。
用池来管理Connection,这可以重复使用Connection,有了池,所以我们就不用自己来创建Connection,而是通过池来获取Connection对象。当使用完Connection后,调用Connection的close()方法也不会真的关闭Connection,而是把Connection“归还”给池。池就可以再利用这个Connection对象了
JDBC数据库连接池接口(DataSource)
Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商可以让自己的连接池实现这个接口。这样应用程序可以方便的切换不同厂商的连接池!
DBCP是Apache提供的一款开源免费的数据库连接池!
Hibernate3.0之后不再对DBCP提供支持!因为Hibernate声明DBCP有致命的缺欠!DBCP因为Hibernate的这一毁谤很是生气,并且说自己没有缺欠
导包下载地址
driverClassName=oracle.jdbc.OracleDriver
url=jdbc:oracle:thin:@127.0.0.1:1521:orcl
username=scott
password=tiger
initialSize=10
maxActive=5
maxIdle=5
minIdle=3
maxWait=-1
initialSize :连接池启动时创建的初始化连接数量(默认值为0)
maxActive :连接池中可同时连接的最大的连接数(默认值为8)
maxIdle:连接池中最大的空闲的连接数,超过的空闲连接将被释放,如果设置为负数表示不限制(默认为8个)
minIdle:连接池中最小的空闲的连接数
maxWait :最大等待时间,当没有可用连接时,连接池等待连接释放的最大时间,超过该时间限制会抛出异常,如果设置-1表示无限等待(默认为无限,调整为60000ms,避免因线程池不够用,而导致请求被无限制挂起)
import org.apache.commons.dbcp.BasicDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.InputStream;
import java.io.Reader;
import java.sql.*;
import java.util.Properties;
public class TestJDBC {
public static void main(String[] args) {
/* *//*定义url*//*
String url = "jdbc:oracle:thin:@127.0.0.1:1521:orcl"; //@后面是主机ip+端口号+实例名,在此均采用默认
*//*用户名与密码*//*
String username ="scott" ;
String password ="tiger";*/
/*定义数据库的sql执行对象*/
PreparedStatement pstmt = null ;
/*定义输入流*/
InputStream in =null ;
/*定义配置文件*/
Properties prop = null ;
/*定义连接*/
Connection conn = null ;
/*更新操作*/
String sql = "update account t set t.sal = t.sal- ? where t.id = ?" ;
try {
/*读取连接池配置文件*/
in = TestJDBC.class.getClassLoader().getResourceAsStream("dbcp.properties");
/*创建配置文件对象*/
prop = new Properties( );
/*加载配置文件*/
prop.load(in);
/*创建数据库连接池*/
DataSource ds = BasicDataSourceFactory.createDataSource(prop);
/*从连接池获得连接*/
//conn = DriverManager.getConnection(url, username, password);//pass
conn = ds.getConnection() ;
/*预编译执行sql语句*/
pstmt = conn.prepareStatement(sql) ;
pstmt.setInt(1,100);
pstmt.setInt(2,1);
int count = pstmt.executeUpdate();
System.out.println(count);
} catch (Exception e) {
e.printStackTrace();
}finally { /*关闭资源*/
try {
if (in!=null)//防止空指针
in.close();
if (conn!=null) //防止空指针
conn.close();
if (pstmt!=null)//防止空指针
pstmt.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}