在使用封装的通用操作数据库方法里,存在获取数据库连接对象的操作,从而导致每次有sql语句调用此通用方法时都会获得一个不同的数据库连接对象,导致这些sql语句不能组成事务(因为Connection的setAutoCommit()方法是针对Connection对象生效的)所以会导致下面的情况:两条sql”组成了表面上的事务“,但其中一条出错后也不能回滚,一个账户减了500元,另一个账户没有加上。
public class GetDifferentConnection {
public static Connection getConnection() throws Exception {
Properties properties = new Properties();
properties.load(new FileInputStream(new File("D:\\develop\\IdeaProject\\SGG\\day28\\src\\BasicDAO\\druid.properties")));
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
Connection connection = dataSource.getConnection();
return connection;
}
public static void update(String sql, Object... args) throws Exception {
Connection conn = getConnection();
System.out.println(conn);
PreparedStatement pst = conn.prepareStatement(sql);
if (args != null && args.length > 0) {
for (int i = 0; i < args.length; i++) {
pst.setObject(i + 1, args[i]);
}
}
//开始进行事务处理
conn.setAutoCommit(false);
try {
pst.executeUpdate();
conn.commit();
} catch (Exception e) {
System.out.println(e.getMessage());
conn.rollback();
} finally {
pst.close();
}
}
}
class Demo2 {
public static void main(String[] args) throws Exception {
String sql1 = "UPDATE acccount SET balance = balance -500 WHERE id=1;";
String sql2 = "UPDATE acccount SET balance = balance +500 WHARE id=2;";
GetDifferentConnection.update(sql1);//com.mysql.jdbc.JDBC4Connection@6385cb26
GetDifferentConnection.update(sql2);//com.mysql.jdbc.JDBC4Connection@c8e4bb0
//每执行一次update()方法,都会执行一次getConnection()方法,导致两次的Connection对象是两个不同的,
// 并且事务处理时的setAutoCommit(false)方法,只针对调用此方法的Connection对象,
//所以,com.mysql.jdbc.JDBC4Connection@6385cb26执行成功,com.mysql.jdbc.JDBC4Connection@c8e4bb0执行失败,
//失败的这个确实会回滚,但由于成功的com.mysql.jdbc.JDBC4Connection@6385cb26与失败的com.mysql.jdbc.JDBC4Connection@c8e4bb0不是同一个对象,
//所以这两个不同的连接对象不能构成一个事务,所以失败的回滚自己不会影响成功的修改数据
}
}
首先使用静态成员数组的方式进行修改,以方便更了解后面使用ThreadLocal的方式。因为这两个方式类似。
原理:每次调用getConnection()获取数据库连接对象时都要判断静态数组里是不是有了一个了,如果有了就不再创建新的,保证了多次执行update()方法时仅有一个数据库连接对象
public class GetSameConnection {
//创建一个保存一个Connection对象的静态数组
static Connection[] connections = new Connection[1];
public static void getConnection() throws Exception {
Properties properties = new Properties();
properties.load(new FileInputStream(new File("D:\\develop\\IdeaProject\\SGG\\day28\\src\\BasicDAO\\druid.properties")));
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
//每次调用getConnection()获取数据库连接对象时都要判断静态数组里是不是有了一个了,如果有了就不再创建新的,保证了多次执行update()方法时仅有一个数据库连接对象
if (connections[0] == null) {
connections[0] = dataSource.getConnection();
}
}
public static void update(String sql, Object... args) throws Exception {
getConnection();
Connection conn = connections[0];
System.out.println(conn);
PreparedStatement pst = conn.prepareStatement(sql);
if (args != null && args.length > 0) {
for (int i = 0; i < args.length; i++) {
pst.setObject(i + 1, args[i]);
}
}
conn.setAutoCommit(false);
try {
pst.executeUpdate();
conn.commit();
} catch (Exception e) {
System.out.println(e.getMessage());
conn.rollback();
} finally {
pst.close();
}
}
}
class Demo3 {
public static void main(String[] args) throws Exception {
String sql1 = "UPDATE acccount SET balance = balance -500 WHERE id=1;";//com.mysql.jdbc.JDBC4Connection@6385cb26
String sql2 = "UPDATE acccount SET balance = balance +500 WHARE id=2;";//com.mysql.jdbc.JDBC4Connection@6385cb26
GetSameConnection.update(sql1);
GetSameConnection.update(sql2);
}
}
接下来就是使用ThreadLocal的方式
使用ThreadLocal可以存储每个线程的备份资源,因为都是main线程去执行update方法,也当然是只有一个main线程进入到了getConnection方法,所以该类里的ThreadLocal对象也是唯一不变的。
第一次执行getConnection时,main线程的ThreadLocal中没有Connection对象,则会使用set方法给装入一个Connection对象。之后每次执行getConnection时,由于main线程的ThreadLocal中存在Connection对象了,就不会再set了,保证了多次执行getConnection方法都只有一个不变的数据库连接对象
public class GetConnectionThreadLocal {
static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
//使用ThreadLocal可以存储每个线程的备份资源,因为都是main线程去执行update方法,
//也当然是只有一个main线程进入到了getConnection方法,所以该类里的ThreadLocal对象也是唯一不变的
//第一次执行getConnection时,main线程的ThreadLocal中没有Connection对象,则会使用set方法给装入一个Connection对象
//之后每次执行getConnection时,由于main线程的ThreadLocal中存在Connection对象了,就不会再set了,保证了多次执行getConnection方法都只有一个不变的数据库连接对象
public static void getConnection() throws Exception {
Properties properties = new Properties();
properties.load(new FileInputStream(new File("D:\\develop\\IdeaProject\\SGG\\day28\\src\\BasicDAO\\druid.properties")));
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
if (threadLocal.get() == null) {
Connection connection = dataSource.getConnection();
threadLocal.set(connection);
}
}
public static void update(String sql, Object... args) throws Exception {
getConnection();
Connection conn = threadLocal.get();
System.out.println(conn);
PreparedStatement pst = conn.prepareStatement(sql);
if (args != null && args.length > 0) {
for (int i = 0; i < args.length; i++) {
pst.setObject(i + 1, args[i]);
}
}
conn.setAutoCommit(false);
try {
pst.executeUpdate();
conn.commit();
} catch (Exception e) {
System.out.println(e.getMessage());
conn.rollback();
} finally {
pst.close();
}
}
}
class Demo {
public static void main(String[] args) throws Exception {
String sql1 = "UPDATE acccount SET balance = balance -500 WHERE id=1;";
String sql2 = "UPDATE acccount SET balance = balance +500 WHERE id=2;";
GetSameConnection.update(sql1);
GetSameConnection.update(sql2);
}
}