jdbc心得-3-事务、批处理、设置主键
1.事务的概念
• 事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功。
• 例如:A——B转帐,对应于如下两条sql语句
update from account set money=money+100 where name=‘b’;
update from account set money=money-100 where name=‘a’;
l 数据库开启事务命令
• start transaction 开启事务
• Rollback 回滚事务
• Commit 提交事务
2. 详解jdbc与数据库事务的连接
当Jdbc程序向数据库获得一个Connection对象时,默认情况下这个Connection对象会自动向数据库提交在它上面发送的SQL语句。若想关闭这种默认提交方式,让多条SQL在一个事务中执行,可使用下列语句:
l JDBC控制事务语句
• Connection.setAutoCommit(false);start transaction
• Connection.rollback(); rollback
• Connection.commit(); commit
l 设置事务回滚点
• Savepoint sp = conn.setSavepoint();
• Conn.rollback(sp);
• Conn.commit(); //回滚后必须要提交
3.多个线程开启各自事务操作数据库中数据时,数据库系统要负责隔离操作,以保证各个线程在获取数据时的准确性。
l 如果不考虑隔离性,可能会引发如下问题:
l 脏读: 指一个事务读取了另外一个事务未提交的数据。
这是非常危险的,假设A向B转帐100元,对应sql语句如下所示:
1.updateaccount set money=money+100 while name=‘b’;
2.updateaccount set money=money-100 while name=‘a’;
当第1条sql执行完,第2条还没执行(A未提交时),如果此时B查询自己的帐户,就会发现自己多了100元钱。如果A等B走后再回滚,B就会损失100元。
l 不可重复读: 在一个事务内读取表中的某一行数据,多次读取结果不同。
例如银行想查询A帐户余额,第一次查询A帐户为200元,此时A向帐户存了100元并提交了,银行接着又进行了一次查询,此时A帐户为300元了。银行两次查询不一致,可能就会很困惑,不知道哪次查询是准的。
• 和脏读的区别是,脏读是读取前一事务未提交的脏数据,不可重复读是重新读取了前一事务已提交的数据。
• 很多人认为这种情况就对了,无须困惑,当然是后面的为准。我们可以考虑这样一种情况,比如银行程序需要将查询结果分别输出到电脑屏幕和写到文件中,结果在一个事务中针对输出的目的地,进行的两次查询不一致,导致文件和屏幕中的结果不一致,银行工作人员就不知道以哪个为准了。
• 虚读(幻读)
• 是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。
• 如丙存款100元未提交,这时银行做报表统计account表中所有用户的总额为500元,然后丙提交了,这时银行再统计发现帐户为600元了,造成虚读同样会使银行不知所措,到底以哪个为准。
4.数据库共定义了四种隔离级别:
• Serializable:可避免脏读、不可重复读、虚读情况的发生。(串行化)(序列化)
• Repeatable read:可避免脏读、不可重复读情况的发生。(可重复读)
• Read committed:可避免脏读情况发生(读已提交)。
• Read uncommitted:最低级别,以上情况均无法保证。(读未提交)
• set transactionisolation level 设置事务隔离级别
• select @@tx_isolation 查询当前事务隔离级别
5.创建JDBC的事务主要分以下步骤
1).设置事务的提交方式为非自动提交:
conn.setAutoCommit(false);
2).将需要添加事务的代码放入try,catch块中。
3).在try块内添加事务的提交操作,表示操作无异常,提交事务。
conn.commit();
4).在catch块内添加回滚事务,表示操作出现异常,撤销事务:
conn.rollback();
5).设置事务提交方式为自动提交:
conn.setAutoCommit(true);
6. 批处理
业务场景:当需要向数据库发送一批SQL语句执行时,应避免向数据库一条条的发送执行,而应采用JDBC的批处理机制,以提升执行效率。
l 实现批处理有两种方式,第一种方式:
• Statement.addBatch(sql)
• 优点:可以向数据库发送多条不同的SQL语句。
• 缺点:(1)SQL语句没有预编译。
(2)当向数据库发送多条语句相同,但仅参数不同的SQL语句时,需重复写上很多条SQL语句
l 执行批处理SQL语句
• executeBatch()方法:执行批处理命令
• clearBatch()方法:清除批处理命令
相关代码:
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtil.getConnection();
String sql1 ="insert into user(name,password,email,birthday)
values('kkk','123','abc@sina.com','1978-08-08')";
String sql2 ="update user set password='123456' where id=3";
st =conn.createStatement();
st.addBatch(sql1); //把SQL语句加入到批命令中
st.addBatch(sql2); //把SQL语句加入到批命令中
st.executeBatch();
} finally{
JdbcUtil.free(conn, st, rs);
}
l 实现批处理的第二种方式:
• PreparedStatement.addBatch()
• 优点:发送的是预编译后的SQL语句,执行效率高。
• 缺点:只能应用在SQL语句相同,但参数不同的批处理中。因此此种形式的批处理经常用于在同一个表中批量插入数据,或批量更新表的数据。
相关代码:
conn = JdbcUtil.getConnection();
String sql ="insert into user(name,password,email,birthday) values(?,?,?,?)";
st =conn.prepareStatement(sql);
for(inti=0;i<50000;i++){
st.setString(1,"aaa" + i);
st.setString(2,"123" + i);
st.setString(3,"aaa" + i + "@sina.com");
st.setDate(4,newDate(1980, 10, 10));
st.addBatch();
if(i%1000==0){
st.executeBatch();
st.clearBatch();
}
}
st.executeBatch();
7.获得数据库自动生成的主键
示例:
Connection conn =JdbcUtil.getConnection();
String sql ="insert into user(name,password,email,birthday)
values('abc','123','abc@sina.com','1978-08-08')";
PreparedStatement st =conn.
prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
st.executeUpdate();
ResultSet rs =st.getGeneratedKeys(); //得到插入行的主键
if(rs.next())
System.out.println(rs.getObject(1));
l 注:此参数仅对insert操作有效