之前我们完成了springMVC的学习和整理,包括spring的入门案例,源代码讲解和实战演练等,现在我们要做的是在我们springMVC框架的基础上新增hibernate的配置,并且使用c3p0作为数据库连接池,以减轻数据库的访问压力。我们所讲的内容不会太追究细节,只是对功能和配置讲解一个流程。之后的文章会对具体细节进行详解。
1、导入jar包
我们应该在之前的jar包基础上新增hibernate与c3p0的jar,但我试了几次都出现冲突的问题,为了节省时间我选择了之前项目的无冲突的jar,比较全,涵盖了ssh等全部的jar
2、配置sessionFactory
在spring的applicationContext中,我们需要增加对sessionHibernate的配置:
<!-- hibernate配置 -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource">
<ref local="dataSource"/>
</property>
<property name="mappingLocations">
<list>
<value>classpath:hibernate/*.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.OracleDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.jdbc.fetch_size">10</prop>
<prop key="hibernate.jdbc.batch_size">30</prop>
<prop key="hibernate.query.factory_class">org.hibernate.hql.ast.ASTQueryTranslatorFactory</prop>
</props>
</property>
<!--与iBatis应用框架保持事务一致性-->
<property name="useTransactionAwareDataSource" value="true"></property>
</bean>
我们发现这个factory使用的数据源是dataSource,一会我们会对他进行配置,它读取的配置文件是在classpath下的hibernate文件夹下的所有以hbm.xml为结尾的文件。sessionFactory配置完成后,我们需要编写我们的DAO类,封装这个sessionFactory并提供接口方法
3、配置daoHibernatImpl
<bean id="daoHibernatImpl" class="com.itcast.hibernate.base.dao.impl.DaoHibernatImpl">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
这个dao类的源代码如下:
package com.itcast.hibernate.base.dao.impl;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import com.itcast.hibernate.base.dao.IDaoHibernat;
/**
*
* @author lvpeng
* 发现:如果不导入spring-transaction-3.x.RELEASE
* 是不能使用HibernateDaoSupport的
*/
public class DaoHibernatImpl extends HibernateDaoSupport implements IDaoHibernat{
@Override
public void save(Object entity) {
try {
this.getHibernateTemplate().save(entity);
} catch (Exception e) {
e.printStackTrace();
}
}
}
方法里我们发现我们因为集成了hibernateDaoSupport所以我们可以使用this.getHibernateTemplate().save(entity);
4、配置数据库连接池C3P0
使用c3p0作为数据库连接池是一件很简单的事情,与spring的整合也很容易,但现在我们想要扩展一下我们的连接池,我们希望连接池中的数据库是可以用多种形式来配置的,因为在一个应用中,也许有1个数据源,也许有多个,有的时候我们是通过内部的配置文件来配置,有的时候我们需要在外部,通过浏览器来配置,针对这些的情况,所以我们的c3p0数据库连接池的数据源不能单纯的使用它自己规定的数据源,而是可以动态改变的,这里我们稍微做了一些封装,首先我们来配置我们的dataSource:
<!-- 配置数据源 实现类是自定义的 -->
<bean id="dataSource" class="com.itcast.hibernate.datasource.C3P0DataSource">
<property name="dataSourceName">
<value>default</value>
</property>
</bean>
看一下这个C3P0DataSource类的源代码,我们可以看到:
package com.itcast.hibernate.datasource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Iterator;
import javax.sql.DataSource;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.xpath.XPath;
import com.itcast.util.DbUtil;
import com.itcast.util.XmlUtil;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class C3P0DataSource implements DataSource{
private ComboPooledDataSource dataSource;
public C3P0DataSource(){
this.dataSource = new ComboPooledDataSource();
}
public void setDataSourceName(String dataSourceName){
try {
Document doc = DataSourceConfig.getInstance().getDocument();
Element root = doc.getRootElement();
Element element = (Element) XPath.selectSingleNode(root,"//JdbcDataSource[@name=\'"+dataSourceName+"\']");
//初始化dataSource
this.dataSource.setDataSourceName(dataSourceName);
String[] driverInfo = DbUtil.translateDriverInfo(
DataBaseType.valueOf(XmlUtil.getAttribString(element,"dbType").toUpperCase()),
XmlUtil.getAttribString(element,"dbUrl"),
XmlUtil.getAttribString(element,"dbName"));
this.dataSource.setDriverClass(driverInfo[0]);
this.dataSource.setJdbcUrl(driverInfo[1]);
this.dataSource.setUser(XmlUtil.getAttribString(element,"dbUser"));
this.dataSource.setPassword(XmlUtil.getAttribString(element,"dbPass"));
Iterator iter = element.getChildren().iterator();
Element child;
String attriValue;
while (iter.hasNext()) {
child = (Element) iter.next();
attriValue = child.getAttribute("name").getValue();
if (attriValue.equals("minPoolSize")) {
this.dataSource.setMinPoolSize(Integer.parseInt(child.getTextTrim()));
continue;
}
if (attriValue.equals("maxPoolSize")) {
this.dataSource.setMaxPoolSize(Integer.parseInt(child.getTextTrim()));
continue;
}
if (attriValue.equals("initialPoolSize")) {
this.dataSource.setInitialPoolSize(Integer.parseInt(child.getTextTrim()));
continue;
}
if (attriValue.equals("acquireIncrement")) {
this.dataSource.setAcquireIncrement(Integer.parseInt(child.getTextTrim()));
continue;
}
if (attriValue.equals("maxIdleTime")) {
this.dataSource.setMaxIdleTime(Integer.parseInt(child.getTextTrim()));
continue;
}
if (attriValue.equals("idleConnectionTestPeriod")) {
this.dataSource.setIdleConnectionTestPeriod(Integer.parseInt(child.getTextTrim()));
continue;
}
if (attriValue.equals("acquireRetryAttempts")) {
this.dataSource.setAcquireRetryAttempts(Integer.parseInt(child.getTextTrim()));
continue;
}
if (attriValue.equals("automaticTestTable")) {
this.dataSource.setAutomaticTestTable(child.getTextTrim());
continue;
}
}
System.out.println("----InitialPoolSize:----"+this.dataSource.getInitialPoolSize());
} catch (Exception e) {
}
}
@Override
public Connection getConnection() throws SQLException {
Connection con = this.dataSource.getConnection();
return con;
}
它实现了dataSource接口,并且重写了相关的方法getConnection,并且我们还重写了setDataSourceName方法,这个方法会在依赖注入dataSource时将我们在xml配置的properties注入进去,而我们在applicationContext.xml配置的dataSourceName是:
<property name="dataSourceName">
<value>default</value>
</property>
default是什么呢,是一个字符串,字符串我们可以认为是一个默认的数据源名称,我们会通过代码和配置文件的形式,按照dataSourceName去查找该数据源,并且将这个数据源的相关参数加载出来,这个流程可以在setDataSourceName方法中体现出来:
//获取配置文件的xml的document
Document doc = DataSourceConfig.getInstance().getDocument();
Element root = doc.getRootElement();
//获取xml中的以jdbcDataSource为开始的节点
Element element = (Element) XPath.selectSingleNode(root,"//JdbcDataSource[@name=\'"+dataSourceName+"\']");
//初始化dataSource
this.dataSource.setDataSourceName(dataSourceName);
String[] driverInfo = DbUtil.translateDriverInfo(
DataBaseType.valueOf(XmlUtil.getAttribString(element,"dbType").toUpperCase()),
XmlUtil.getAttribString(element,"dbUrl"),
XmlUtil.getAttribString(element,"dbName"));
this.dataSource.setDriverClass(driverInfo[0]);
this.dataSource.setJdbcUrl(driverInfo[1]);
this.dataSource.setUser(XmlUtil.getAttribString(element,"dbUser"));
this.dataSource.setPassword(XmlUtil.getAttribString(element,"dbPass"));
其中使用到了DataSourceConfig这个类,该类的源代码如下:
package com.itcast.hibernate.datasource;
import java.io.FileInputStream;
import java.io.InputStream;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.jdom.Document;
import com.itcast.util.XmlUtil;
public class DataSourceConfig implements ServletContextListener{
private static final String DEFAULT_CONFIG_FILE = "/WEB-INF/classes/db-config.xml";
private static final String LOCAL_CONFIG_FILE = "db-config.xml";
private static DataSourceConfig sysDSconfig = new DataSourceConfig();
private static ServletContext context;
public DataSourceConfig(){
}
public static DataSourceConfig getInstance(){
return sysDSconfig;
}
private Document document;
public Document getDocument() {
return document;
}
public void config(){
System.out.println(System.getProperty("user.dir"));
InputStream in = null;
try {
if(context != null){
in = context.getResourceAsStream(DEFAULT_CONFIG_FILE);
}else{
in = new FileInputStream("G:/Workplace/spring/springmvc_hibernate/config/"+LOCAL_CONFIG_FILE);
}
document = XmlUtil.getDocument(in);
System.out.println("document:"+document);
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void contextInitialized(ServletContextEvent sce) {
context = sce.getServletContext();
DataSourceConfig.getInstance().config();
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
它是一个监听器,负责在服务启动时就去加载该配置文件,通过这样的机制,我们就可以动态的加载和变换配置文件的目录,如果找不到工程里的配置文件就可以告诉用户需要在外部配置我们的数据源。(代码中为了配置单元测试,我们将配置文件的路径写死了,这里可以优化)
在代码中我们还使用到了DbUtil.translateDriverInfo方法,源代码如下:
package com.itcast.util;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import com.itcast.hibernate.datasource.DataBaseType;
public class DbUtil {
/**
* @return [driverClassName, connectURI]
*/
public static String[] translateDriverInfo(DataBaseType dbType, String serverName, String dbName) {
String[] result = new String[2];
switch (dbType) {
case ORACLE:
result[0] = "oracle.jdbc.driver.OracleDriver";
if (serverName.indexOf("(") != -1) {
result[1] = "jdbc:oracle:thin:@" + serverName;
} else {
if (serverName.indexOf(":") == -1)
result[1] = "jdbc:oracle:thin:@" + serverName + ":1521:" + dbName;
else
result[1] = "jdbc:oracle:thin:@" + serverName + ":" + dbName;
}
break;
case MYSQL:
result[0] = "com.mysql.jdbc.Driver";
result[1] = "jdbc:mysql://" + serverName + "/" + dbName;
if (dbName.indexOf("?") == -1)
result[1] = result[1] + "?useUnicode=true&characterEncoding=GBK";
break;
}
return result;
}
public static void closeDBObject(ResultSet rs, Statement stat, Connection dbConn) {
try {
if (rs != null) {
rs.close();
rs = null;
}
} catch (SQLException e) {
}
try {
if (stat != null) {
stat.close();
stat = null;
}
} catch (SQLException e) {
}
try {
if (dbConn != null) {
dbConn.close();
dbConn = null;
}
} catch (SQLException e) {
}
}
}
它的主要功能是帮助我们找到数据源对应的驱动信息等。而我们的数据源配置文件的内容是:
<?xml version="1.0" encoding="UTF-8"?>
<lvpeng>
<DataSources>
<JdbcDataSource name="default" dbType="ORACLE" dbUrl="127.0.0.1:1521" dbName="ORCL" dbUser="scott" dbPass="scott">
<!-- 连接池中保留的最小连接数 -->
<Property name="minPoolSize">3</Property>
<!-- 连接池中保留的最大连接数 -->
<Property name="maxPoolSize">5</Property>
<!-- 初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间 -->
<Property name="initialPoolSize">3</Property>
<!-- 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数 -->
<Property name="acquireIncrement">2</Property>
<!-- 最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃 -->
<Property name="maxIdleTime">3600</Property>
<!-- 每60秒检查所有连接池中的空闲连接 -->
<Property name="idleConnectionTestPeriod">60</Property>
<!-- 定义在从数据库获取新连接失败后重复尝试的次数 -->
<Property name="acquireRetryAttempts">30</Property>
<!-- c3p0将建一张名为Test的空表,并使用其自带的查询语句进行测试 -->
<Property name="automaticTestTable">S_TEST</Property>
</JdbcDataSource>
</DataSources>
</lvpeng>
而如果我们想采用外部配置的方式,也需要提供这些基本的配置参数,来生成我们的目标配置文件,以供监听器发现。
5、增加业务DAO层和user.hbm.xml
package com.itcast.sm.dao.impl;
import org.springframework.beans.factory.annotation.Autowired;
import com.itcast.hibernate.base.dao.IDaoHibernat;
import com.itcast.spring.mvc.service.model.User;
public class UserDaoImpl implements UserDao{
@Autowired
private IDaoHibernat daoHibernat;
@Override
public void SaveUser(User user) {
this.daoHibernat.save(user);
}
}
这个类负责saveUser,将daoHibernate注入到该类中作为属性。直接调用save方法就能完成对user的insert,但我们需要配置对应的hbm.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.itcast.spring.mvc.service.model.User" table="S_USER">
<id name="id" column="id"/>
<property name="userName" type="java.lang.String" column="USERNAME"></property>
<property name="passWord" type="java.lang.String" column="PASSWORD"></property>
<property name="realName" type="java.lang.String" column="REALNAME"></property>
</class>
</hibernate-mapping>
6、配置bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd" >
<bean id="userService" class="com.itcast.spring.mvc.service.impl.UserServiceImpl"></bean>
<bean id="userDao" class="com.itcast.sm.dao.impl.UserDaoImpl"></bean>
</beans>
7、修改web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- 系统数据源加载监听器 -->
<listener>
<listener-class>com.itcast.hibernate.datasource.DataSourceConfig</listener-class>
</listener>
<!-- spring容器的监听器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 通过contextConfigLocation参数指定业务层
Spring容器的配置文件(多个文件用','号分开);
如果没有配置contextConfigLocation属性,
默认自动加载spring-servlet.xml文件即:
<servlet-Name>-servlet.xml -->
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</init-param>
</servlet>
<!-- servlet-Mapping指定的DispatcherServlet处理所有URL未.html为后缀的HTTP请求 -->
<!-- 注意:一个web.xml可以配置多个DispatcherServlet 通过其<servlet-mapping>
的配置,让每一个DispatcherServlet处理不同的请求-->
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
</web-app>
8、工程目录