1.DAO和事务管理的牵绊
事务管理的目的是保证数据操作的事务性(原子性、一致性、隔离性、持久性,即所谓的ACID),脱离了事务性,DAO照样可以顺利地进行数据操作
1.1.JDBC访问数据库
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();
//检查数据源autoCommit的设置
System.out.println("autoCommit:"+dataSource.getDefaultAutoCommit());
//插入一条记录,初始分数为10
jdbcTemplate.execute("INSERT INTO t_user(user_name,password,score,last_logon_time) VALUES('tom','123456',10,"+System.currentTimeMillis()+")");
//调用工作在无事务环境下的服务类方法,将分数添加到20分
userJdbcWithoutTransManagerService.addScore("tom",20);
//查看此时用户的分数
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中没有配置任何事务管理器,但是数据已经成功持久化到数据库中。在默认情况下,dataSource数据源的autoCommit被设置为ture,这也意味着所有通过JdbcTemplate执行的语句马上提交,没有事务。如果将dataSource的defaultAutoCommit设置为false,再次运行UserJdbcWithoutTransManagerService,将抛出错误,原因是新增及更改数据库的操作都没有提交到数据库,所以查询语句无法从数据库中查询匹配大记录而发生异常
对于强调读速度的应用,数据库本身可能就不支持事务,如使用MyISAM引擎的MySQL数据库。这时,无须在Spring应用中配置事务管理器,因为即使配置了,也没有用处
1.2.Hibernate访问数据库
对于Hibernate来说,情况就比较复杂了。因为Hibernate的事务管理拥有其自身的意义,它和Hibernate一级缓存存在密切的关系:在强调Session的save、update等方法时,Hibernate并不直接向数据库发送SQL语句,只在提交事务或flush一级缓存时才真正向数据库发送SQL。所以,即使底层数据库不支持事务,Hibernate的事务管理也是有一定好处的,不会对数据库操作的效率造成负面的影响。所以,如果使用Hiberbate数据访问技术,则没有理由不配置HibernateTransactionManager事务管理器
但是,如果不使用Hibernate事务管理器,Spring就会采取默认的事务管理器策略搜索(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);
//以下语句取消注释后,由于默认事务管理器不支持数据更改将报异常
//通过Hibernate操作数据库
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();
//①检查数据源autoCommit的设置
System.out.println("autoCommit:"+ basicDataSource.getDefaultAutoCommit());
//②插入一条记录,初始分数为10
jdbcTemplate.execute("INSERT INTO t_user(user_name,password,score,last_logon_time) VALUES('tom','123456',10,"+System.currentTimeMillis()+")");
//③调用工作在无事务环境下的服务类方法,将分数添加20分
service.addScore("tom",20);
//④查看此时用户的分数
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.应用分层的迷惑
任何项目不一定必须按照web,service和dao三层来进行分层开发,对于一个简单的项目来说,往往只有一些对数据库增、删、改、查的操作,此时,过分强调“面向接口编程”,除了会带来更多的类文件,并不会有什么好处。
3.事务方法嵌套调用的迷茫
3.1.Spring事务传播机制回顾
Spring事务的一个被讹传很广的说法是:一个事务方法不应该调用另一个事务方法,否则会产生两个事务。结果造成开发人员在设计事务方法时束手束脚
Spring对事务控制的支持统一在TransactionDefinition类中描述,该类有以下几个重要的接口方法
- int getPropagationBehavior():事务的传播级别
- int getIsolationLevel():事务的隔离级别
- int getTimeout():事务的过期时间
- boolean isReadOnly():事务的读/写特性
除了事务的传播行为,对于事务的其它特性,Spring是借助底层资源的功能来完成的,Spring无非是充当了一个代理的角色。但是事务的传播行为却是Spring凭借自身的框架提供的功能,是Spring提供给开发者最珍贵的礼物。
所谓事务传播行为,就是多个事务方法相互调用时,事务如何在这些方法间传播。Spring支持以下7种事务传播行为:
- PROPAGATION_REQUIRED:如果当前没有事务,就新建一个事务;如果已经存在一个事务,就加入到这个事务中。这是最常见的选择
- PROPAGATION_SUPPORTS:支持当前事务。如果没有事务,就以非事务方式执行
- PROPAGATION_MANDATORY:使用当前事务。如果没有当前事务,就抛出异常
- PROPAGATION_REQUIRES_NEW:新建事务。如果当前存在事务,就把当前事务挂起
- PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作。如果当前存在事务,就把当前事务挂起
- PROPAGATION_NEVER:以非事务方式执行。如果当前存在事务,就抛出异常
- PROPAGATION_NESTED:如果当前存在事务,就在嵌套事务内执行;如果当前没有事务,就执行与PROPAGATION_REQUIRED类似的操作
spring默认的事务传播行为是PROPAGATION_REQUIRED,它适合绝大多数场景,如果多个Service.methodX均工作在事务环境下(均被spring事务增强),且程序中存在调用链Service1.method1()->Service2.method2()->Service3.method3(),那么这3个服务类的3个方法通过Spring的事务传播机制都工作在同一个事务中
3.2相互嵌套的服务方法
@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();
//插入一条记录,初始分数为10
jdbcTemplate.execute("INSERT INTO t_user(user_name,password,score,last_logon_time) VALUES('tom','123456',10," + System.currentTimeMillis() + ")");
//调用工作在无事务环境下的服务类方法,将分数添加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的事务管理器是通过线程相关的ThreadLocal来保存数据访问基础设施(Connection实例)的,再结合Ioc和AOP实现高级声明式事务的功能,所以Spring的事务天然地和线程有着千丝万缕的联系
一个类能够以单实例的方式运行的前提是“无状态”,即一个类不能拥有状态化的成员变量。在传统的编程中,DAO必须持有一个Connection,而Connection就是状态化的对象.所以传统的DAO不能做成单实例的,每次要用时都必须创建一个新的实例。传统的Service由于内部包含了若干有状态的DAO成员变量,所以其本身也是有状态的
在Spring中,DAO和Service都以单实例的方式存在。Spring通过ThreadLocal将有状态的变量(如Connection等)本地线程化,达到另一个层面上的“线程无关”,从而实现线程安全。Spring不遗余力的将有状态的对象无状态化,就是要达到单实例化Bean的目的
4.2.启动独立线程调用事务方法
@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");
//插入一条记录,初始分数为10
jdbcTemplate.execute("INSERT INTO t_user(user_name,password,score,last_logon_time) VALUES('tom','123456',10," + System.currentTimeMillis() + ")");
//调用工作在无事务环境下的服务类方法,将分数添加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();
}
}
}
在相同线程中进行相互嵌套使用的事务方法工作在相同的事务中,如果这些相互嵌套调用的方法工作在不同的线程中,则不同线程下的事务方法工作在独立的事务中。
5.联合军中作战的混乱
5.1.Spring事务管理器的应对
Spring抽象的DAO体系兼容多种数据访问技术,比如Hibernate是一个非常优秀的ORM实现方案,但对底层SQL的控制不太方便,而MyBatis则通过模板化技术让用户方便地控制SQL,但没有Hibernate那样高的开发效率;自由度最高的就是直接使用Spring JDBC了,
但是代码是比较繁复的
Spring提供的事务管理能力能够很好应对ORM框架和JDBC联合,由于ORM框架的会话是对后者连接的封装,Spring会“足够智能地”在同一事务线程中让前者的会话封装后者的连接。因此,只要采用前者的事务管理器就行了。
序号 | 混合数据访问技术框架 | 事务管理器 |
---|---|---|
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混合框架的事务管理
下面展示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技术访问数据
System.out.println("before userService.updateLastLogonTime()..");
updateLastLogonTime(userName);
System.out.println("end userService.updateLastLogonTime()..");
//2、通过JDBC技术访问数据
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一级缓存中的内容刷新到数据
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");
//插入一条记录,初始分数为10
jdbcTemplate.execute("INSERT INTO t_user(user_name,password,score,last_logon_time) VALUES('tom','123456',10,"+System.currentTimeMillis()+")");
//调用工作在无事务环境下的服务类方法,将分数添加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事务管理器-->
<bean id="hiberManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager"
p:sessionFactory-ref="sessionFactory"/>
<!--使用UserServie及ScoreService共用方法都拥有事务-->
<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技术操作数据库,而在2处调用addScore()方法,该方法内部使用Spring JDBC技术操作数据库。3处显示调用了flush方法(),将Session中的缓存同步到数据库中(马上向数据库发送一条更新记录的SQL语句)。之所以要显示执行flush()方法,是因为在默认情况下,Hibernate对数据的更新只记录在一级缓存中,要等到事务提交后者显示调用flush()方法时才会将一级缓存中的数据同步到数据库中。
6.特殊方法成漏网之鱼
6.1.哪些方法补不能实施Spring AOP事务
由于Spring事务管理是基于接口代理或动态字节码技术,通过AOP实施事务增强的。对于接口动态代理的AOP事务增强来说,由于接口的方法都必须是public的,这就要求实现类的实现方法也必须是public的,同时不能使用static修饰符。基于CGLib字节码动态代理方案是通过扩展增强类,动态创建其子类的方式进行AOP增强织入的。由于使用final、static、private修饰符的方法都不能被子类覆盖,相应地这些方法将无法实施AOP增强。
6.2.事务增强遗漏实例
@Service("userService")
public class UserService implements UserServiceInterface{
//1、private方法因访问权限的限制,无法被子类覆盖
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 方法可以被子类覆盖,因此可以被动态字节码增强
public void method4() {
System.out.println("in method4");
}
//5、final方法不能被子类覆盖
public final void method5() {
System.out.println("in method5");
}
//6、protected方法可以被子类覆盖,因此可以被动态字节码增强
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");
//基于接口的动态代理
// 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事务增强的特殊方法并非就不工作在事务环境中,只要他们被外层的事务方法调用了,由于Spring事务管理的传播级别,内部方法也可以工作工作在外部方法所启动的事务上下文中。这些方法不能启动事务增强,是指这些方法不能启动事务,但是外层方法的事务上下文依旧可以顺利地传播到这些方法中。换句话说,这些无法启动事务的方法被无事务的上下文方法调用,则他们就工作在无事务的上下文中;反之,如果被有事务上下文的方法调用,则它们就工作在事务上下文中
7、数据连接泄漏
7.1、底层连接资源的访问问题
获取被Spring管控的数据连接,Spring提供了两种方法:其一是使用数据资源获取工具类;其二是对数据源(或衍生品,如Hibernate的SessionFactory)进行代理
7.2.Spring JDBC数据连接泄漏
@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);//②模拟程序代码的执行时间
} 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("连接数[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.事务环境下通过DataSourceUtils获取数据连接
Spring 提供了一个能从当前事务上下文中获取绑定的数据连接的工具类,即DataSourceUtils。Spring强调必须使用DataSourceUtils获取数据连接,Spring的JdbcTemplate内部也是通过DataSourceUtils来获取连接的。DataSourceUtils提供了若干获取和释放数据连接的静态方法,
- static Connection doGetConnection(DataSource dataSource):首先尝试从事务上
下文中获取连接,失败后再从数据源获取连接 - static Connection getConnection(DataSource dataSource):和doGetConnection()
方法的功能一样,实际上,其内部就是通过调用doGetConnection()方法获取连接的 - static void doReleaseConnection(Connection conn,DataSource dataSource):释
放连接,放回连接池中 - static void releaseConnection(Connection conn,DataSource dataSource):和doR
eleaseConnection()方法的功能一样,实际上,其内部就是通过调用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);//②模拟程序代码的执行时间
} 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("连接数[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.无事务环境下通过DataSourceUtils获取数据连接
如果DataSourceUtils在没有事务上下文的方法中使用getConnection()方法连接,那么依然会造成数据连接泄漏,需要按照下面所示显示释放掉连接
文档中用到的代码项目地址
@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);//②模拟程序代码的执行时间
} catch (Exception e) {
e.printStackTrace();
}finally {
//显示使用DataSourceUtils释放连接
DataSourceUtils.releaseConnection(conn,jdbcTemplate.getDataSource());
}
}
7.5.JdbcTemplate如何做到对连接泄漏的免疫
通过分析JdbcTemplate的代码,可以清楚的看到它开放的每个数据库操作的方法:首先使用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);
//显示的关闭Jdbc连接
DataSourceUtils.releaseConnection(con, this.getDataSource());
}
return var7;
}
7.6.使用TransactionAwareDataSourceProxy
如果不得以要显示获取数据连接,除了可以使用DataSourceUtils获取事务上下文绑定的连接,还可以通过TransactionAwareDataSourceProxy对数据源进行代理。数据源对象被代理后就具有事务上下文感知的能力,通过代理数据源的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/>
网友评论