通向架构师的道路(第七天)之漫谈使用ThreadLocal改进你的层次的划分
一、什么是ThreadLocal
早在JDK 1.2的版本中就提供Java.lang.ThreadLocal,ThreadLocal為解決多線程程序的并發(fā)問題提供了一種新的思路。使用這個(gè)工具類可以很簡潔地編寫出優(yōu)美的多線程程序。
ThreadLocal很容易讓人望文生義,想當(dāng)然地認(rèn)為是一個(gè)“本地線程”。其實(shí),ThreadLocal并不是一個(gè)Thread,而是Thread的局部變量,也許把它命名為ThreadLocalVariable更容易讓人理解一些。
當(dāng)使用ThreadLocal維護(hù)變量時(shí),ThreadLocal為每個(gè)使用該變量的線程提供獨(dú)立的變量副本,所以每一個(gè)線程都可以獨(dú)立地改變自己的副本,而不會影響其它線程所對應(yīng)的副本。
從線程的角度看,目標(biāo)變量就象是線程的本地變量,這也是類名中“Local”所要表達(dá)的意思。
線程局部變量并不是Java的新發(fā)明,很多語言(如IBM IBM XL FORTRAN)在語法層面就提供線程局部變量。在Java中沒有提供在語言級支持,而是變相地通過ThreadLocal的類提供支持。
所以,在Java中編寫線程局部變量的代碼相對來說要笨拙一些,因此造成線程局部變量沒有在Java開發(fā)者中得到很好的普及。
ThreadLocal的接口方法
ThreadLocal類接口很簡單,只有4個(gè)方法,我們先來了解一下:
2??void set(Object value)
設(shè)置當(dāng)前線程的線程局部變量的值。
2??public Object get()
該方法返回當(dāng)前線程所對應(yīng)的線程局部變量。
2??public void remove()
將當(dāng)前線程局部變量的值刪除,目的是為了減少內(nèi)存的占用,該方法是JDK 5.0新增的方法。需要指出的是,當(dāng)線程結(jié)束后,對應(yīng)該線程的局部變量將自動被垃圾回收,所以顯式調(diào)用該方法清除線程的局部變量并不是必須的操作,但它可以加快內(nèi)存回收的速度。
2??protected ObjectinitialValue()
返回該線程局部變量的初始值,該方法是一個(gè)protected的方法,顯然是為了讓子類覆蓋而設(shè)計(jì)的。這個(gè)方法是一個(gè)延遲調(diào)用方法,在線程第1次調(diào)用get()或set(Object)時(shí)才執(zhí)行,并且僅執(zhí)行1次。ThreadLocal中的缺省實(shí)現(xiàn)直接返回一個(gè)null。
值得一提的是,在JDK5.0中,ThreadLocal已經(jīng)支持泛型,該類的類名已經(jīng)變?yōu)門hreadLocal<T>。API方法也相應(yīng)進(jìn)行了調(diào)整,新版本的API方法分別是void set(T value)、T get()以及T initialValue()。
一、來看一個(gè)實(shí)際案例
2.1?同一Service方法中調(diào)用多個(gè)Dao方法
可以看到,我們有一個(gè)Service方法,在該Service方法中調(diào)用多個(gè)Dao方法,所有在該Service方法中的的Dao都處于同一事務(wù)中。
該Service方法結(jié)束后,提交事務(wù);
該Service方法中有任何錯(cuò),回滾事務(wù);
2.2?傳統(tǒng)的做法
來看下面這段偽代碼
Service層代碼:
| public void serviceMethod(){ Connection conn=null; try{ Connection conn=getConnection(); conn.setAutoCommit(false); Dao1 dao1=new Dao1(conn); dao1.doSomething(); Dao2 dao2=new Dao2(conn); dao2.doSomething(); Dao3 dao3=new Dao3(conn); dao3.doSomething(); }catch(Exception e){ ??? try{ ??? conn.rollback(); }catch(Exception ex){} }finally{ try{ conn.setAutoCommit(true); }catch(Exception e){} ??? try{ ??? if(conn!=null){ ??? conn.close(); ??? conn=null; } }catch(Exception e){} } } |
每個(gè)Dao層的代碼:
| Class Dao1{ private Connection conn=null; public Dao1(Connection conn){ ??? this.conn=conn; } public void doSomething(){ ??? PreparedStatement pstmt=null; ??? try{ ??????? pstmt=conn.preparedStatement(sql); ??????? pstmt.execute… ??????? … }catch(Exception e){ ??? log.error(e,”Exeception occurred in Dao1.doSomething():”+e.getMessage,e); }finally{ ??? try{ ??????? if(pstmt!=null){ ??????????? pstmt.close(); ??????????? pstmt=null; } ??? }catch(Exception e){} } } } |
如果我一個(gè)Service方法有調(diào)用一堆dao方法,先不說這樣寫首先破壞了OOP的封裝性原則,如果有一個(gè)dao多關(guān)了一個(gè)conn,那就會導(dǎo)致其它的dao得到的conn為null,這種事在這樣的寫法下由其當(dāng)你還有業(yè)務(wù)邏輯混合在一起時(shí)很容易發(fā)生。
筆者曾經(jīng)遇見過2個(gè)項(xiàng)目,出現(xiàn)out of memory或者是connection pool has been leakage,經(jīng)查代碼就是在每個(gè)dao中多關(guān)或者在service層中漏關(guān),或者是每個(gè)dao有自己的conntionconn=getConnection(),然后還跑到Service層里去關(guān)這個(gè)connection(那關(guān)什么,關(guān)個(gè)P關(guān)!)。
當(dāng)然,如果你說你在寫法上絕對promise絕對注意這樣的問題不會發(fā)生,但是我們來看看下面的這種做法,是否會比上面這個(gè)寫法更好呢?
2.3 Spring中的做法
先來看spring中的寫法。
大家應(yīng)該都很熟悉Spring中的寫法了,來看一下它是怎么解決的。
Service層
| public void serviceMethod(){ try{ ??? //aop 自動加入connection,并且將conn.setAutoCommit(false); dao1.doSomething(); dao2.doSomething(); dao3.doSomething(); }catch(Exception e){ ??? //aop 自動加入rollback }finally{ ??? //aop自動加入conn.setAutoCommit(true) ??? //aop 自動加入conn.close(); } |
這邊我們不講AOP,因?yàn)橛妙惙瓷浣Y(jié)合xml很容易將aop 自動。。。這些東西加入我們的代碼中去是不是?我們只管寫dao方法,service方法,不需要關(guān)心在哪邊commit哪邊rollback何時(shí)connection,spring的聲明式事務(wù)會幫我們負(fù)責(zé),這種風(fēng)格我們稱為“優(yōu)雅”,各層間耦合度極大程度上的降低,封裝性好。
因此,我們可以總結(jié)出下面這些好處:
2? Service層的方法只管開啟事務(wù)(如果講究點(diǎn)的還會設(shè)一個(gè)Transaction);
2? 在該Service層中的所有dao使用該service方法中開啟的事務(wù)(即connection);
2? Dao中每次只管getCurrentConnection(獲取當(dāng)前的connection),與進(jìn)行數(shù)據(jù)處理
2? Dao層中如果發(fā)生錯(cuò)誤就拋回Service層
2? Service層中接到exception,在catch{}中rollback,在try{}未尾commit,在finally塊中關(guān)閉整個(gè)connection。
這。。。就是我們所說的ThreadLocal。
舉個(gè)更實(shí)際的例子再次來說明ThreadLocal:
我們有3個(gè)用戶訪問同一個(gè)service方法,該service方法內(nèi)有3個(gè)dao方法為一個(gè)完整事務(wù),那么整個(gè)web容器內(nèi)只因該有3個(gè)connection,并且每個(gè)connection之間的狀態(tài),彼此“隔離”。
我們下面一起來看我們?nèi)绾斡么a實(shí)現(xiàn)類似于Spring的這種做法。
首先,根據(jù)我們的ThreadLocal的概念,我們先聲明一個(gè)ConnectionManager的類。
2.4?利用ThreadLocal制作ConnectionManager
| public class ConnectionManager { ???????? private static ThreadLocal tl = new ThreadLocal(); ???????? private static Connection conn = null; ???????? public static void BeginTrans(boolean beginTrans) throws Exception { ?????????????????? if (tl.get() == null || ((Connection) tl.get()).isClosed()) { ??????????????????????????? conn = SingletonDBConnection.getInstance().getConnection(); ??????????????????????????? conn = new ConnectionSpy(conn); ??????????????????????????? if (beginTrans) { ???????????????????????????????????? conn.setAutoCommit(false); ??????????????????????????? } ??????????????????????????? tl.set(conn); ?????????????????? } ???????? } ???????? public static Connection getConnection() throws Exception { ?????????????????? return (Connection) tl.get(); ???????? } ???????? public static void close() throws SQLException { ?????????????????? try { ??????????????????????????? ((Connection) tl.get()).setAutoCommit(true); ?????????????????? } catch (Exception e) { ?????????????????? } ?????????????????? ((Connection) tl.get()).close(); ?????????????????? tl.set(null); ???????? } ???????? public static void commit() throws SQLException { ?????????????????? try { ??????????????????????????? ((Connection) tl.get()).commit(); ?????????????????? } catch (Exception e) { ?????????????????? } ?????????????????? try { ??????????????????????????? ((Connection) tl.get()).setAutoCommit(true); ?????????????????? } catch (Exception e) { ?????????????????? } ???????? } ???????? public static void rollback() throws SQLException { ?????????????????? try { ??????????????????????????? ((Connection) tl.get()).rollback(); ?????????????????? } catch (Exception e) { ?????????????????? } ?????????????????? try { ??????????????????????????? ((Connection) tl.get()).setAutoCommit(true); ?????????????????? } catch (Exception e) { ?????????????????? } ???????? } } |
2.5?利用ThreadLocal改造Service與Dao層
Service層(注意紅色標(biāo)粗-好粗yeah,的地方)
| package sky.org.service.impl; public class StudentServiceImpl implements StudentService { ???????? public void addStudent(Student std) throws Exception { ???????????????????StudentDAO studentDAO = new StudentDAOImpl(); ?????????????????? ClassRoomDAO classRoomDAO = new ClassRoomDAOImpl(); ?????????????????? try { ??????????????????????????? ConnectionManager.BeginTrans(true); ????????????????????????????studentDAO.addStudent(std); ????????????????????????????classRoomDAO ??????????????????????????????????????????????.addStudentClassRoom(std.getClassRoomId(), std.getsNo()); ????????????????????????????ConnectionManager.commit(); ?????????????????? } catch (Exception e) { ????????????????????????????try { ???????????????????????????????????? ConnectionManager.rollback(); ??????????????????????????? } catch (Exception de) { ??????????????????????????? } ??????????????????????????? throw new Exception(e); ?????????????????? }finally { ??????????????????????????? try { ???????????????????????????????????? ConnectionManager.close(); ??????????????????????????? } catch (Exception e) { ??????????????????????????? } ?????????????????? } ???????? } } |
Look,如果我把上述標(biāo)粗(沒有加紅色)的地方,全部用AOP的方式從這塊代碼的外部“切”進(jìn)去。。。是不是一個(gè)Spring里的Service方法就誕生了?
下面來看一個(gè)完整的例子
2.6?使用ThreadLocal分離Service、DAO層
先來看表結(jié)構(gòu):
T_Student表
T_ClassRoom表
T_Student_ClassRoom表
需求:
很簡單,T_ClassRoom表里已經(jīng)有值了,在插入T_Student表的數(shù)據(jù)時(shí)同時(shí)要給這個(gè)學(xué)生分配一個(gè)班級并且插入T_Student_ClassRoom表,這就是一個(gè)事務(wù),這兩步中有任何一步出錯(cuò),事務(wù)必須回滾。
看來工程的結(jié)構(gòu)吧:
下面開始放出所有源代碼:
2.6.1 ConnectionManager類
| package sky.org.util.db; import java.sql.*; public class ConnectionManager { ???????? private static ThreadLocal tl = new ThreadLocal(); ???????? private static Connection conn = null; ???????? public static void BeginTrans(boolean beginTrans) throws Exception { ?????????????????? if (tl.get() == null || ((Connection) tl.get()).isClosed()) { ??????????????????????????? conn = DBConnection.getInstance().getConnection(); ??????????????????????????? conn = new ConnectionSpy(conn); ??????????????????????????? if (beginTrans) { ???????????????????????????????????? conn.setAutoCommit(false); ??????????????????????????? } ??????????????????????????? tl.set(conn); ?????????????????? } ???????? } ???????? public static Connection getConnection() throws Exception { ????????????????? return (Connection) tl.get(); ???????? } ???????? public static void close() throws SQLException { ?????????????????? try { ??????????????????????????? ((Connection) tl.get()).setAutoCommit(true); ?????????????????? } catch (Exception e) { ?????????????????? } ?????????????????? ((Connection) tl.get()).close(); ?????????????????? tl.set(null); ???????? } ???????? public static void commit() throws SQLException { ?????????????????? try { ??????????????????????????? ((Connection) tl.get()).commit(); ?????????????????? } catch (Exception e) { ?????????????????? } ?????????????????? try { ??????????????????????????? ((Connection) tl.get()).setAutoCommit(true); ?????????????????? } catch (Exception e) { ?????????????????? } ???????? } ???????? public static void rollback() throws SQLException { ?????????????????? try { ??????????????????????????? ((Connection) tl.get()).rollback(); ?????????????????? } catch (Exception e) { ?????????????????? } ?????????????????? try { ??????????????????????????? ((Connection) tl.get()).setAutoCommit(true); ?????????????????? } catch (Exception e) { ?????????????????? } ???????? } } |
2.6.2 DBConnection類
| package sky.org.util.db; public class DBConnection { ???????? private static DBConnection instance = null; ???????? private static String driverClassName = null; ???????? private static String connectionUrl = null; ???????? private static String userName = null; ???????? private static String password = null; ???????? private static Connection conn = null; ???????? private static Properties jdbcProp = null; ???????? private DBConnection() { ???????? } ???????? private static Properties getConfigFromPropertiesFile() throws Exception { ?????????????????? Properties prop = null; ?????????????????? prop = JdbcProperties.getPropObjFromFile(); ?????????????????? return prop; ???????? } ???????? private static void initJdbcParameters(Properties prop) { ?????????????????? driverClassName = prop.getProperty(Constants.DRIVER_CLASS_NAME); ?????????????????? connectionUrl = prop.getProperty(Constants.CONNECTION_URL); ?????????????????? userName = prop.getProperty(Constants.DB_USER_NAME); ?????????????????? password = prop.getProperty(Constants.DB_USER_PASSWORD); ???????? } ???????? private static void createConnection() throws Exception { ?????????????????? Class.forName(driverClassName); ?????????????????? conn = DriverManager.getConnection(connectionUrl, userName, password); ???????? } ???????? public static Connection getConnection() throws Exception { ?????????????????? return conn; ???????? } ???????? public synchronized static DBConnection getInstance()throws Exception{ ?????????????????? if (instance == null) { ??????????????????????????? jdbcProp = getConfigFromPropertiesFile(); ??????????????????????????? instance = new DBConnection(); ?????????????????? } ?????????????????? initJdbcParameters(jdbcProp); ?????????????????? createConnection(); ?????????????????? return instance; ???????? } } |
2.6.3 JdbcProperties類
| package sky.org.util.db; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.NET.URL; import java.util.*; public class JdbcProperties { ???????? private static Log logger = LogFactory.getLog(JdbcProperties.class); ???????? public static Properties getPropObjFromFile() { ?????????????????? Properties objProp = new Properties(); ?????????????????? ClassLoader classLoader = Thread.currentThread() ???????????????????????????????????? .getContextClassLoader(); ?????????????????? URL url = classLoader.getResource(Constants.JDBC_PROPERTIES_FILE); ?????????????????? if (url == null) { ??????????????????????????? classLoader = ClassLoader.getSystemClassLoader(); ??????????????????????????? url = classLoader.getResource(Constants.JDBC_PROPERTIES_FILE); ?????????????????? } ?????????????????? File file = new File(url.getFile()); ?????????????????? InputStream inStream = null; ?????????????????? try { ??????????????????????????? inStream = new FileInputStream(file); ??????????????????????????? objProp.load(inStream); ?????????????????? } catch (FileNotFoundException e) { ??????????????????????????? objProp = null; ??????????????????????????? e.printStackTrace(); ?????????????????? } catch (IOException e) { ??????????????????????????? e.printStackTrace(); ?????????????????? } finally { ??????????????????????????? try { ???????????????????????????????????? if (inStream != null) { ?????????????????????????????????????????????? inStream.close(); ?????????????????????????????????????????????? inStream = null; ???????????????????????????????????? } ??????????????????????????? } catch (Exception e) { ??????????????????????????? } ?????????????????? } ?????????????????? return objProp; ???????? } } |
2.6.4 Resource目錄下的jdbc.properties
| jdbc.driverClassName=com.MySQL.jdbc.Driver jdbc.databaseURL=jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8 jdbc.username=mysql jdbc.password=password_1 |
2.6.5 StudentService接口
| package sky.org.service; import java.util.List; import java.util.Vector; import sky.org.bean.*; public interface StudentService { ???????? public void addStudent(Student std) throws Exception; } |
2.6.6 StudentServiceImpl類
| package sky.org.service.impl; import java.util.ArrayList; import java.util.List; import java.util.Vector; import sky.org.util.db.ConnectionManager; import sky.org.util.*; import sky.org.bean.*; import sky.org.dao.*; import sky.org.dao.impl.*; import sky.org.service.*; public class StudentServiceImpl implements StudentService { ???????? public void addStudent(Student std) throws Exception { ?????????????????? StudentDAO studentDAO = new StudentDAOImpl(); ?????????????????? ClassRoomDAO classRoomDAO = new ClassRoomDAOImpl(); ?????????????????? try { ??????????????????????????? ConnectionManager.BeginTrans(true); ??????????????????????????? studentDAO.addStudent(std); ??????????????????????????? classRoomDAO ?????????????????????????????????????????????? .addStudentClassRoom(std.getClassRoomId(), std.getsNo()); ??????????????????????????? ConnectionManager.commit(); ?????????????????? } catch (Exception e) { ??????????????????????????? try { ???????????????????????????????????? ConnectionManager.rollback(); ??????????????????????????? } catch (Exception de) { ??????????????????????????? } ??????????????????????????? throw new Exception(e); ?????????????????? } finally { ??????????????????????????? try { ???????????????????????????????????? ConnectionManager.close(); ??????????????????????????? } catch (Exception e) { ??????????????????????????? } ?????????????????? } ???????? } } |
2.6.7 ClassRoomDAO接口
| package sky.org.dao; import java.util.HashMap; import java.util.List; public interface ClassRoomDAO { ???????? public void addStudentClassRoom(String roomId, String sNo) throws Exception; } |
2.6.8 ClassRoomDAOImpl類
| package sky.org.dao.impl; import java.sql.*; import java.util.*; import sky.org.dao.ClassRoomDAO; import sky.org.util.db.ConnectionManager; public class ClassRoomDAOImpl implements ClassRoomDAO { ???????? public void addStudentClassRoom(String roomId, String sNo) throws Exception { ?????????????????? Connection conn = null; ?????????????????? PreparedStatement pstmt = null; ?????????????????? try { ??????????????????????????? conn = ConnectionManager.getConnection(); ??????????????????????????? pstmt = conn ?????????????????????????????????????????????? .prepareStatement(ClassRoomDAOSql.ADD_STUDENT_CLASSROOM); ??????????????????????????? pstmt.setString(1, roomId); ??????????????????????????? pstmt.setString(2, sNo); ??????????????????????????? pstmt.executeUpdate(); ?????????????????? } catch (Exception e) { ??????????????????????????? throw new Exception("addStudentClassRoom:" + e.getMessage(), e); ?????????????????? } finally { ??????????????????????????? try { ???????????????????????????????????? if (pstmt != null) { ?????????????????????????????????????????????? pstmt.close(); ?????????????????????????????????????????????? pstmt = null; ???????????????????????????????????? } ??????????????????????????? } catch (Exception e) { ??????????????????????????? } ?????????????????? } ???????? } } |
2.6.9 StudentDAO接口
| package sky.org.dao; import java.util.*; import sky.org.bean.Student; public interface StudentDAO { ???????? public void addStudent(Student std) throws Exception; } |
2.6.10 StudentDAOImpl類
| package sky.org.dao.impl; import java.sql.*; import javax.sql.*; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import sky.org.bean.Student; import sky.org.dao.StudentDAO; import sky.org.util.db.ConnectionManager; import java.util.List; import java.util.ArrayList; import java.util.Vector; import java.text.*; import sky.org.util.StringUtil; public class StudentDAOImpl implements StudentDAO { ???????? private Log logger = LogFactory.getLog(this.getClass()); ???????? public void addStudent(Student std) throws Exception { ?????????????????? Connection conn = null; ?????????????????? PreparedStatement pstmt = null; ?????????????????? try { ??????????????????????????? conn = ConnectionManager.getConnection(); ??????????????????????????? pstmt = conn.prepareStatement(StudentDAOSql.ADD_STUDENT); ??????????????????????????? pstmt.setString(1, std.getsNo()); ??????????????????????????? pstmt.setString(2, std.getsName()); ??????????????????????????? pstmt.setString(3, std.getsAge()); ??????????????????????????? pstmt.setString(4, std.getGender()); ??????????????????????????? pstmt.setDate(5, StringUtil.convertStrToDate(std.getSbirth())); ??????????????????????????? pstmt.executeUpdate(); ?????????????????? } catch (Exception e) { ??????????????????????????? throw new Exception("addStudent:" + e.getMessage(), e); ?????????????????? } finally { ??????????????????????????? try { ???????????????????????????????????? if (pstmt != null) { ?????????????????????????????????????????????? pstmt.close(); ?????????????????????????????????????????????? pstmt = null; ???????????????????????????????????? } ??????????????????????????? } catch (Exception e) { ??????????????????????????? } ?????????????????? } ???????? } ???????? public void delStudent(String sNo) throws Exception { ?????????????????? Connection conn = null; ?????????????????? PreparedStatement pstmt = null; ?????????????????? try { ??????????????????????????? conn = ConnectionManager.getConnection(); ??????????????????????????? pstmt = conn.prepareStatement(StudentDAOSql.DEL_STUDENT); ??????????????????????????? pstmt.setString(1, sNo); ??????????????????????????? pstmt.executeUpdate(); ?????????????????? } catch (Exception e) { ??????????????????????????? throw new Exception("delStudent:" + e.getMessage(), e); ?????????????????? } finally { ??????????????????????????? try { ???????????????????????????????????? if (pstmt != null) { ?????????????????????????????????????????????? pstmt.close(); ?????????????????????????????????????????????? pstmt = null; ???????????????????????????????????? } ??????????????????????????? } catch (Exception e) { ??????????????????????????? } ?????????????????? } ???????? } } |
2.6.11 StudentDAOSql類
| package sky.org.dao.impl; public class StudentDAOSql { public final static String ADD_STUDENT = "insert into t_student(sno, sname, sage, gender, sbirth)values(?,?,?,?,?)"; } |
2.6.12 ClassRoomDAOSql類
| package sky.org.dao.impl; public class ClassRoomDAOSql { ???????? public static String ADD_STUDENT_CLASSROOM = "insert into t_student_classroom(room_id,sno)values(?,?)"; } |
2.6.13 ClassRoom?類
| package sky.org.bean; import java.io.*; public class ClassRoom implements Serializable { ???????? private String roomId = ""; ???????? private String roomName = ""; ???????? public String getRoomId() { ?????????????????? return roomId; ???????? } ???????? public void setRoomId(String roomId) { ?????????????????? this.roomId = roomId; ???????? } ???????? public String getRoomName() { ?????????????????? return roomName; ???????? } ???????? public void setRoomName(String roomName) { ?????????????????? this.roomName = roomName; ???????? } } |
2.6.14 Student類
| package sky.org.bean; import java.io.*; public class Student implements Serializable { ???????? public String getsNo() { ?????????????????? return sNo; ???????? } ???????? public void setsNo(String sNo) { ?????????????????? this.sNo = sNo; ???????? } ???????? public String getsName() { ?????????????????? return sName; ???????? } ???????? public void setsName(String sName) { ?????????????????? this.sName = sName; ???????? } ???????? public String getsAge() { ?????????????????? return sAge; ???????? } ???????? public void setsAge(String sAge) { ?????????????????? this.sAge = sAge; ???????? } ???????? public String getGender() { ?????????????????? return gender; ???????? } ???????? public void setGender(String gender) { ?????????????????? this.gender = gender; ???????? } ???????? private String sNo = ""; ???????? private String sName = ""; ???????? private String sAge = ""; ???????? private String gender = ""; ???????? private String sbirth = ""; ???????? private String classRoomId = ""; ???????? private String classRoomName = ""; ???????? public String getClassRoomId() { ?????????????????? return classRoomId; ???????? } ???????? public void setClassRoomId(String classRoomId) { ?????????????????? this.classRoomId = classRoomId; ???????? } ???????? public String getClassRoomName() { ?????????????????? return classRoomName; ???????? } ???????? public void setClassRoomName(String classRoomName) { ?????????????????? this.classRoomName = classRoomName; ???????? } ???????? public String getSbirth() { ?????????????????? return sbirth; ???????? } ???????? public void setSbirth(String sbirth) { ?????????????????? this.sbirth = sbirth; ???????? } } |
2.6.15 StudentCRUD類(運(yùn)行主類)
| package sky.org.test; import sky.org.bean.Student; import sky.org.service.StudentService; import sky.org.service.impl.StudentServiceImpl; public class StudentCRUD { ???????? public void addStudent() throws Exception { ?????????????????? StudentService stdService = new StudentServiceImpl(); ?????????????????? Student std = new Student(); ?????????????????? std.setsNo("101"); ?????????????????? std.setsName("abc"); ?????????????????? std.setSbirth("1977/01/01"); ?????????????????? std.setsAge("35"); ?????????????????? std.setGender("m"); ?????????????????? std.setClassRoomId("1"); ?????????????????? std.setClassRoomName("class1"); ?????????????????? stdService.addStudent(std); ???????? } ???????? public static void main(String[] args) { ?????????????????? StudentCRUD testStudentCRUD = new StudentCRUD(); ?????????????????? try { ??????????????????????????? testStudentCRUD.addStudent(); ?????????????????? } catch (Exception e) { ??????????????????????????? e.printStackTrace(); ??????????????????????????? System.exit(-1); ?????????????????? } ???????? } } |
三、Hibernate里的ThreadLocal
hibernate在事務(wù)操作中也支持ThreadLocal的作法,我們這邊指的是不用Spring去做代理,而直接用Hibernate。即:
| Service Method{ hbDAO1.doSomething(); hbDAO2.doSomething(); hbDAO3.doSomething(); 。。。 } |
Hibernate版本3后增加了新特性,即getCurrentSession()。
3.1 getCurrentSession與openSession的區(qū)別
3.1.1 openSession
我們傳統(tǒng)的做法是openSession即:
| public void testUser() throws Exception { ?????????????????? Transaction tran = null; ?????????????????? SessionFactory factory = null; ?????????????????? UserDAO userDAO = new UserDAOImpl(); ?????????????????? try { ??????????????????????????? factory = HibernateUtil.getInstance().getSessionFactory(); ??????????????????????????? Session session = factory.openSession(); ??????????????????????????? tran = session.beginTransaction(); ??????????????????????????? TUser testUser = new TUser(); ??????????????????????? testUser.setId(new Integer(i)); ??????????????????????????? testUser.setName("abc"); ??????????????????????????? userDAO.addUser(testUser); ??????????????????????????? tran.commit(); ?????????????????? } catch (Exception e) { ??????????????????????????? tran.rollback(); ??????????????????????????? throw new Exception(e); ?????????????????? } finally { ??????????????????????????? try{ ??? if(session!=null){ ??? session.close(); ??? session=null(); } }catch(Excepton e){} ?????????????????? } ???????? } |
這樣做,能夠保證我們每次在finally塊中正確關(guān)閉session,但是,如果我們也遇上了這樣的case即:
| Service Method{ hbDAO1.doSomething(); hbDAO2.doSomething(); hbDAO3.doSomething(); 。。。 } |
這時(shí),我們?nèi)绻玫氖莖penSession,應(yīng)該怎么辦?
解決方案一:
自己用ThreadLocal模式寫一個(gè)SessionManagement類,來維護(hù)這個(gè)session。
解決方案二:
把在Service方法中打開的session,傳到每個(gè)dao方法中,使每個(gè)dao方法使用同一個(gè)session,最后在Service方法中去關(guān)閉它(很爛的做法)。
下面我們來看看Hibernate自身提供的getCurrentSession()的做法吧
3.1.2 getCurrentSession
要使用這個(gè)getCurrentSession,你的hibernate的設(shè)置必須如下(紅色加粗部分顯示-就喜歡粗J):
| <?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE hibernate-configuration PUBLIC ????????? "-//Hibernate/Hibernate Configuration DTD 3.0//EN" ????????? "http://hibernate.sourceforge.Net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> ???????? <session-factory> ?????????????????? <property name="connection.url"> ??????????????????????????? jdbc:Oracle:thin:@localhost:1521:myorcl ?????????????????? </property> ?????????????????? <property name="dialect"> ??????????????????????????? org.hibernate.dialect.Oracle9Dialect ?????????????????? </property> ?????????????????? <property name="connection.username">abc</property> ?????????????????? <property name="connection.password">abc</property> ?????????????????? <property name="connection.driver_class"> ??????????????????????????? oracle.jdbc.OracleDriver ?????????????????? </property> ?????????????????? <property name="show_sql">true</property> ?????????????????? <property name="hibernate.hbm2ddl.auto">update</property> ???????????????????<property name="hibernate.current_session_context_class">thread</property> ?????????????????? <mapping resource="com/cts/testhb/model/TUser.hbm.xml" /> ???????? </session-factory> </hibernate-configuration> |
然后上述代碼將變成如下的樣子:
| public void testUser() throws Exception { ?????????????????? Transaction tran = null; ?????????????????? SessionFactory factory = null; ?????????????????? UserDAO userDAO = new UserDAOImpl(); ?????????????????? try { ??????????????????????????? factory = HibernateUtil.getInstance().getSessionFactory(); ????????????????????????????Session session = factory.getCurrentSession(); ??????????????????????????? tran = session.beginTransaction(); ??????????????????????????? for (int i = 0; i < 100; i++) { ???????????????????????????????????? TUser testUser = new TUser(); ???????????????????????????????????? testUser.setId(new Integer(i)); ???????????????????????????????????? testUser.setName("abc"); ???????????????????????????????????? userDAO.addUser(testUser); ??????????????????????????? } ??????????????????????????? tran.commit(); ?????????????????? } catch (Exception e) { ??????????????????????????? tran.rollback(); ??????????????????????????? throw new Exception(e); ?????????????????? } finally { ????????????????????????????ThreadLocalSessionContext.unbind(factory); ?????????????????? } ???????? } |
而你的每個(gè)DAO方法中的代碼是這樣實(shí)現(xiàn)的:
| public void addUser(TUser user) throws Exception { ???????? SessionFactory factory = HibernateUtil.getInstance() ??????????????????????????? .getSessionFactory(); ???????? Session session = factory.getCurrentSession(); ???????? session.save(user); } |
是不是很方便的哈。
3.1.3 openSession與getCurrentSession的區(qū)別
嚴(yán)重注意下面3點(diǎn):
2? openSession一旦被調(diào)用,必須且一定要在finally塊中close,要不然你就等著out of memory吧;
2? 如果你使用的是getCurrentSession,那么你不能在finally塊中調(diào)用”session.close()”,不行你可以在finally塊中用try-catch把session.close();包起來,然后在catch{}塊中拋出這個(gè)exception,這個(gè)exception將會是:sessionhas been already closed。
因?yàn)?#xff1a;
l?? 如果你用的是getCurrentSession,那么它在session.commit()或者是session.rollback()時(shí)就已經(jīng)調(diào)用了一次session.close()了,因此你只要正確放置session.commit()與rollback()即可。
l?? 你必須在finally塊中調(diào)用”ThreadLocalSessionContext.unbind(factory);”,以使得當(dāng)前的事務(wù)結(jié)束時(shí)把session(即dbconnection)還回db connection pool中
2? 如果你使用的是getCurrentSession,那么就算你是一個(gè)簡單的select語句,也必須包含在:
| tran = session.beginTransaction(); //your select hibernate query tran.commit(); |
這樣的事務(wù)塊中,要不然它將會拋出這樣的一個(gè)錯(cuò)誤:
NoHibernate Session bound to thread, and configuration does not allow creation ofnon-transactional
看下面的例子:
| try { ??????????????????? factory = HibernateUtil.getInstance().getSessionFactory(); ??????????????????? Session session = factory.getCurrentSession(); ????????????????????tran = session.beginTransaction(); ??????????????????? TUser testUser = userDAO.getUserByID("1"); ??????????????????? log.info("user id===="+testUser.getId()+"? user name===="+testUser.getName()); ????????????????????tran.commit(); ?????????? } catch (Exception e) { ????????????????????tran.rollback(); ??????????????????? throw new Exception(e); ?????????? } finally { ??????????????????? ThreadLocalSessionContext.unbind(factory); ?????????????????? } |
可以看到我們的查詢是被tran=session.beginTransaction一直到tran.commit()或者是tran.rollback()結(jié)束的,如果,你把你的hibernate查詢移到了tran=session.beginTransaction的上面。。。就會拋上述這個(gè)錯(cuò)誤。
3.1.4 getCurrentSession帶來的問題
getCurrentSession非常好,不需要我們自己寫ThreadLocal只需要在hibernate.cfg的配置文件中聲音一下就可以獲得ThreadLocal的好處,便于我們劃分我們的程序的層次與封裝,帶也帶來了一定的性能問題。
特別是“如果你使用的是getCurrentSession,那么就算你是一個(gè)簡單的select語句,也必須包含在事務(wù)塊中”。這給我們帶來了很大的問題。
因此,本人建議,在碰到如果:
1.?????? 一個(gè)service方法中只有單個(gè)dao操作且此操作是一個(gè)select類的操作,請使用openSession,并且即時(shí)在finally塊中關(guān)閉它;
2.?????? 如果一個(gè)service方法中涉及到多個(gè)dao操作,請一定使用getCurrentSession;
3.?????? 如果一個(gè)service方法中混合著select操作,delete, update, insert操作。請按照下述原則:
1)????? 將屬于select的操作,單獨(dú)做成一個(gè)dao方法,該dao使用openSession并且在finally塊中及時(shí)關(guān)閉session,該dao只需要返回一個(gè)java的object如:List<Student>即可,如果出錯(cuò)將exception拋回給調(diào)用它的service方法。
2)????? 對于其它的delete, insert, update的dao操作,請使用getCurrentSession。
4.?????? 忌諱,把select類的操作放在“事務(wù)”中;
總結(jié)
以上是生活随笔為你收集整理的通向架构师的道路(第七天)之漫谈使用ThreadLocal改进你的层次的划分的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 通向架构师的道路(第六天)之漫谈基于数据
- 下一篇: 通向架构师的道路(第十天)之Axis2