分析:
1.jdbc的conn对象提供了setAutoCommit方法可以关闭自动提交,在Dao层增删改查时可以方便的开事务
2.有些业务需求,如平台充值程序,需要先更新一个资金表表示支付资金,然后再查出一张充值卡发送给用户。
这两个动作是业务上要求必须加事物的,所以需要在service层加事物。
3.由于service层即使拿到conn,开了事物,这个开了事物的conn也传不到dao,dao也调用不成,即使可以传到,
Dao的实现就依赖了Service,不利于代码解耦。
4.ThreadLocal是一个Key为当前线程名的Map,我们可以调用它的Set方法直接往进放东西。
所以每个线程可以方便的往进存入和取出数据。
5.我们将Utils层的getConn方法重写,每次Dao调用getConn时先从ThreadLocal中拿出。
如果有就直接返回,如果没有,就从数据源(c3p0)中拿一个,放到ThreadLocal中,再返回。
6.此时在Service层也可以拿到当前线程的conn,开事务,但是这样做代码耦合度太高,所以在Utils中
写一个专门用于开事物的方法和提交事物的方法供Service层调用。
7.static的Map中存的记录如果存的是对象引用,如果不删除,那么这些对象实例是永久存在的,不会被垃圾回收机制回收。
反过来说,java中的对象的释放只能靠垃圾回收机制。所以当conn.close后被返回到连接池,Map的引用依然指向它,只是不能使用而已。
8.由于线程的命名是有规律可循的,比如Thread1,Thread2,当Thread1释放后下一个线程又有可能起这个名。
所以ThreadLocal根据线程名来取的话很可能拿到的是一个以前也叫Thread1的线程遗留下的已经colse的conn
9.针对以上现象,我们要在关闭conn时从ThreadLocal中remove这个conn.
代码实现
package cn.itcast.utils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.sql.DataSource;
import cn.itcast.dao.DaoException;
import com.mchange.v2.c3p0.ComboPooledDataSource;
/*
* java 有垃圾回收机制 有没有内存泄漏的问题?
*
* 有 静态的集合
*
*/
public class JdbcUtils {
private JdbcUtils(){}
private static DataSource ds ;
private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
// 静态代码块读配置文件
static {
try {
ComboPooledDataSource cpds = new ComboPooledDataSource();
ds = cpds;
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
// 返回数据连接池
public static DataSource getDataSource() {
return ds;
}
// 返回一个连接
public static Connection getConnection() throws SQLException {
// 看当先线程是否绑定了 connectin ,如果没绑定 绑定一个
// 如果绑定了 就返回线程上绑定的那个
// 从 ThreadLocale 中获得连接对象
Connection conn = tl.get(); // 获得当先线程绑定的 Connection
if(conn==null) {
// 说明当前线程未绑定连接, 创建连接 并且绑定
conn = ds.getConnection();
tl.set(conn);
}
return conn;
}
public static void release(Connection conn, Statement stmt, ResultSet rs) {
// 关的顺序和获得的顺序相反
if(rs!=null) {
try {
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
rs = null;
}
if(stmt!=null) {
try {
stmt.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
stmt = null;
}
if(conn!=null) {
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
conn = null;
}
}
// 开启事务
public static void startTransaction() {
try {
// 为当前线程绑定的Connection 开启事务
Connection conn = getConnection();
conn.setAutoCommit(false);
} catch (SQLException e) {
throw new DaoException(e);
}
}
// 提交事务
public static void commit() {
try {
// 判断一下线程上是否有连接
if(tl.get()!=null) {
// 提交当前线程上绑定的 Connection
Connection conn = getConnection();
conn.commit();
}
} catch (SQLException e) {
throw new DaoException(e);
}
}
// 回滚事务
// 关闭连接
public static void close() {
// 关闭当前线程上的连接
try {
// 判断一下线程上是不是真的有连接
if(tl.get()!=null) {
// 提交当前线程上绑定的 Connection
Connection conn = getConnection();
conn.close();
// 将连接从 tl 中移除 移除当前线程绑定的 Connection
tl.remove();
}
} catch (SQLException e) {
throw new DaoException(e);
}
}
}
package cn.itcast.service;
import java.sql.Connection;
import java.sql.SQLException;
import cn.itcast.dao.AccountDao;
import cn.itcast.domain.Account;
import cn.itcast.domain.Person;
import cn.itcast.utils.JdbcUtils;
// service设计些什么样的方法 取决于项目实现的具体功能
public class BusinessService {
// 实现一个转账的功能
public boolean transfer(String from, String to, float money) {
try {
AccountDao dao = new AccountDao();
int num = 1/0;
// 开启事务
JdbcUtils.startTransaction();
// 根据from和to查出account
Account fromAccount = dao.find(from);
Account toAccount = dao.find(to);
// 将两个账户 一个加 一个减
fromAccount.setMoney(fromAccount.getMoney() - money);
toAccount.setMoney(toAccount.getMoney() + money);
// 找 dao 做更新操作
boolean b1 = dao.update(fromAccount);
int i = 1 / 0;
boolean b2 = dao.update(toAccount);
// 提交事务
JdbcUtils.commit();
return b1 & b2;
} finally {
// 关闭连接
JdbcUtils.close();
}
}
}