问题背景:
使用JDBC进行开发的时候,每一次的增删改查都必须和数据库建立连接,才可以对数据项进行相应的操作。当我们的业务比较复杂的情况下,可能会出现在一个方法中多次的执行增删改查,这样的话,在这个方法的执行过程中,就需要与数据库建立多次的连接,在这种场景中,如何保证在并发执行这个方法的过程中,与数据库的连接不会混乱,保证这些操作的原子性,就显得尤为重要了。如何解决这个问题呢?
问题分析:
我能想到的方案有两个,其一:在这个多次执行增删改查的方法内,声明一个局部变量用于存放数据库连接对象Connection,这样在调用增删改查方法的时候,将Connection对象作为参数传进去,这样就保证了这些子操作都使用的是同一个连接,从而保证了所有操作的连续性和原子性,不会出现数据库连接对象混乱使用的情况。
其二:我们仔细想想,其实用户的每一次请求,都会调用程序相应的Servlet,而Servlet是单实例多线程的,也就是说每一次的请求程序都启动一个线程为用户服务,在这个线程中会调用很多的方法,包括我们上面提到的那个需要和数据库进行多次连接的复杂方法,由此我们可以这样想,只要保证在这个线程中,我们所使用的数据库连接对象Connection都是同一个,一样可以保证这些操作的连续性和原子性。
相比较第一种方案而言,第二种显然要更好一些,因为第一种需要我们在编写增删改查方法时,定义Connection的参数,第二种则不用,直接将Connection进行一下封装即可。
问题解决:
JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序,ThreadLocal并不是一个Thread,而是Thread的局部变量。我们就使用ThreadLocal这个类来对Connection进行一下简单的封装,以满足我们的需求。代码如下:
/**
* 采用ThreadLocal封装Connection
* @author ljw
*
*/
public class ConnectionManager {
//声明一个本地线程变量,用于存放connection
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>();
/**
* 得到Connection
* @return
*/
public static Connection getConnection() {
Connection conn = connectionHolder.get();
//如果在当前线程中没有绑定相应的Connection
if (conn == null) {
try {
JdbcConfig jdbcConfig = XmlConfigReader.getInstance().getJdbcConfig();
Class.forName(jdbcConfig.getDriverName());
conn = DriverManager.getConnection(jdbcConfig.getUrl(), jdbcConfig.getUserName(), jdbcConfig.getPassword());
//将Connection设置到线程变量ThreadLocal中
connectionHolder.set(conn);
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new ApplicationException("系统错误,请联系系统管理员");
} catch (SQLException e) {
e.printStackTrace();
throw new ApplicationException("系统错误,请联系系统管理员");
}
}
return conn;
}
/**
* 关闭连接,只关闭当前线程的连接
*/
public static void closeConnection() {
Connection conn = connectionHolder.get();
if (conn != null) {
try {
conn.close();
//从ThreadLocal中清除Connection
connectionHolder.remove();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(Connection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(Statement pstmt) {
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(ResultSet rs ) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void beginTransaction(Connection conn) {
try {
if (conn != null) {
if (conn.getAutoCommit()) {
conn.setAutoCommit(false); //手动提交
}
}
}catch(SQLException e) {}
}
public static void commitTransaction(Connection conn) {
try {
if (conn != null) {
if (!conn.getAutoCommit()) {
conn.commit();
}
}
}catch(SQLException e) {}
}
public static void rollbackTransaction(Connection conn) {
try {
if (conn != null) {
if (!conn.getAutoCommit()) {
conn.rollback();
}
}
}catch(SQLException e) {}
}
}
小结一下:
我们可以在方法内部进行控制,当然我们也可以在线程中进行控制,同一个线程的局部变量不会影响到另一个线程的,Java为我们提供了丰富的类库去开发各种各样的程序,把玩这些类可以窥探程序设计乐趣,这就是编程的魅力所在。在编程中多线程的问题很值得研究,本篇文章中只是解决了一个小小的Connection独立性的问题,还有很多很多的其他问题,需要我们去认识和理解。