事务概念
对多个SQL指令进行操作,只有这些指令都成功时,才能认为整个操作是完成的,这样的操作称为”事务操作“。如果一个SQL指令操作失败,之前的各个操作都要取消,这种取消动作称为”回滚 (rollback)“。
JDBC中的事务操作是基于同一个数据连接的,各个连接之间相互独立。当数据连接断开后,一个事务就结束了。事务操作的方法都位于java.sql.Connection接口中。
JDBC事务操作默认是自动提交的,一条对数据库的更新表达式代表一项事务操作,操作成功后,系统会自动调用commit()来提交,否则会调用rollback()来回滚。如果想取消自动提交可以调用setAutoCommit(false),回复自动提交则可以设置其实参为true。取消自动提交可以灵活地将多个表达式作为一个事务,然后使用commit()来提交,如果出现异常可以使用rollback()来回滚。
实例
import java.sql.*; public class Main { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Connection con = null; Statement smt; try{ Class.forName("com.mysql.jdbc.Driver");//加载JDBC MYSQL驱动 con=DriverManager.getConnection("jdbc:mysql://localhost:3306/javatemp","root","root"); //建立数据库连接 con.setAutoCommit(false);//取消事务的自动提交 //System.out.println(con.getTransactionIsolation()); //获取事务默认加锁方式 //con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);//设置事务加锁方式 //System.out.println(con.getTransactionIsolation());//获取事务当前加锁方式 smt=con.createStatement();//创建Statement对象 smt.executeUpdate("insert into users(username) values('beston5')");//执行SQL命令 con.rollback();//回滚 smt.executeUpdate("insert into users(username) values('beston6')"); con.commit();//提交 con.setAutoCommit(true);//恢复事务的自动提交 PreparedStatement ps=con.prepareStatement("insert into users(username) values(?)");//使用prepareStatement对象操作数据指令 ps.setString(1,"oseye6");//设置占位符值 ps.executeUpdate(); //执行SQL命令 ResultSet res=smt.executeQuery("select * from users");//执行SQL命令并获得结果集 while(res.next()){//处理结果集 System.out.println("userid:"+res.getInt("userid")+"\t"+"username:"+res.getString("username")); } res.close();//关闭结果集连接的数据并释放JDBC资源 }catch(Exception ex){ ex.printStackTrace();//打印异常信息 }finally{ if(con!=null){ try{ con.close();//关闭数据库连接 }catch(Exception ex){ ex.printStackTrace(); } } } } }
上面注释的应该很详细了,"beston5"不会插入到数据库,"beston6"和"oseye6"将会插入到数据中。
JDBC 对数据库的加锁机制
先来看三个概念:
- 脏数据读写(dirty reads):当一个事务修改了某一数据行的值而未提交时,另一事务读取了此行值。倘若前一事务发生了回滚,则后一事务将得到一个无效的值(脏数据)。
- 重复读写(repeatable reads):当一个事务在读取某一数据行时,另一事务同时在修改此数据行。则前一事务在重复读取此行时将得到一个不一致的值。
- 影象读写(phantomreads):当一个事务在某一表中进行数据查询时,另一事务恰好插入了满足了查询条件的数据行。则前一事务在重复读取满足条件的值时,将得到一个额外的“影象“值。
JDBC API支持事务对数据库的加锁,并且提供了5种操作支持:
static int TRANSACTION_NONE= 0; //禁止事务操作和加锁。 static int TRANSACTION_READ_UNCOMMITTED= 1; //允许脏数据读写、重复读写和影象读写 static int TRANSACTION_READ_COMMITTED= 2;//禁止脏数据读写,允许重复读写和影象读写 static int TRANSACTION_REPEATABLE_READ= 4;//禁止脏数据读写和重复读写,允许影象读写 static int TRANSACTION_SERIALIZABLE= 8;//禁止脏数据读写、重复读写和允许影象读写
上述归类可以分为两种加锁密度,表加锁和行加锁。其中最后一项为表加锁,其余3~4项为行加锁。
如上代码中,取消
//System.out.println(con.getTransactionIsolation()); //获取事务默认加锁方式 //con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);//设置事务加锁方式 //System.out.println(con.getTransactionIsolation());//获取事务当前加锁方式
的注释,将会看到默认枷锁是为4,设置后的枷锁为2.
总结:
随着加锁方式值增加,其事务的独立性增加,更能有效的防止事务操作之间的冲突;同时也增加了加锁的开销,降低了用户之间访问数据库的并发性,程序的运行效率也回随之降低。因此得平衡程序运行效率和数据一致性之间的冲突。
一般来说:
- 对于只涉及到数据库的查询操作时,可以采用TRANSACTION_READ_UNCOMMITTED方式;
- 对于数据查询远多于更新的操作,可以采用TRANSACTION_READ_COMMITTED方式;
- 对于更新操作较多的,可以采用TRANSACTION_REPEATABLE_READ;
- 在数据一致性要求更高的场合再考虑最后一项,由于涉及到表加锁,因此会对程序运行效率产生较大的影响;
另外,在oracle中数据库驱动对事务处理的缺省值是TRANSACTION_NONE,即不支持事务操作,所以需要在程序中手动进行设置;而MYSQL是不支持TRANSACTION_NONE的,这点需要注意。