一、JDBC手动提交事务
模拟一个JDBC事务,实现A账户向B账户转账一万。A账户减去一万和B账户加上一万,必须同时成功或者失败。SQL建表语句:
drop table if exists bankAccount;
create table bankAccount(
actno varchar(1),
bank_money double(7,2),
#10和2分别是有效数字和小数的个数
primary key(actno));
insert into bankaccount values ('A',20000.69);
insert into bankaccount values ('B',10000.45);
然后在A转账和B收钱之间插入一个异常:
package com.fan;
import java.sql.*;
import java.util.ResourceBundle;
public class Test {
public static void main(String[] args){
Connection con=null;
PreparedStatement ps=null;
ResourceBundle db=ResourceBundle.getBundle("resource/db");
String driver=db.getString("driver");
String url=db.getString("url");
String user=db.getString("user");
String password=db.getString("password");
try {
Class.forName(driver);
con=DriverManager.getConnection(url,user,password);
/*令A的bankmoney减去10000
* */
String sql="update bankaccount set bank_money=? where actno=?";
ps=con.prepareStatement(sql);
ps.setDouble(1,10000);
ps.setString(2,"A");
ps.executeUpdate();
//出现异常,程序直接转入catch语句执行
String s=null;
s.toString();
/*这里再模拟B受到转账
* */
sql="update bankaccount set bank_money=? where actno=?";
ps=con.prepareStatement(sql);
ps.setDouble(1,20000);
ps.setString(2,"B");
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}finally {
if(ps!=null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (con!=null){
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
因为JDBC的事务是自动提交的,所以上述事务中A执行了而B没有执行,从而发生错误。因此在实际开发中需要关闭JDBC的自动提交机制,改成手动提交。在获得数据库连接后就要关闭自动提交。
con=DriverManager.getConnection(url,user,password);
//关闭自动提交模式
con.setAutoCommit(false);
在正常执行结束后,在上述程序try语句块最后设置手动提交。
con.commit()
出现异常,在try语句块进行回滚。
catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
//出现异常进行回滚
if (con!=null){
con.rollback();
}
二、JDBC工具类封装
编写一个工具类,便于JDBC的编写。
package com.fan.JDBCtool;
import java.sql.*;
import java.util.ResourceBundle;
public class DButil {
//工具类中的构造方法一般都是静态的,防止new对象
private DButil(){
}
//从配置文件获取驱动等名称
private static ResourceBundle rb=ResourceBundle.getBundle("resource/db");
//封装一个静态代码块加载驱动
//在调用DButil工具类的时候,就会在类加载时先执行静态代码块
//由于静态代码块只加载一次,第二次调用工具类里面的方法,不会执行静态代码块
static{
try {
Class.forName(rb.getString("driver"));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//获取连接的方法
public static Connection getConnection() throws SQLException {
String url=rb.getString("url");
String user=rb.getString("user");
String password=rb.getString("password");
return DriverManager.getConnection(url,user,password);
}
//设置关闭连接等对象的方法
//用Statement,若传入的对象是PreparedStatement,可以使用多态
public static void close(Connection con, Statement sta, ResultSet rs){
if (rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (sta!=null){
try {
sta.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (con!=null){
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
测试工具类:
package com.fan;
import com.fan.JDBCtool.DButil;
import java.sql.*;
import java.util.ResourceBundle;
public class Test {
public static void main(String[] args) {
Connection con=null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
con= DButil.getConnection();
con.setAutoCommit(false);
String sql="select sname from student where sid like ?";
ps=con.prepareStatement(sql);
ps.setString(1,"0%");
rs=ps.executeQuery();
while(rs.next()){
System.out.println(rs.getString("sname"));
}
con.commit();
} catch (SQLException e) {
e.printStackTrace();
if (con!=null){
try {
con.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}finally {
DButil.close(con,ps,rs);
}
}
}
三、行级锁for update
在DQL语句后面加上for update,意思是在查询的过程中,其他任何事务不能对查询的记录进行操作,直到当前事务结束。该锁又称悲观锁。
行级锁使用索引,当没有索引或者索引失效会锁住整张表。