一、什麼是ThreadLocal
早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal爲解決多線程程序的併發問題提供了一種新的思路。使用這個工具類可以很簡潔地編寫出優美的多線程程序。
ThreadLocal很容易讓人望文生義,想當然地認爲是一個“本地線程”。其實,ThreadLocal並不是一個Thread,而是Thread的局部變量,也許把它命名爲ThreadLocalVariable更容易讓人理解一些。
當使用ThreadLocal維護變量時,ThreadLocal爲每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。
從線程的角度看,目標變量就象是線程的本地變量,這也是類名中“Local”所要表達的意思。
線程局部變量並不是Java的新發明,很多語言(如IBM IBM XL FORTRAN)在語法層面就提供線程局部變量。在Java中沒有提供在語言級支持,而是變相地通過ThreadLocal的類提供支持。
所以,在Java中編寫線程局部變量的代碼相對來說要笨拙一些,因此造成線程局部變量沒有在Java開發者中得到很好的普及。
ThreadLocal的接口方法
ThreadLocal類接口很簡單,只有4個方法,我們先來了解一下:
² void set(Object value)
設置當前線程的線程局部變量的值。
² public Object get()
該方法返回當前線程所對應的線程局部變量。
² public void remove()
將當前線程局部變量的值刪除,目的是爲了減少內存的佔用,該方法是JDK 5.0新增的方法。需要指出的是,當線程結束後,對應該線程的局部變量將自動被垃圾回收,所以顯式調用該方法清除線程的局部變量並不是必須的操作,但它可以加快內存回收的速度。
² protected ObjectinitialValue()
返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是爲了讓子類覆蓋而設計的。這個方法是一個延遲調用方法,在線程第1次調用get()或set(Object)時才執行,並且僅執行1次。ThreadLocal中的缺省實現直接返回一個null。
值得一提的是,在JDK5.0中,ThreadLocal已經支持泛型,該類的類名已經變爲ThreadLocal<T>。API方法也相應進行了調整,新版本的API方法分別是void set(T value)、T get()以及T initialValue()。
一、來看一個實際案例
2.1 同一Service方法中調用多個Dao方法
可以看到,我們有一個Service方法,在該Service方法中調用多個Dao方法,所有在該Service方法中的的Dao都處於同一事務中。
該Service方法結束後,提交事務;
該Service方法中有任何錯,回滾事務;
2.2 傳統的做法
來看下面這段僞代碼
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){} } } |
每個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){} } } } |
如果我一個Service方法有調用一堆dao方法,先不說這樣寫首先破壞了OOP的封裝性原則,如果有一個dao多關了一個conn,那就會導致其它的dao得到的conn爲null,這種事在這樣的寫法下由其當你還有業務邏輯混合在一起時很容易發生。
筆者曾經遇見過2個項目,出現out of memory或者是connection pool has been leakage,經查代碼就是在每個dao中多關或者在service層中漏關,或者是每個dao有自己的conntionconn=getConnection(),然後還跑到Service層裏去關這個connection(那關什麼,關個P關!)。
當然,如果你說你在寫法上絕對promise絕對注意這樣的問題不會發生,但是我們來看看下面的這種做法,是否會比上面這個寫法更好呢?
2.3 Spring中的做法
先來看Spring中的寫法。
大家應該都很熟悉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,因爲用類反射結合xml很容易將aop 自動。。。這些東西加入我們的代碼中去是不是?我們只管寫dao方法,service方法,不需要關心在哪邊commit哪邊rollback何時connection,spring的聲明式事務會幫我們負責,這種風格我們稱爲“優雅”,各層間耦合度極大程度上的降低,封裝性好。
因此,我們可以總結出下面這些好處:
² Service層的方法只管開啓事務(如果講究點的還會設一個Transaction);
² 在該Service層中的所有dao使用該service方法中開啓的事務(即connection);
² Dao中每次只管getCurrentConnection(獲取當前的connection),與進行數據處理
² Dao層中如果發生錯誤就拋回Service層
² Service層中接到exception,在catch{}中rollback,在try{}未尾commit,在finally塊中關閉整個connection。
這。。。就是我們所說的ThreadLocal。
舉個更實際的例子再次來說明ThreadLocal:
我們有3個用戶訪問同一個service方法,該service方法內有3個dao方法爲一個完整事務,那麼整個web容器內只因該有3個connection,並且每個connection之間的狀態,彼此“隔離”。
我們下面一起來看我們如何用代碼實現類似於Spring的這種做法。
首先,根據我們的ThreadLocal的概念,我們先聲明一個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層(注意紅色標粗-好粗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,如果我把上述標粗(沒有加紅色)的地方,全部用AOP的方式從這塊代碼的外部“切”進去。。。是不是一個Spring裏的Service方法就誕生了?
下面來看一個完整的例子
2.6 使用ThreadLocal分離Service、DAO層
先來看錶結構:
T_Student表
T_ClassRoom表
T_Student_ClassRoom表
需求:
很簡單,T_ClassRoom表裏已經有值了,在插入T_Student表的數據時同時要給這個學生分配一個班級並且插入T_Student_ClassRoom表,這就是一個事務,這兩步中有任何一步出錯,事務必須回滾。
看來工程的結構吧:
下面開始放出所有源代碼:
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(); 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類(運行主類)
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在事務操作中也支持ThreadLocal的作法,我們這邊指的是不用Spring去做代理,而直接用Hibernate。即:
Service Method{ hbDAO1.doSomething(); hbDAO2.doSomething(); hbDAO3.doSomething(); 。。。 } |
Hibernate版本3後增加了新特性,即getCurrentSession()。
3.1 getCurrentSession與openSession的區別
3.1.1 openSession
我們傳統的做法是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塊中正確關閉session,但是,如果我們也遇上了這樣的case即:
Service Method{ hbDAO1.doSomething(); hbDAO2.doSomething(); hbDAO3.doSomething(); 。。。 } |
這時,我們如果用的是openSession,應該怎麼辦?
解決方案一:
自己用ThreadLocal模式寫一個SessionManagement類,來維護這個session。
解決方案二:
把在Service方法中打開的session,傳到每個dao方法中,使每個dao方法使用同一個session,最後在Service方法中去關閉它(很爛的做法)。
下面我們來看看Hibernate自身提供的getCurrentSession()的做法吧
3.1.2 getCurrentSession
要使用這個getCurrentSession,你的hibernate的設置必須如下(紅色加粗部分顯示-就喜歡粗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); } } |
而你的每個DAO方法中的代碼是這樣實現的:
public void addUser(TUser user) throws Exception { SessionFactory factory = HibernateUtil.getInstance() .getSessionFactory(); Session session = factory.getCurrentSession(); session.save(user); } |
是不是很方便的哈。
3.1.3 openSession與getCurrentSession的區別
嚴重注意下面3點:
² openSession一旦被調用,必須且一定要在finally塊中close,要不然你就等着out of memory吧;
² 如果你使用的是getCurrentSession,那麼你不能在finally塊中調用”session.close()”,不行你可以在finally塊中用try-catch把session.close();包起來,然後在catch{}塊中拋出這個exception,這個exception將會是:sessionhas been already closed。
因爲:
l 如果你用的是getCurrentSession,那麼它在session.commit()或者是session.rollback()時就已經調用了一次session.close()了,因此你只要正確放置session.commit()與rollback()即可。
l 你必須在finally塊中調用”ThreadLocalSessionContext.unbind(factory);”,以使得當前的事務結束時把session(即dbconnection)還回db connection pool中
² 如果你使用的是getCurrentSession,那麼就算你是一個簡單的select語句,也必須包含在:
tran = session.beginTransaction(); //your select hibernate query tran.commit(); |
這樣的事務塊中,要不然它將會拋出這樣的一個錯誤:
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()結束的,如果,你把你的hibernate查詢移到了tran=session.beginTransaction的上面。。。就會拋上述這個錯誤。
3.1.4 getCurrentSession帶來的問題
getCurrentSession非常好,不需要我們自己寫ThreadLocal只需要在hibernate.cfg的配置文件中聲音一下就可以獲得ThreadLocal的好處,便於我們劃分我們的程序的層次與封裝,帶也帶來了一定的性能問題。
特別是“如果你使用的是getCurrentSession,那麼就算你是一個簡單的select語句,也必須包含在事務塊中”。這給我們帶來了很大的問題。
因此,本人建議,在碰到如果:
1. 一個service方法中只有單個dao操作且此操作是一個select類的操作,請使用openSession,並且即時在finally塊中關閉它;
2. 如果一個service方法中涉及到多個dao操作,請一定使用getCurrentSession;
3. 如果一個service方法中混合着select操作,delete, update, insert操作。請按照下述原則:
1) 將屬於select的操作,單獨做成一個dao方法,該dao使用openSession並且在finally塊中及時關閉session,該dao只需要返回一個java的object如:List<Student>即可,如果出錯將exception拋回給調用它的service方法。
2) 對於其它的delete, insert, update的dao操作,請使用getCurrentSession。
4. 忌諱,把select類的操作放在“事務”中;