好記性不如爛筆頭99-spring3(20)-spring中的資料庫連接池泄露,99-spring3-spring
資料庫連接池泄露的問題,在JAVA開發中,是我們一直要面對的問題。但是呢,這種問題一般都是在很久很久以前的開發中,自從我們使用了hibernate或者spring的架構之後。資料庫連接泄露的問題,好像基本上就消失了。
一般的輕量級應用,就算有一些輕微泄露,如果訪問量強度不夠,那麼這個問題確實不顯著;
但是有的應用的資料庫訪問強度相當的高,一點點的泄露,最終都能把整個系統打爆。
使用了spring這些架構,能夠大幅度降低資料量連結泄露的問題的出現機率,但是並不是真的就徹底讓這些泄露這種問題消失了,一旦發生資料庫連接池泄露,要尋找這些問題,並不容易,因為很多人已經不知道這個事情是怎麼發生的。
這種隱藏的錯誤是非常難被發現的。
1) 前提和準備
我們需要利用log4j的info模式來觀察,因此我們需要系統能支援log4j運行;
我們需要觀察事務,因此我們要訪問一個資料庫。
create table FFM_USER( USERNAME VARCHAR2(64), PASSWORD VARCHAR2(64), LAST_LOGON_TIME VARCHAR2(64), CHARGE INTEGER)
裡面有一條資料: 在username欄位對應“ffm”
2) 業務情境
利用多線程啟動多個UserService裡面的方法,故意寫了一個能夠造成資料庫連接池泄露的範例。
注意:不要在實際項目中這樣使用
3) 實現spring架構下資料庫連接池泄露的原始碼
實作類別
package com.spring.jdbc;import java.sql.Connection;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;/** * 類比在spring架構下的資料庫連接泄露 * @author 範芳銘 */ @Service("userService")public class UserService { @Autowired private JdbcTemplate jdbcTemplate; @Transactional public void logon(String userName){ try{ //下面這個語句會造成資料庫連接池泄露【 標註A】 Connection conn = jdbcTemplate.getDataSource().getConnection(); String sql = "update ffm_user u set u.last_logon_time = ? where username =? "; jdbcTemplate.update(sql, System.currentTimeMillis(),userName); Thread.sleep(1000); //如果使用了【 標註A】,必須明顯調用close方法 //conn.close(); }catch(Exception e){ e.printStackTrace(); } }}
利用多線程啟動資料庫連接池泄露
package com.spring.jdbc;import org.apache.commons.dbcp.BasicDataSource;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.springframework.stereotype.Service;/** * 類比在spring架構下的資料庫連接泄露 * * @author 範芳銘 */ @Service("jdbcLeakService")public class EasyJdbcLeak { //用非同步多線程的方式啟動 public static void asynchThreadAdd(UserService userService,String username){ UserThread thread = new UserThread(userService,username); thread.start(); } public static void sleep(long time){ try{ Thread.sleep(time); }catch(Exception e){ e.printStackTrace(); } } private static class UserThread extends Thread{ private UserService userService; private String username; private UserThread(UserService userService,String username){ this.userService = userService; this.username = username; } public void run(){ try{ userService.logon(username); }catch(Exception e ){ e.printStackTrace(); } } } //獲得當前的串連數 public static void getConnNum(BasicDataSource basicDataSource){ System.out.println("當前串連數【活躍的:閒置】:" + basicDataSource.getNumActive() + ":" + basicDataSource.getNumIdle()); } public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("b_jdbc.xml"); UserService service = (UserService)ctx.getBean("userService"); BasicDataSource basicDataSource = (BasicDataSource)ctx.getBean("dataSource"); EasyJdbcLeak.getConnNum(basicDataSource); EasyJdbcLeak.asynchThreadAdd(service, "ffm"); EasyJdbcLeak.sleep(500); EasyJdbcLeak.getConnNum(basicDataSource); EasyJdbcLeak.asynchThreadAdd(service, "ffm"); EasyJdbcLeak.sleep(1000); EasyJdbcLeak.getConnNum(basicDataSource); EasyJdbcLeak.asynchThreadAdd(service, "ffm"); EasyJdbcLeak.sleep(1500); EasyJdbcLeak.getConnNum(basicDataSource); }}
4)設定檔b_jdbc.xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd"> <context:component-scan base-package="com.spring.jdbc"/> <context:property-placeholder location="classpath:dbconfig.properties" /> <bean id = "dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method = "close" p:driverClassName="${jdbc.o2o.driverClassName}" p:url="${jdbc.o2o.url}" p:username="${jdbc.o2o.username}" p:password="${jdbc.o2o.password}" /> <bean id="jdbcTemplate" class ="org.springframework.jdbc.core.JdbcTemplate" p:dataSource-ref="dataSource" /></beans>
5)運行系統
開啟INFO模式,啟動“類比容器運行”應用。查看日誌。
當前串連數【活躍的:閒置】:0:0
當前串連數【活躍的:閒置】:0:0
當前串連數【活躍的:閒置】:2:2
當前串連數【活躍的:閒置】:3:1
活躍的資料庫連接數越來越多,幾乎不會被釋放,當串連數用完之後,系統就會出問題。
解決spring資料庫連接池泄露的方法
//如果使用了【 標註A】,必須明顯調用close方法
//conn.close();
把 conn.close()的注釋去掉(最好是要放到finally中),把系統再運行下:
日誌
當前串連數【活躍的:閒置】:0:0
當前串連數【活躍的:閒置】:0:0
當前串連數【活躍的:閒置】:2:2
當前串連數【活躍的:閒置】:0:4
活躍的串連數運行之後,還是0,資料庫連接池正常了。
在代碼中,需要觀察是否有不正常的調用方法,這些方法是否安全,是架構師、開發人員都必須要密切關注的。