解决spring+c3p0数据库连接一直增加的问题

来源:互联网 发布:阿里云打印域名证书 编辑:程序博客网 时间:2024/05/16 09:27

解决spring+c3p0数据库连接一直增加的问题

  • 问题描述
  • spring+c3p0的配置
  • 针对不同情况的解决方案

问题描述:

SSH框架,hibernate的配置由spring接管,数据源的配置放在spring的配置文件中,当数据源不配置数据库连接池时,数据库的连接处于正常状态,中规中矩地每次需要了建立连接,用完了释放连接,没有任何资源复用的概念。访问量小的时候问题不大,访问量大的时候问题就来了,1000个用户重复了1000个建立连接和释放连接的动作,显而易见的极大资源浪费,连接池的基本目的即在此处,把建立好的连接进行复用,节省资源的同时也一定程度提高用户访问速度,因为对数据库的操作不用每次重新建立连接了。可是当引入c3p0数据库连接池之后发现,每一个接口的访问都会增加一定数量的数据库连接,在数据库命令窗口用root用户登录,使用show processlist;命令查看连接数,数量随着接口的访问持续增加,并且在接口访问结束之后几乎不会有连接释放,等待多久连接数依旧。

贴一下applicationContext.xml文件中的配置代码:

数据源配置:

<bean id="dataSource"        class="com.mchange.v2.c3p0.ComboPooledDataSource"  destroy-method="close" >        <property name="driverClass"            value="com.mysql.jdbc.Driver">        </property>        <property name="jdbcUrl"            value="jdbc:mysql://xx.xx.xx.xx:3306/icheck_sziit">        </property>        <property name="user" value="xxx"></property>        <property name="password" value="xxx"></property>        <!--连接池中保留的最小连接数。-->        <property name="minPoolSize"><value>5</value></property>        <!--连接池中保留的最大连接数。Default: 15 -->        <property name="maxPoolSize"><value>30</value></property>        <!--初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->        <property name="initialPoolSize"><value>10</value></property>        <!--最大空闲时间,30秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->        <property name="maxIdleTime"><value>20</value></property>        <!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->        <property name="acquireIncrement"><value>5</value></property>        <!--每60秒检查所有连接池中的空闲连接。Default: 0 -->        <property name="idleConnectionTestPeriod"><value>3600</value></property>        <!--定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 -->        <property name="acquireRetryAttempts"><value>3</value></property>    </bean> 

更多关于c3p0的配置请自行搜索进行深入学习。
spring接管hibernate时对hibernate的配置:

    <bean id="sessionFactory"        class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">        <property name="dataSource">            <ref bean="dataSource" />        </property>        <property name="hibernateProperties">            <props>                <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>                <prop key="hibernate.show_sql">true</prop>                <prop key="hibernate.format_sql">true</prop>                <prop key="hibernate.generate_statistics">true</prop>                <prop key="hibernate.connection.release_mode">auto</prop>                <prop key="hibernate.autoReconnect">true</prop>             </props>        </property>        <property name="mappingResources">            <list>                <value>                    com/xx/xx/xx/StudentCourse.hbm.xml                </value>            </list>        </property></bean>

此处注意:当使用spring接管了hibernate时,对数据库连接池的配置不可以放在hibernate配置中,网上有的帖子在解决此博文问题时提到了关于c3p0的配置位置,正确的位置就是如上面的配置文件所示,将c3p0配置在spring的dataSource中,而不是放在配置hibernate的代码块中,否则c3p0不起作用。

然后,正常运行的时候就开始出现上面所描述的问题,现在来写总结解决方案(本人初学者,也是遇到问题解决问题,大部分方法均来自于各大论坛的高手,引用的内容会一一注明转帖地址,感谢前辈的无私分享,才得以站在巨人的肩上,本帖旨在总结清晰的思路,让在此之后的人能走更少的弯路,省下时间来去走得更远。)遇到此问题的人不在少数,鉴于大家情况都不完全一样,请自行斟酌最好的方案,以下的方案均针对不同的人有效。

针对不同情况的解决方案

情况一:

最简单的情况,c3p0或者是spring的配置出了问题,需要反复检查代码,确保没有多了一个空格什么的,确保配置生效了,这个就靠细心了。

情况二:

大部分人遇到的均是此种情况,当spring和hibernate版本较老时,首先需要考虑在自己的DAO类中是否有自己编写的针对数据库的操作,不管DAO类是根据工具逆向工程生成的还是自己手动编写,均需要检查代码中是否有类似于getSessionFactory().openSession();的操作,需要确保在该openSession操作之后关闭会话,必须有显式的关闭会话的地方,否则连接不会自动释放,这是大多数人所遇到的数据库连接一直增加的原因所在。而关于如何关闭spring的session,可以参考如下文章:
http://blog.sina.com.cn/s/blog_864f40cb01017oef.html
或者自行搜索。除了自己显式地关闭session之外,还可以通过配置事务的方式来关闭会话,将每一次的操作封装在事务之中,关于事务,你需要自行搜索spring事务配置方法来深入学习。除此之外,还可以用getHibernateTemplate()方法来代替上面打开会话的方法,例如在myeclipse8.5之后,使用myeclipse自带的工具逆向工程自动生成DAO类时,基本的操作是这样的:.

    public void save(User transientInstance) {        log.debug("saving User instance");        try {            getHibernateTemplate().save(transientInstance);            log.debug("save successful");        } catch (RuntimeException re) {            log.error("save failed", re);            throw re;        }    }

这是一个save操作示例,自动生成的DAO类会继承自HibernateDaoSupport,然后所有的操作都是通过getHibernateTemplate() 来进行的,getHibernateTemplate()方法是spring提供的,可以简单理解成一个小事务,是对session的进一步封装,调用前会建立session,调用结束之后会主动释放session,例如下面也是一个基本操作:

public void delete(User persistentInstance) {        log.debug("deleting User instance");        try {            getHibernateTemplate().delete(persistentInstance);            log.debug("delete successful");        } catch (RuntimeException re) {            log.error("delete failed", re);            throw re;        }    }

我觉得使用getHibernateTemplate()方法来操作数据库应该是最好的办法。

情况三

如果你的项目中没有任何显式地手动打开session,而持久化的操作也是通过getHibernateTemplate()方法完成的,也就是能保证每次会话确实是关闭的,如果还是出现数据库连接持续增加的情况,那就是有可能是hibernate的配置的问题,查看自己spring配置文件中对hibernate的配置代码,看看关于hibernate.connection.release_mode这个属性设置的是否正确,此处普及一下这个属性的含义:取值一共有四种 取值 on_close | after_transaction | after_statement | auto,默认是on_close,hibernate.connection.release_mode 作用是指定Hibernate在何时释放JDBC连接. 默认情况下,直到Session被显式关闭或被断开连接时,才会释放JDBC连接. 对于应用程序服务器的JTA数据源, 你应当使用after_statement, 这样在每次JDBC调用后都会主动的释放连接. 对于非JTA的连接, 使用after_transaction在每个事务结束时释放连接是合理的. 使用auto将自动为JTA和CMT事务策略选择after_statement, 为JDBC事务策略选择after_transaction.

情况四:

在确保配置文件均无误且项目中没有出现过自己主动打开的session或者确保在打开之后又关闭了,如果还是出现数据库连接持续增加的问题,就有可能是这种问题了,本人遇到的情况即是这一种,兜兜转转,所幸最终也定位了问题所在,在一片排版比较乱的博文中偶然看到,后面会贴出地址。具体问题是出在spring的配置文件applicationContext.xml文件上。我们知道,数据源和所有的bean都会在该文件中进行相应的配置,而我们的web项目在框架的集成时会在web.xml文件中作如下配置:

 <context-param>      <param-name>contextConfigLocation</param-name>      <param-value>classpath:applicationContext.xml</param-value>  </context-param>  <listener>      <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  </listener>

这是添加ContextLoaderListener监听器,目的是注册applicationContext.xml文件,也是为了在项目启动的时候加载该文件,使得文件中的配置生效。当然除了此种方式之外,还有一种方式能注册applicationContext.xml,同样在web.xml文件中添加如下代码:

<context-param>          <param-name>contextConfigLocation</param-name>          <param-value>classpath:applicationContext.xml</param-value>      </context-param>       <servlet>          <servlet-name>context</servlet-name>          <servlet-class>              org.springframework.web.context.ContextLoaderServlet           </servlet-class>    <load-on-startup>0</load-on-startup> 

这两种方式的效果是相同的,ContextLoaderServlet和ContextLoaderListener都是先创建ContextLoader的一个对象,然后调用它的initWebApplicationContex方法初始化WebApplicationContext获得一个对象;然后在我的项目中,每次需要ApplicationContext对象的时候,都是通过

ApplicationContext wac = new ClassPathXmlApplicationContext("applicationContext.xml");

这种方式获取,这就是致命的地方,每一次都是重新加载了一次applicationContext.xml文件,也就是重新添加了数据源,重新加载了一份bean文件,所以如果配置过c3p0数据连接池,可以发现每次调用上面的语句时都会有数据库连接的增加,而且增加的数量刚好是设置的初始化数量。这不是SSH框架的问题,也不是c3p0的问题,是你自己代码的问题!我在刚开始学习SSH框架的时候,教程通过此种方式获取,一直沿用至今。applicationContext.xml既然在项目启动的时候就已经加载过,在之后的调用都应该是用那个已经加载好的,而不是重新加载!下面我们来谈谈applicationContext对象的获取方式,其实我们上面所使用的获取方式是适用于独立的spring框架的非web应用,需要程序通过配置文件手工初始化Spring的情况,对于web应用,有以下几种方式:

方法1:
ApplicationContext ac1 = WebApplicationContextUtils.getRequiredWebApplicationContext(ServletContext sc)
方法二:
ApplicationContext ac2 = WebApplicationContextUtils.getWebApplicationContext(ServletContext sc)

注意:其中 servletContext sc 可以具体 换成 servlet.getServletContext()或者 this.getServletContext() 或者 request.getSession().getServletContext(); 另外,由于spring是注入的对象放在ServletContext中的,所以可以直接在ServletContext取出 WebApplicationContext 对象:

WebApplicationContext webApplicationContext = (WebApplicationContext) servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
方法三:

写一个工具类类继承ApplicationObjectSupport

方法四:

写一个工具类类继承WebApplicationObjectSupport

方法五:

写一个工具类实现ApplicationContextAware接口
示例:

import java.util.Map;import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;/** * 获取ApplicationContext和Object的工具类 * @author yzl * */@SuppressWarnings({ "rawtypes", "unchecked" })public class SpringContextUtils implements ApplicationContextAware {    private static ApplicationContext applicationContext;    public void setApplicationContext(ApplicationContext arg0)            throws BeansException {        applicationContext = arg0;    }    /**     * 获取applicationContext对象     * @return     */    public static ApplicationContext getApplicationContext(){        return applicationContext;    }    /**     * 根据bean的id来查找对象     * @param id     * @return     */    public static Object getBeanById(String id){        return applicationContext.getBean(id);    }    /**     * 根据bean的class来查找对象     * @param c     * @return     */    public static Object getBeanByClass(Class c){        return applicationContext.getBean(c);    }    /**     * 根据bean的class来查找所有的对象(包括子类)     * @param c     * @return     */    public static Map getBeansByClass(Class c){        return applicationContext.getBeansOfType(c);    }}

这几种获取方式来源于以下博文:
http://www.cnblogs.com/yangzhilong/p/3949332.html
也可参考http://www.blogjava.net/Todd/archive/2009/09/15/295112.html
博文中还有对于非web应用怎么获取applicationContext对象,讲解清晰,感谢博主分析!
至此,本人项目中遇到的问题总算是解决了,修改applicationContext对象获取方式之后每个用户在访问时数据库的连接数不再持续增加了,数据库的连接池真正发挥着作用。

在自己项目中进行后续研究又有一点关于解决此情况四问题的新的发现,即如何避免新建applicationContext对象,除了用上述解决办法,更换获取applicationContext对象的方法之外,还可以在spring配置文件中进行注入,例如在你的logAction中,假如你想获得UserDAO,在spring中,userDAO配置如下:

    <bean id="UserDAO" class="com.UserDAO">        <property name="sessionFactory">            <ref bean="sessionFactory" />        </property>    </bean>

在没有任何注入配置的情况下,你需要在代码中获取applicationContext对象,然后用

UserDAO userDAO = (UserDAO) wac.getBean("UserDAO");

方式获取UserDAO对象,此种方式也是造成问题的根源所在。换一个角度,如果logAction中用到了UserDAO,那就在spring中增加如下配置:

    <bean id="logAction" class="com.Login">        <property name="userDAO">            <ref bean="UserDAO" />        </property>    </bean>

并在logAction增加UserDAO类型的成员变量userDAO,变量类型可设置为私有,再增加该变量的标准set和get方法,myeclipse中可以选中变量然后右键,在source中选择Generate Getters and Setters…,即可快捷生成get和set方法,注意方法类型要设置为public。
这样,UserDAO就被注入到logAction中,从而程序中就能从已经存在的applicationContext对象中获取UserDAO对象,也就避免了spring配置文件的重复加载,也就避免了数据库连接的持续增长。

2 0
原创粉丝点击