数据库事务:在数据库中所谓事务是指一组逻辑操作单元,使数据从一种状态转换到另一种状态。为确保数据库中的数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这些单元中一部分操作失败,整个事务全部视为错误,所有从起始点开始以后的操作应全部回退到开始状态。
事务的操作:先定义开始一个事务,然后对数据进行修改操作,这时如果提交(commit),这些修改就永久的保存下来,
*如果回退(rollback)数据库管理系统将放弃所做的所有修改操作,而回到开始事务时的状态。事务的(ACID)属性:
——原子性(Atomicity):是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
——一致性(Consistency):事务必须使数据库从一个一致性状态转变成另外一个一致性状态。
——隔离性(Lsolation):指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的并发执行的各个事务不能相互干扰。
——持久性(Durability):指一个事务一旦被提交,它对数据库中的数据的改变就是永久性的接下来的其他操作和数据库的故障不应该对其有任何影响。
JDBC的事务:指构成单个逻辑工作单元的操作集合。
事务处理:保证所有的事务都作为一个工作单元来执行,即使出现了故障都不能改变这种执行方式,当在一个事务中执行多个操作时要么所有的事务都被提交,要么整个事务回滚到最初的状态。
当一个连接对象被创建时默认情况下是自动提交事务,每次执行一个SQL语句时,如果执行成功,就会向数据库自动提交,而不能回滚,为了让多个SQL语句作为一个事务执行,可使用一下步骤:
- ——调用Connection对象的setAutoCommit(false),以取消自动提交事务。
- ——在所有的SQL语句都成功执行后,调用commit方法提交事务。
- ——在出现异常时,调用rollback方法,事务回滚。
- ——若此时Conneciton没有关闭,则需要恢复其自动提交状态。
注意:若使用的数据库引擎不是innodb,则事务无法回滚。而mysql默认的数据库引擎是MyISAM,所以第一次使用事务则需要更改数据表的引擎。
事务的隔离级别:对于同时运行的多个事务,当这些事务访问数据库中相同的数据时,如果没有采取必要的隔离机制,就会导致各种并发问题:
- ——脏读:对于两个事务T1、T2,T1读取的已经被T2更新但没有提交的字段之后,若T2回滚,T1读取的内容就是临时且无效的。
- ——不可重复读:对于两个事务T1、T2,T1读取的一个字段,T2更新了该字段之后,T1再次读取同一个字段,值就不一样了。
- ——幻读:对于两个事务T1、T2,T1从表中读取一个字段,然后T2在表中插入一些行,之后T1再次读取同一张表,就会多出几行。
数据库事务的隔离性:数据库系统必须具有隔离并发运行各个事务的能力,使他们不会相会影响,避免各种并发问题。
隔离级别:一个事务和其他事务隔离的程度。隔离级别越高,数据一致性就越好,但并发性越弱。
数据库提供的四种事务隔离级别。
- ——read uncommitted(读未提交数据)
- ——read committed(读已提交数据)
- ——repeatable read(可重复读)
- ——serialized(串行化)
- Oracle支持2种事务隔离级别read uncommitted、serialized,默认是read uncommitted
- Mysql支持4种隔离级别,默认repeatable read。
- 设置事务隔离级别:
- 使用sql语句:select @@tx_isolation查看当前隔离级别
- ——设置当前的mysql连接的隔离级别:set transaction isolation level read commited;
- ——设置数据库系统的全局隔离级别:set global transaction isolation level read commited;
- 使用代码:
conn.setTransactionIsolation(conn.TRANSACTION_READ_COMMITTED);
代码实例
public class Transaction_10 {
/**
* 1.Tom给Jerry汇款500元
*注意:如果多个操作,每个操作使用的都是自己单独的连接,则无法保证事务。
*/
@Test
public void testTransaction(){
DAO1_7 dao = new DAO1_7();
String sql = "update user set balance = balance - 500 where id = 1";
dao.update(sql);
//出现异常时,Tom的钱被扣掉500,但是Jerry的钱没有增加500,
int number = 10 / 0;
System.out.println(number);
sql = "update user set balance = balance + 500 where id = 2";
dao.update(sql);
}
/**
* 2.修改以上(1)方法,使用同一个连接
* @param conn
* @param sql
* @param objects
*/
public void update(Connection conn, String sql, Object ...objects){
PreparedStatement preparedstatement = null;
try {
preparedstatement = conn.prepareStatement(sql);
for(int i = 0; i < objects.length; i++){
preparedstatement.setObject(i + 1, objects[i]);
}
preparedstatement.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}finally{
TestTools.release(preparedstatement,null);
}
}
/**
* 2_1.测试以上方法
*/
@Test
public void test(){
Connection conn = null;
try {
conn = TestTools.getConnection();
//开始事务,在开始之前要关闭数据库默认自动提交
conn.setAutoCommit(false);
String sql = "update users set balance = balance - 500 where id = 1";
update(conn,sql);
//出现异常时,事务回滚到之前的状态
int number = 10 / 0;
System.out.println(number);
sql = "update users set balance = balance + 500 where id = 2";
update(conn,sql);
//结束,就提交事务
conn.commit();
} catch (Exception e) {
e.printStackTrace();
//发生异常,事务回滚
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}finally{
//设置事务提交方式为自动提交:
try {
conn.setAutoCommit(true);
} catch (SQLException e) {
e.printStackTrace();
}
//关闭连接
TestTools.release(null, conn);
}
}
/**
* 3.返回某条记录的某一个字段的值或一个统计的值(一共有多少条记录等。),即:确定的某一行的某个字段的值,
*或者计算的值,3.、3_1.方法处理数据并设置隔离级别,3_2.方法获取数据以测试不同的隔离级别下获取的数据的差异
* @param sql
* @param objects
* @return
*/
public <E> E getForValue(String sql, Object ... objects){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
//1.得到结果集:该结果集应该只有一行,且只有一列
conn = TestTools.getConnection();
//设置隔离级别:读未提交,该隔离级别即使事务未提交也可获得更新但未提交的的数据。获取数据结果:500
//conn.setTransactionIsolation(conn.TRANSACTION_READ_UNCOMMITTED);
//设置隔离级别:读已提交,该隔离级别若事务未提交则获取更新之前的数据,获取数据结果:1000
conn.setTransactionIsolation(conn.TRANSACTION_READ_COMMITTED);
ps = conn.prepareStatement(sql);
for(int i = 0; i < objects.length; i++){
ps.setObject(i + 1, objects[i]);
}
rs = ps.executeQuery();
if(rs.next()){
//2.取得结果
return (E)rs.getObject(1);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
TestTools.release(rs, ps, conn);
}
return null;
}
/**
* 3_1.测试以上方法设置事务的隔离级别,使用update方法更新数据,使balance数据减500
*/
@Test
public void testTransactionIsolationUpdate(){
Connection conn = null;
try {
conn = TestTools.getConnection();
//关闭自动提交
conn.setAutoCommit(false);
String sql = "update users set balance = balance - 500 where id = 1";
update(conn, sql);
//提交事务
conn.commit();
} catch (Exception e) {
e.printStackTrace();
}finally{
TestTools.release(null, conn);
}
}
/**
* 3_2.使用getForValue()方法,查询被更新但未提交的数据
*/
@Test
public void testTransactionIosationRead(){
String sql = "select balance from users where id = 1";
Integer balance = getForValue(sql);
System.out.println(balance);
}
}