1.DAO和事務(wù)管理的牽絆
事務(wù)管理的目的是保證數(shù)據(jù)操作的事務(wù)性(原子性、一致性、隔離性、持久性,即所謂的ACID),脫離了事務(wù)性,DAO照樣可以順利地進行數(shù)據(jù)操作
1.1.JDBC訪問數(shù)據(jù)庫
public class UserJdbcWithoutTransManagerService {
@Autowired
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void addScore(String userName,int toAdd){
String sql="update t_user u set u.score=u.score+? where user_name=?";
jdbcTemplate.update(sql,toAdd,userName);
}
public static void main(String[] args) {
ApplicationContext ctx=new ClassPathXmlApplicationContext("jdbcWithoutTx.xml");
UserJdbcWithoutTransManagerService userJdbcWithoutTransManagerService= (UserJdbcWithoutTransManagerService) ctx.getBean("userService");
JdbcTemplate jdbcTemplate= (JdbcTemplate) ctx.getBean("jdbcTemplate");
BasicDataSource dataSource= (BasicDataSource) jdbcTemplate.getDataSource();
//檢查數(shù)據(jù)源autoCommit的設(shè)置
System.out.println("autoCommit:"+dataSource.getDefaultAutoCommit());
//插入一條記錄,初始分數(shù)為10
jdbcTemplate.execute("INSERT INTO t_user(user_name,password,score,last_logon_time) VALUES('tom','123456',10,"+System.currentTimeMillis()+")");
//調(diào)用工作在無事務(wù)環(huán)境下的服務(wù)類方法,將分數(shù)添加到20分
userJdbcWithoutTransManagerService.addScore("tom",20);
//查看此時用戶的分數(shù)
int score=jdbcTemplate.queryForObject("select score from t_user where user_name='tom'",Integer.class);
System.out.println("score:"+score);
jdbcTemplate.execute("delete from t_user where user_name='tom'");
}
}
<context:component-scan base-package="com"/>
<context:property-placeholder
location="classpath:jdbc.properties" ignore-unresolvable="true"/>
<!--apache.commons.dbcp.BasicDataSource-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}"/>
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dataSource"/>
<bean id="userService" class="com.servcie.UserJdbcWithoutTransManagerService"/>
在jdbcWithoutTx.xml中沒有配置任何事務(wù)管理器,但是數(shù)據(jù)已經(jīng)成功持久化到數(shù)據(jù)庫中。在默認情況下,dataSource數(shù)據(jù)源的autoCommit被設(shè)置為ture,這也意味著所有通過JdbcTemplate執(zhí)行的語句馬上提交,沒有事務(wù)。如果將dataSource的defaultAutoCommit設(shè)置為false,再次運行UserJdbcWithoutTransManagerService,將拋出錯誤,原因是新增及更改數(shù)據(jù)庫的操作都沒有提交到數(shù)據(jù)庫,所以查詢語句無法從數(shù)據(jù)庫中查詢匹配大記錄而發(fā)生異常
對于強調(diào)讀速度的應(yīng)用,數(shù)據(jù)庫本身可能就不支持事務(wù),如使用MyISAM引擎的MySQL數(shù)據(jù)庫。這時,無須在Spring應(yīng)用中配置事務(wù)管理器,因為即使配置了,也沒有用處
1.2.Hibernate訪問數(shù)據(jù)庫
對于Hibernate來說,情況就比較復(fù)雜了。因為Hibernate的事務(wù)管理擁有其自身的意義,它和Hibernate一級緩存存在密切的關(guān)系:在強調(diào)Session的save、update等方法時,Hibernate并不直接向數(shù)據(jù)庫發(fā)送SQL語句,只在提交事務(wù)或flush一級緩存時才真正向數(shù)據(jù)庫發(fā)送SQL。所以,即使底層數(shù)據(jù)庫不支持事務(wù),Hibernate的事務(wù)管理也是有一定好處的,不會對數(shù)據(jù)庫操作的效率造成負面的影響。所以,如果使用Hiberbate數(shù)據(jù)訪問技術(shù),則沒有理由不配置HibernateTransactionManager事務(wù)管理器
但是,如果不使用Hibernate事務(wù)管理器,Spring就會采取默認的事務(wù)管理器策略搜索(PROPAGATION_REQUIRED,readOnly)。如果有修改操作是不允許的,就會拋出異常
@Service("hiberService")
public class UserHibernateWithoutTransManagerService {
private HibernateTemplate hibernateTemplate;
@Autowired
public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
this.hibernateTemplate = hibernateTemplate;
}
public void addScore(String userName,int toAdd){
User user = hibernateTemplate.get(User.class,userName);
user.setScore(user.getScore()+toAdd);
//以下語句取消注釋后,由于默認事務(wù)管理器不支持數(shù)據(jù)更改將報異常
//通過Hibernate操作數(shù)據(jù)庫
hibernateTemplate.update(user);
}
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/smart/withouttx/hiber/hiberWithoutTx.xml");
UserHibernateWithoutTransManagerService service = (UserHibernateWithoutTransManagerService)ctx.getBean("hiberService");
JdbcTemplate jdbcTemplate = (JdbcTemplate)ctx.getBean("jdbcTemplate");
BasicDataSource basicDataSource = (BasicDataSource)jdbcTemplate.getDataSource();
//①檢查數(shù)據(jù)源autoCommit的設(shè)置
System.out.println("autoCommit:"+ basicDataSource.getDefaultAutoCommit());
//②插入一條記錄,初始分數(shù)為10
jdbcTemplate.execute("INSERT INTO t_user(user_name,password,score,last_logon_time) VALUES('tom','123456',10,"+System.currentTimeMillis()+")");
//③調(diào)用工作在無事務(wù)環(huán)境下的服務(wù)類方法,將分數(shù)添加20分
service.addScore("tom",20);
//④查看此時用戶的分數(shù)
int score = jdbcTemplate.queryForObject("SELECT score FROM t_user WHERE user_name ='tom'", Integer.class);
System.out.println("score:"+score);
jdbcTemplate.execute("DELETE FROM t_user WHERE user_name='tom'");
}
}
<context:component-scan base-package="com.smart.withouttx.hiber"/>
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}"/>
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dataSource"/>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"
p:dataSource-ref="dataSource">
<property name="annotatedClasses">
<list>
<value>smart.User</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
<bean id="hibernateTemplate"
class="org.springframework.orm.hibernate4.HibernateTemplate"
p:sessionFactory-ref="sessionFactory"/>
<bean id="userService" class="smart.withouttx.jdbc.UserJdbcWithoutTransManagerService"/>
<bean id="hiberService" class="smart.withouttx.hiber.UserHibernateWithoutTransManagerService"/>
2.應(yīng)用分層的迷惑
任何項目不一定必須按照web,service和dao三層來進行分層開發(fā),對于一個簡單的項目來說,往往只有一些對數(shù)據(jù)庫增、刪、改、查的操作,此時,過分強調(diào)“面向接口編程”,除了會帶來更多的類文件,并不會有什么好處。
3.事務(wù)方法嵌套調(diào)用的迷茫
3.1.Spring事務(wù)傳播機制回顧
Spring事務(wù)的一個被訛傳很廣的說法是:一個事務(wù)方法不應(yīng)該調(diào)用另一個事務(wù)方法,否則會產(chǎn)生兩個事務(wù)。結(jié)果造成開發(fā)人員在設(shè)計事務(wù)方法時束手束腳
Spring對事務(wù)控制的支持統(tǒng)一在TransactionDefinition類中描述,該類有以下幾個重要的接口方法
- int getPropagationBehavior():事務(wù)的傳播級別
- int getIsolationLevel():事務(wù)的隔離級別
- int getTimeout():事務(wù)的過期時間
- boolean isReadOnly():事務(wù)的讀/寫特性
除了事務(wù)的傳播行為,對于事務(wù)的其它特性,Spring是借助底層資源的功能來完成的,Spring無非是充當(dāng)了一個代理的角色。但是事務(wù)的傳播行為卻是Spring憑借自身的框架提供的功能,是Spring提供給開發(fā)者最珍貴的禮物。
所謂事務(wù)傳播行為,就是多個事務(wù)方法相互調(diào)用時,事務(wù)如何在這些方法間傳播。Spring支持以下7種事務(wù)傳播行為:
- PROPAGATION_REQUIRED:如果當(dāng)前沒有事務(wù),就新建一個事務(wù);如果已經(jīng)存在一個事務(wù),就加入到這個事務(wù)中。這是最常見的選擇
- PROPAGATION_SUPPORTS:支持當(dāng)前事務(wù)。如果沒有事務(wù),就以非事務(wù)方式執(zhí)行
- PROPAGATION_MANDATORY:使用當(dāng)前事務(wù)。如果沒有當(dāng)前事務(wù),就拋出異常
- PROPAGATION_REQUIRES_NEW:新建事務(wù)。如果當(dāng)前存在事務(wù),就把當(dāng)前事務(wù)掛起
- PROPAGATION_NOT_SUPPORTED:以非事務(wù)方式執(zhí)行操作。如果當(dāng)前存在事務(wù),就把當(dāng)前事務(wù)掛起
- PROPAGATION_NEVER:以非事務(wù)方式執(zhí)行。如果當(dāng)前存在事務(wù),就拋出異常
- PROPAGATION_NESTED:如果當(dāng)前存在事務(wù),就在嵌套事務(wù)內(nèi)執(zhí)行;如果當(dāng)前沒有事務(wù),就執(zhí)行與PROPAGATION_REQUIRED類似的操作
spring默認的事務(wù)傳播行為是PROPAGATION_REQUIRED,它適合絕大多數(shù)場景,如果多個Service.methodX均工作在事務(wù)環(huán)境下(均被spring事務(wù)增強),且程序中存在調(diào)用鏈Service1.method1()->Service2.method2()->Service3.method3(),那么這3個服務(wù)類的3個方法通過Spring的事務(wù)傳播機制都工作在同一個事務(wù)中
3.2相互嵌套的服務(wù)方法
@Service("userService")
public class UserService extends BaseService {
private JdbcTemplate jdbcTemplate;
private ScoreService scoreService;
@Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Autowired
public void setScoreService(ScoreService scoreService) {
this.scoreService = scoreService;
}
public void logon(String userName) {
System.out.println("before userService.updateLastLogonTime...");
updateLastLogonTime(userName);
System.out.println("after userService.updateLastLogonTime...");
System.out.println("before scoreService.addScore...");
scoreService.addScore(userName, 20);
System.out.println("after scoreService.addScore...");
}
public void updateLastLogonTime(String userName) {
String sql = "UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?";
jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
}
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/smart/nestcall/applicatonContext.xml");
UserService service = (UserService) ctx.getBean("userService");
JdbcTemplate jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");
BasicDataSource basicDataSource = (BasicDataSource) jdbcTemplate.getDataSource();
//插入一條記錄,初始分數(shù)為10
jdbcTemplate.execute("INSERT INTO t_user(user_name,password,score,last_logon_time) VALUES('tom','123456',10," + System.currentTimeMillis() + ")");
//調(diào)用工作在無事務(wù)環(huán)境下的服務(wù)類方法,將分數(shù)添加20分
System.out.println("before userService.logon method...");
service.logon("tom");
System.out.println("after userService.logon method...");
jdbcTemplate.execute("DELETE FROM t_user WHERE user_name='tom'");
}
}
@Service("scoreUserService")
public class ScoreService extends BaseService{
private JdbcTemplate jdbcTemplate;
@Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void addScore(String userName, int toAdd) {
String sql = "UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?";
jdbcTemplate.update(sql, toAdd, userName);
}
}
4.多線程的困惑
4.1.Spring通過單實例化Bean簡化多線程問題
由于Spring的事務(wù)管理器是通過線程相關(guān)的ThreadLocal來保存數(shù)據(jù)訪問基礎(chǔ)設(shè)施(Connection實例)的,再結(jié)合Ioc和AOP實現(xiàn)高級聲明式事務(wù)的功能,所以Spring的事務(wù)天然地和線程有著千絲萬縷的聯(lián)系
一個類能夠以單實例的方式運行的前提是“無狀態(tài)”,即一個類不能擁有狀態(tài)化的成員變量。在傳統(tǒng)的編程中,DAO必須持有一個Connection,而Connection就是狀態(tài)化的對象.所以傳統(tǒng)的DAO不能做成單實例的,每次要用時都必須創(chuàng)建一個新的實例。傳統(tǒng)的Service由于內(nèi)部包含了若干有狀態(tài)的DAO成員變量,所以其本身也是有狀態(tài)的
在Spring中,DAO和Service都以單實例的方式存在。Spring通過ThreadLocal將有狀態(tài)的變量(如Connection等)本地線程化,達到另一個層面上的“線程無關(guān)”,從而實現(xiàn)線程安全。Spring不遺余力的將有狀態(tài)的對象無狀態(tài)化,就是要達到單實例化Bean的目的
4.2.啟動獨立線程調(diào)用事務(wù)方法
@Service("userService")
public class UserService extends BaseService {
private JdbcTemplate jdbcTemplate;
private ScoreService scoreService;
@Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Autowired
public void setScoreService(ScoreService scoreService) {
this.scoreService = scoreService;
}
public void logon(String userName) {
System.out.println("before userService.updateLastLogonTime method...");
updateLastLogonTime(userName);
System.out.println("after userService.updateLastLogonTime method...");
// scoreService.addScore(userName, 20);
Thread myThread = new MyThread(this.scoreService, userName, 20);//使用一個新線程運行
myThread.start();
}
public void updateLastLogonTime(String userName) {
String sql = "UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?";
jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
}
private class MyThread extends Thread {
private ScoreService scoreService;
private String userName;
private int toAdd;
private MyThread(ScoreService scoreService, String userName, int toAdd) {
this.scoreService = scoreService;
this.userName = userName;
this.toAdd = toAdd;
}
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("before scoreService.addScor method...");
scoreService.addScore(userName, toAdd);
System.out.println("after scoreService.addScor method...");
}
}
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/smart/multithread/applicatonContext.xml");
UserService service = (UserService) ctx.getBean("userService");
JdbcTemplate jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");
//插入一條記錄,初始分數(shù)為10
jdbcTemplate.execute("INSERT INTO t_user(user_name,password,score,last_logon_time) VALUES('tom','123456',10," + System.currentTimeMillis() + ")");
//調(diào)用工作在無事務(wù)環(huán)境下的服務(wù)類方法,將分數(shù)添加20分
System.out.println("before userService.logon method...");
service.logon("tom");
System.out.println("after userService.logon method...");
jdbcTemplate.execute("DELETE FROM t_user WHERE user_name='tom'");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在相同線程中進行相互嵌套使用的事務(wù)方法工作在相同的事務(wù)中,如果這些相互嵌套調(diào)用的方法工作在不同的線程中,則不同線程下的事務(wù)方法工作在獨立的事務(wù)中。
5.聯(lián)合軍中作戰(zhàn)的混亂
5.1.Spring事務(wù)管理器的應(yīng)對
Spring抽象的DAO體系兼容多種數(shù)據(jù)訪問技術(shù),比如Hibernate是一個非常優(yōu)秀的ORM實現(xiàn)方案,但對底層SQL的控制不太方便,而MyBatis則通過模板化技術(shù)讓用戶方便地控制SQL,但沒有Hibernate那樣高的開發(fā)效率;自由度最高的就是直接使用Spring JDBC了,
但是代碼是比較繁復(fù)的
Spring提供的事務(wù)管理能力能夠很好應(yīng)對ORM框架和JDBC聯(lián)合,由于ORM框架的會話是對后者連接的封裝,Spring會“足夠智能地”在同一事務(wù)線程中讓前者的會話封裝后者的連接。因此,只要采用前者的事務(wù)管理器就行了。
| 序號 | 混合數(shù)據(jù)訪問技術(shù)框架 | 事務(wù)管理器 |
|---|---|---|
| 1 | Hibernate+Spring JDBC或MyBatis | org.springframework.orm.hibernateX.HibernateTransactionManager |
| 2 | JPA+Spring JDBC或MyBatis | org.springframework.orm.jpa.JpaTransactionManager |
| 3 | JDO+Spring JDBC或MyBatis | org.springframework.orm.jdo.JdoTransactionManager |
5.2.Hibernate+Spring JDBC混合框架的事務(wù)管理
下面展示ORM框架+JDBC框架的情況
文檔中用到的代碼項目地址
@Service("userService")
public class UserService extends BaseService {
private HibernateTemplate hibernateTemplate;
private ScoreService scoreService;
@Autowired
public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
this.hibernateTemplate = hibernateTemplate;
}
@Autowired
public void setScoreService(ScoreService scoreService) {
this.scoreService = scoreService;
}
public void logon(String userName) {
//1、通過Hibernate技術(shù)訪問數(shù)據(jù)
System.out.println("before userService.updateLastLogonTime()..");
updateLastLogonTime(userName);
System.out.println("end userService.updateLastLogonTime()..");
//2、通過JDBC技術(shù)訪問數(shù)據(jù)
System.out.println("before scoreService.addScore()..");
scoreService.addScore(userName, 20);
System.out.println("end scoreService.addScore()..");
}
public void updateLastLogonTime(String userName) {
User user = hibernateTemplate.get(User.class,userName);
user.setLastLogonTime(System.currentTimeMillis());
hibernateTemplate.update(user);
//將hibernate一級緩存中的內(nèi)容刷新到數(shù)據(jù)
hibernateTemplate.flush();//③請看下文的分析
}
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/smart/mixdao/applicationContext.xml");
UserService service = (UserService) ctx.getBean("userService");
JdbcTemplate jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");
//插入一條記錄,初始分數(shù)為10
jdbcTemplate.execute("INSERT INTO t_user(user_name,password,score,last_logon_time) VALUES('tom','123456',10,"+System.currentTimeMillis()+")");
//調(diào)用工作在無事務(wù)環(huán)境下的服務(wù)類方法,將分數(shù)添加20分
System.out.println("before userService.logon()..");
service.logon("tom");
System.out.println("after userService.logon()..");
int score = jdbcTemplate.queryForObject("SELECT score FROM t_user WHERE user_name ='tom'", Integer.class);
System.out.println("score:"+score);
jdbcTemplate.execute("DELETE FROM t_user WHERE user_name='tom'");
}
}
<context:component-scan base-package="com.smart.mixdao"/>
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}"/>
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dataSource"/>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"
p:dataSource-ref="dataSource">
<property name="annotatedClasses">
<list>
<value>com.smart.User</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
<bean id="hibernateTemplate"
class="org.springframework.orm.hibernate4.HibernateTemplate"
p:sessionFactory-ref="sessionFactory"/>
<!--使用Hibernate事務(wù)管理器-->
<bean id="hiberManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager"
p:sessionFactory-ref="sessionFactory"/>
<!--使用UserServie及ScoreService共用方法都擁有事務(wù)-->
<aop:config proxy-target-class="true">
<aop:pointcut id="serviceJdbcMethod"
expression="within(com.smart.mixdao.BaseService+)"/>
<aop:advisor pointcut-ref="serviceJdbcMethod"
advice-ref="hiberAdvice"/>
</aop:config>
<tx:advice id="hiberAdvice" transaction-manager="hiberManager">
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
在1處使用Hibernate技術(shù)操作數(shù)據(jù)庫,而在2處調(diào)用addScore()方法,該方法內(nèi)部使用Spring JDBC技術(shù)操作數(shù)據(jù)庫。3處顯示調(diào)用了flush方法(),將Session中的緩存同步到數(shù)據(jù)庫中(馬上向數(shù)據(jù)庫發(fā)送一條更新記錄的SQL語句)。之所以要顯示執(zhí)行flush()方法,是因為在默認情況下,Hibernate對數(shù)據(jù)的更新只記錄在一級緩存中,要等到事務(wù)提交后者顯示調(diào)用flush()方法時才會將一級緩存中的數(shù)據(jù)同步到數(shù)據(jù)庫中。
6.特殊方法成漏網(wǎng)之魚
6.1.哪些方法補不能實施Spring AOP事務(wù)
由于Spring事務(wù)管理是基于接口代理或動態(tài)字節(jié)碼技術(shù),通過AOP實施事務(wù)增強的。對于接口動態(tài)代理的AOP事務(wù)增強來說,由于接口的方法都必須是public的,這就要求實現(xiàn)類的實現(xiàn)方法也必須是public的,同時不能使用static修飾符?;贑GLib字節(jié)碼動態(tài)代理方案是通過擴展增強類,動態(tài)創(chuàng)建其子類的方式進行AOP增強織入的。由于使用final、static、private修飾符的方法都不能被子類覆蓋,相應(yīng)地這些方法將無法實施AOP增強。
6.2.事務(wù)增強遺漏實例
@Service("userService")
public class UserService implements UserServiceInterface{
//1、private方法因訪問權(quán)限的限制,無法被子類覆蓋
private void method1() {
System.out.println("in method1");
}
//2、final方法無法被子類覆蓋
public final void method2() {
System.out.println("in method2");
}
//3、static 是類級別的方法,無法被子類覆蓋
public static void method3() {
System.out.println("in method3");
}
//4、public 方法可以被子類覆蓋,因此可以被動態(tài)字節(jié)碼增強
public void method4() {
System.out.println("in method4");
}
//5、final方法不能被子類覆蓋
public final void method5() {
System.out.println("in method5");
}
//6、protected方法可以被子類覆蓋,因此可以被動態(tài)字節(jié)碼增強
protected void method6(){
System.out.println("in method6");
}
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/smart/special/applicationContext.xml");
UserService service = (UserService) ctx.getBean("userService");
System.out.println("before method1");
service.method1();
System.out.println("after method1");
System.out.println("before method2");
service.method2();
System.out.println("after method2");
System.out.println("before method3");
service.method3();
System.out.println("after method3");
System.out.println("before method4");
service.method4();
System.out.println("after method4");
System.out.println("before method5");
service.method5();
System.out.println("after method5");
System.out.println("before method6");
service.method6();
System.out.println("after method6");
//基于接口的動態(tài)代理
// UserServiceInterface service = (UserServiceInterface) ctx.getBean("userService");
// System.out.println("before method4");
// service.method4();
// System.out.println("after method4");
//
// System.out.println("before method5");
// service.method5();
// System.out.println("after method5");
}
}
這些不能被Spring事務(wù)增強的特殊方法并非就不工作在事務(wù)環(huán)境中,只要他們被外層的事務(wù)方法調(diào)用了,由于Spring事務(wù)管理的傳播級別,內(nèi)部方法也可以工作工作在外部方法所啟動的事務(wù)上下文中。這些方法不能啟動事務(wù)增強,是指這些方法不能啟動事務(wù),但是外層方法的事務(wù)上下文依舊可以順利地傳播到這些方法中。換句話說,這些無法啟動事務(wù)的方法被無事務(wù)的上下文方法調(diào)用,則他們就工作在無事務(wù)的上下文中;反之,如果被有事務(wù)上下文的方法調(diào)用,則它們就工作在事務(wù)上下文中
7、數(shù)據(jù)連接泄漏
7.1、底層連接資源的訪問問題
獲取被Spring管控的數(shù)據(jù)連接,Spring提供了兩種方法:其一是使用數(shù)據(jù)資源獲取工具類;其二是對數(shù)據(jù)源(或衍生品,如Hibernate的SessionFactory)進行代理
7.2.Spring JDBC數(shù)據(jù)連接泄漏
@Service("jdbcUserService")
public class JdbcUserService {
private JdbcTemplate jdbcTemplate;
@Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Transactional
public void logon(String userName) {
try {
Connection conn = jdbcTemplate.getDataSource().getConnection();
// Connection conn = DataSourceUtils.getConnection(jdbcTemplate.getDataSource());
String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";
jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
Thread.sleep(1000);//②模擬程序代碼的執(zhí)行時間
} catch (Exception e) {
e.printStackTrace();
}
}
public static void asynchrLogon(JdbcUserService userService, String userName) {
UserServiceRunner runner = new UserServiceRunner(userService, userName);
runner.start();
}
public static void sleep(long time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void reportConn(BasicDataSource basicDataSource) {
System.out.println("連接數(shù)[active:idle]-[" +
basicDataSource.getNumActive()+":"+basicDataSource.getNumIdle()+"]");
}
private static class UserServiceRunner extends Thread {
private JdbcUserService userService;
private String userName;
public UserServiceRunner(JdbcUserService userService, String userName) {
this.userService = userService;
this.userName = userName;
}
public void run() {
userService.logon(userName);
}
}
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/smart/connleak/applicatonContext.xml");
JdbcUserService userService = (JdbcUserService) ctx.getBean("jdbcUserService");
BasicDataSource basicDataSource = (BasicDataSource) ctx.getBean("dataSource");
JdbcUserService.reportConn(basicDataSource);
JdbcUserService.asynchrLogon(userService, "tom");
JdbcUserService.sleep(500);
JdbcUserService.reportConn(basicDataSource);
JdbcUserService.sleep(2000);
JdbcUserService.reportConn(basicDataSource);
JdbcUserService.asynchrLogon(userService, "john");
JdbcUserService.sleep(500);
JdbcUserService.reportConn(basicDataSource);
JdbcUserService.sleep(2000);
JdbcUserService.reportConn(basicDataSource);
}
}
7.3.事務(wù)環(huán)境下通過DataSourceUtils獲取數(shù)據(jù)連接
Spring 提供了一個能從當(dāng)前事務(wù)上下文中獲取綁定的數(shù)據(jù)連接的工具類,即DataSourceUtils。Spring強調(diào)必須使用DataSourceUtils獲取數(shù)據(jù)連接,Spring的JdbcTemplate內(nèi)部也是通過DataSourceUtils來獲取連接的。DataSourceUtils提供了若干獲取和釋放數(shù)據(jù)連接的靜態(tài)方法,
- static Connection doGetConnection(DataSource dataSource):首先嘗試從事務(wù)上
下文中獲取連接,失敗后再從數(shù)據(jù)源獲取連接 - static Connection getConnection(DataSource dataSource):和doGetConnection()
方法的功能一樣,實際上,其內(nèi)部就是通過調(diào)用doGetConnection()方法獲取連接的 - static void doReleaseConnection(Connection conn,DataSource dataSource):釋
放連接,放回連接池中 - static void releaseConnection(Connection conn,DataSource dataSource):和doR
eleaseConnection()方法的功能一樣,實際上,其內(nèi)部就是通過調(diào)用doReleaseConnection()方法獲取連接的
文檔中用到的代碼項目地址
@Service("jdbcUserService")
public class JdbcUserService {
private JdbcTemplate jdbcTemplate;
@Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Transactional
public void logon(String userName) {
try {
// Connection conn = jdbcTemplate.getDataSource().getConnection();
Connection conn = DataSourceUtils.getConnection(jdbcTemplate.getDataSource());
String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";
jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
Thread.sleep(1000);//②模擬程序代碼的執(zhí)行時間
} catch (Exception e) {
e.printStackTrace();
}
}
public static void asynchrLogon(JdbcUserService userService, String userName) {
UserServiceRunner runner = new UserServiceRunner(userService, userName);
runner.start();
}
public static void sleep(long time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void reportConn(BasicDataSource basicDataSource) {
System.out.println("連接數(shù)[active:idle]-[" +
basicDataSource.getNumActive()+":"+basicDataSource.getNumIdle()+"]");
}
private static class UserServiceRunner extends Thread {
private JdbcUserService userService;
private String userName;
public UserServiceRunner(JdbcUserService userService, String userName) {
this.userService = userService;
this.userName = userName;
}
public void run() {
userService.logon(userName);
}
}
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/smart/connleak/applicatonContext.xml");
JdbcUserService userService = (JdbcUserService) ctx.getBean("jdbcUserService");
BasicDataSource basicDataSource = (BasicDataSource) ctx.getBean("dataSource");
JdbcUserService.reportConn(basicDataSource);
JdbcUserService.asynchrLogon(userService, "tom");
JdbcUserService.sleep(500);
JdbcUserService.reportConn(basicDataSource);
JdbcUserService.sleep(2000);
JdbcUserService.reportConn(basicDataSource);
JdbcUserService.asynchrLogon(userService, "john");
JdbcUserService.sleep(500);
JdbcUserService.reportConn(basicDataSource);
JdbcUserService.sleep(2000);
JdbcUserService.reportConn(basicDataSource);
}
}
7.4.無事務(wù)環(huán)境下通過DataSourceUtils獲取數(shù)據(jù)連接
如果DataSourceUtils在沒有事務(wù)上下文的方法中使用getConnection()方法連接,那么依然會造成數(shù)據(jù)連接泄漏,需要按照下面所示顯示釋放掉連接
文檔中用到的代碼項目地址
@Transactional
public void logon(String userName) {
Connection conn =null;
try {
// Connection conn = jdbcTemplate.getDataSource().getConnection();
conn = DataSourceUtils.getConnection(jdbcTemplate.getDataSource());
String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";
jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
Thread.sleep(1000);//②模擬程序代碼的執(zhí)行時間
} catch (Exception e) {
e.printStackTrace();
}finally {
//顯示使用DataSourceUtils釋放連接
DataSourceUtils.releaseConnection(conn,jdbcTemplate.getDataSource());
}
}
7.5.JdbcTemplate如何做到對連接泄漏的免疫
通過分析JdbcTemplate的代碼,可以清楚的看到它開放的每個數(shù)據(jù)庫操作的方法:首先使用DataSourceUtils獲取連接,然后在方法返回之前使用DataSourceUtils釋放連接
文檔中用到的代碼項目地址
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
//獲取連接
Connection con = DataSourceUtils.getConnection(this.getDataSource());
Statement stmt = null;
Object var7;
try {
Connection conToUse = con;
if (this.nativeJdbcExtractor != null && this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
stmt = conToUse.createStatement();
this.applyStatementSettings(stmt);
Statement stmtToUse = stmt;
if (this.nativeJdbcExtractor != null) {
stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
}
T result = action.doInStatement(stmtToUse);
this.handleWarnings(stmt);
var7 = result;
} catch (SQLException var11) {
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, this.getDataSource());
con = null;
throw this.getExceptionTranslator().translate("StatementCallback", getSql(action), var11);
} finally {
JdbcUtils.closeStatement(stmt);
//顯示的關(guān)閉Jdbc連接
DataSourceUtils.releaseConnection(con, this.getDataSource());
}
return var7;
}
7.6.使用TransactionAwareDataSourceProxy
如果不得以要顯示獲取數(shù)據(jù)連接,除了可以使用DataSourceUtils獲取事務(wù)上下文綁定的連接,還可以通過TransactionAwareDataSourceProxy對數(shù)據(jù)源進行代理。數(shù)據(jù)源對象被代理后就具有事務(wù)上下文感知的能力,通過代理數(shù)據(jù)源的getConnection()方法獲取連接和使用DataSourceUtils.getConnection()方法獲取連接是一樣的
文檔中用到的代碼項目地址
<context:component-scan base-package="smart.connleak"/>
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="originDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}"/>
<!--<bean id="dataSource"
class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
<property name="targetDataSource">
<bean class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}"/>
</property>
</bean> -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy"
p:targetDataSource-ref="originDataSource"/>
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dataSource"/>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
<tx:annotation-driven/>