SSH Chapter 12 搭建SSH框架
本章目标 :
- 掌握Spring与Hibernate的集成
- 掌握Spring与Struts 2的集成
环境准备:
- Spring 5.1.5
- Struts2 2.5.16
- Hibernate 5.4.2.Final
- C3P0
- HikariCP
- Intellij IDEA
- jdk1.8
- Oracle 11g
- Maven 3.5
- Git 2.18.0
1 . 搭建基于SSH的程序结构
1.1 SSH简介
SSH架构指的是使用Struts 2、Spring和Hibemate这3个框架来搭建项目的主体架构,这也是目前比较流行的项目架构。
Struts 2和Hibernate是两个独立的框架,它们之间没有直接的联系。
由于Spring框架提供了对象管理,切面编程等非常实用的功能,如果把Struts 2和Hibernate的对象交给Spring容器进行解耦合管理,不仅能大大增强系统的灵活性,便于功能扩展,还能通过Spring提供的服务简化编码,减少开发工作量,提高开发效率。
所以SSH框架整合其实就是分别实现Spring与Struts 2、Spring与Hibernate的整合,而实现整合的只要工作就是把Struts 2、Hibernate中的对象配置到Spring容器中,交给Spring来管理。
1.2 整合思路分析
JavaWeb应用开发经过多年的发展,已经形成了一套成熟的程序结构。一个典型的使用了Struts2和Hibernate框架的应用,其结构如图所示
- Struts2的主控制器接到请求后会调用特定的Action。在Action中又会调用业务类(Service)来执行业务逻辑。
- 如果需要访问数据库,业务类则会继续调用数据库访问对象(DAO)。而在数据库访问对象中,则需要调用SessionFactory提供的Session实例的方法执行具体操作。
- Session最终会通过Connection等JDBC API来实现增删该查等操作,而Connction可以通过配置的数据源(DataSource)来提供。
通过以上分析不难看出,程序执行过程中依赖的方向是Action—>Service—>DAO—>Session(由SessionFactory提供)—>Connection(由DataSource提供),当使用Spring IOC进行依赖管理时,依赖注入的方向则正好与相反。
下面通过租房系统中的用户登录功能展示SSH架构的搭建过程。
实现思路及关键代码如下:
- step 1. 为租房系统添加3个框架所需要的JAR文件 , 并创建相关的配置文件
- step 2. 配置数据源对象
- step 3. 为Hibernate 配置 SessionFactory对象
- step 4. 实现并配置DAO和Service
- step 5. 为业务层添加事务管理
- step 6. 实现并配置Action
- step 7. 创建JSP测试页面(登录页面)
根据之前的分析,我们将按照==DataSource--->SessionFactory--->DAO--->Service--->Action
==的顺序实现依赖注入,即先完成Spring和Hibernate的整合,再对业务层进行整合,最后完成Spring和Struts 2的整合。
多模块项目中,父模块pom.xml
中加入以下依赖:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<!--统一定义maven的版本号-->
<hibernate-core.version>5.4.2.Final</hibernate-core.version>
<slf4j-simple.version>1.7.5</slf4j-simple.version>
<ojdbc6.version>11.2.0.1.0</ojdbc6.version>
<junit.version>4.12</junit.version>
<javax.servlet-api.version>3.1.0</javax.servlet-api.version>
<javax.servlet.jsp-api.version>2.2.1</javax.servlet.jsp-api.version>
<struts2.version>2.5.16</struts2.version>
<jstl.version>1.2</jstl.version>
<log4j2.version>2.10.0</log4j2.version>
<spring.version>5.2.8.RELEASE</spring.version>
</properties>
<dependencyManagement>
<dependencies>
<!--定义spring 依赖的-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>${spring.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--struts2-->
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-bom</artifactId>
<version>${struts2.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--log4j2-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-bom</artifactId>
<version>${log4j2.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
<!--hibernate-core -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate-core.version}</version>
</dependency>
<!-- hibernate-c3p0 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>${hibernate-core.version}</version>
</dependency>
<!-- hibernate-hikaricp -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-hikaricp</artifactId>
<version>${hibernate-core.version}</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<!--ojdbc -->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>${ojdbc6.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${javax.servlet-api.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>${jstl.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<!--设置项目打成war包之后的包名,例如以下的代码是将项目打成的war包
名为basic-struts.war-->
<!-- <finalName>basic-struts</finalName>-->
<plugins>
<!--使用jetty插件-->
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.4.24.v20191120</version>
<configuration>
<httpConnector>
<!--设置jetty的端口号-->
<port>8088</port>
<!--<host>localhost</host>-->
</httpConnector>
<scanIntervalSeconds>5</scanIntervalSeconds>
</configuration>
</plugin>
<!--或者使用tomcat插件-->
<plugin>
<!-- tomcat7-maven-plugin -->
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8080</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
单模块的pom.xml
内容如下:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<!--统一定义maven的版本号-->
<hibernate-core.version>5.4.2.Final</hibernate-core.version>
<slf4j-simple.version>1.7.5</slf4j-simple.version>
<ojdbc6.version>11.2.0.1.0</ojdbc6.version>
<junit.version>4.12</junit.version>
<javax.servlet-api.version>3.1.0</javax.servlet-api.version>
<struts2.version>2.5.16</struts2.version>
<jstl.version>1.2</jstl.version>
<log4j2.version>2.10.0</log4j2.version>
<spring.version>5.2.8.RELEASE</spring.version>
</properties>
<dependencyManagement>
<dependencies>
<!--定义spring 依赖的-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>${spring.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--struts2-->
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-bom</artifactId>
<version>${struts2.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--log4j2-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-bom</artifactId>
<version>${log4j2.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--spring start-->
<!--spring-orm -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
</dependency>
<!--spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<!--spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
<!--spring end-->
<!--hibernate start-->
<!--hibernate-core -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate-core.version}</version>
</dependency>
<!-- hibernate-c3p0 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>${hibernate-core.version}</version>
</dependency>
<!-- hibernate-hikaricp -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-hikaricp</artifactId>
<version>${hibernate-core.version}</version>
</dependency>
<!--hibernate end-->
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!--ojdbc -->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>${ojdbc6.version}</version>
</dependency>
<!--Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${javax.servlet-api.version}</version>
<scope>provided</scope>
</dependency>
<!--jstl -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>${jstl.version}</version>
</dependency>
<!--struts2 start-->
<!-- struts2-core -->
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
</dependency>
<!-- struts2-spring-plugin -->
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-spring-plugin</artifactId>
</dependency>
<!-- struts2-json-plugin -->
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-json-plugin</artifactId>
</dependency>
<!--struts2 end-->
<!--log4j start-->
<!-- log4j-core -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
</dependency>
<!-- log4j-api -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
</dependency>
<!--log4j end-->
</dependencies>
<build>
<!--设置项目打成war包之后的包名,例如以下的代码是将项目打成的war包
名为basic-struts.war-->
<!-- <finalName>basic-struts</finalName>-->
<plugins>
<!--使用jetty插件-->
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.4.24.v20191120</version>
<configuration>
<httpConnector>
<!--设置jetty的端口号-->
<port>8088</port>
<!--<host>localhost</host>-->
</httpConnector>
<scanIntervalSeconds>5</scanIntervalSeconds>
</configuration>
</plugin>
<!--或者使用tomcat插件-->
<plugin>
<!-- tomcat7-maven-plugin -->
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8080</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
2 . 实现 Spring 和 Hibernate 的整合
2.1 创建各个模块并导入对应的依赖
1. 创建houserent_entity
并在模块中加入相应的依赖
pom.xml
文件代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ssh-demo</artifactId>
<groupId>cn.hibernatedemo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>cn.houserent</groupId>
<artifactId>houserent_entity</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
</project>
2. 创建houserent_dao
并在模块中加入相应的依赖
pom.xml
文件代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ssh-demo</artifactId>
<groupId>cn.hibernatedemo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>cn.hibernatedemo</groupId>
<artifactId>houserent_dao</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>cn.houserent</groupId>
<artifactId>houserent_entity</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-hikaricp</artifactId>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
</dependency>
</dependencies>
</project>
3. 创建houserent_service
并在模块中加入相应的依赖
pom.xml
文件代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ssh-demo</artifactId>
<groupId>cn.hibernatedemo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>cn.houserent</groupId>
<artifactId>houserent_service</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>cn.hibernatedemo</groupId>
<artifactId>houserent_dao</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
</dependencies>
</project>
4.创建houserent_web
并在模块中加入相应的依赖
pom.xml
文件代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ssh-demo</artifactId>
<groupId>cn.hibernatedemo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>cn.houserent</groupId>
<artifactId>houserent_web</artifactId>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>cn.houserent</groupId>
<artifactId>houserent_service</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.hibernatedemo</groupId>
<artifactId>houserent_util</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-spring-plugin</artifactId>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-json-plugin</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
</resources>
</build>
</project>
2.2 配置SessionFactory
使用Spring整合Hibernate , 首先应将诸如JDBC DataSource或者Hibernate SessionFactory 等数据访问资源以Bean的形式定义在Spring容器中 , 交由Spring容器进行管理
如果应用中存在一个独立的Hibernate配置文件
hibernate.cfg.xml
, 可以采用如示例1所指示的方式在Spring配置文件中轻松实现SessionFactory Bean的定义
1. Spring整合Hibernate第一种方式:配置sessionFactory
示例1
applicationContext.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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--Spring整合hibernate第一种方式:配置sessionFactory-->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml"/>
</bean>
</beans>
参照官网如下:
log4j2.xml文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="com.opensymphony.xwork2" level="info"/>
<Logger name="org.apache.struts2" level="info"/>
<Logger name="org.hibernate" level="info"/>
<Logger name="com" level="info"/>
<Logger name="cn.houserent" level="debug"/>
<Root level="warn">
<AppenderRef ref="STDOUT"/>
</Root>
</Loggers>
</Configuration>
编写测试类 , 代码如下:
@Test
public void test_01() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
SessionFactory sessionFactory = context.getBean(SessionFactory.class);
Session session = sessionFactory.getCurrentSession();
Transaction transaction =
session.beginTransaction();
List<User> list = session.createQuery("from User").list();
list.forEach(x -> System.out.println(x.getName()));
}
2. Spring整合Hibernate的第二种方式:配置数据源
定义C3P0数据源 , 如示例2所示:
<context:property-placeholder location="classpath:c3p0.properties"/>
<!--Spring整合hibernate的第二种方式:定义c3p0连接池 -->
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close" >
<property name="user" value="${c3p0.user}"/>
<property name="password" value="${c3p0.password}"/>
<property name="driverClass" value="${c3p0.driverClass}"/>
<property name="jdbcUrl" value="${c3p0.jdbcUrl}"/>
<!--minPoolSize :最小连接数(default 3)-->
<property name="minPoolSize" value="${c3p0.minPoolSize}"/>
<!--maxPoolSize :最大连接数(default 15)-->
<property name="maxPoolSize" value="${c3p0.maxPoolSize}"/>
<!--initialPoolSize : 初始连接数(default 3)-->
<property name="initialPoolSize" value="${c3p0.initialPoolSize}"/>
<!--maxIdleTime :最大闲置时间, 未使用的连接在被丢弃之前在连接池存活时间,单位为秒(default 0, 永久存活)-->
<property name="maxIdleTime" value="${c3p0.maxIdleTime}"/>
<!--acquireIncrement : 获取连接数量, 连接不足时,c3p0尝试一次获取新连接的个数(default 3)-->
<property name="acquireIncrement" value="${c3p0.acquireIncrement}"/>
<!-- 空闲检查时间间隔, 每隔60秒检查连接池里的空闲连接 ,单位是秒-->
<property name="idleConnectionTestPeriod" value="${c3p0.idleConnectionTestPeriod}"/>
</bean>
c3p0.properties
内容如下:
#
# This file is detritus from various testing attempts
# the values below may change, and often do not represent
# reasonable values for the parameters.
#testConnectionOnCheckout:性能消耗大。如果为true,在每次
# getConnection的时候都会测试,为了提高性能,尽量不要用。default : false(不建议使用)
# c3p0.testConnectionOnCheckout=true
# testConnectionOnCheckin:如果为true,则在close的时候测试连接的有效性
# default : false(不建议使用)
# c3p0.testConnectionOnCheckin=true
#c3p0.dataSourceName=poop
c3p0.minPoolSize=5
c3p0.maxPoolSize=20
# c3p0.checkoutTimeout=2000
c3p0.idleConnectionTestPeriod=60
#c3p0.idleConnectionTestPeriod=1
#c3p0.maxConnectionAge=1
c3p0.maxIdleTime=120
# c3p0.maxIdleTimeExcessConnections=1
# c3p0.propertyCycle=1
# c3p0.numHelperThreads=10
# c3p0.unreturnedConnectionTimeout=15
# c3p0.debugUnreturnedConnectionStackTraces=true
# c3p0.forceSynchronousCheckins=true
# c3p0.maxStatements=20
# c3p0.maxStatementsPerConnection=5
# c3p0.maxAdministrativeTaskTime=3
# c3p0.preferredTestQuery=SELECT 1
#c3p0.preferredTestQuery=SXLECT 1
#c3p0.preferredTestQuery=SELECT a FROM emptyyukyuk WHERE a = 5
#c3p0.preferredTestQuery=SELECT a FROM testpbds WHERE a = 5
# c3p0.statementCacheNumDeferredCloseThreads=1
#c3p0.usesTraditionalReflectiveProxies=true
#c3p0.automaticTestTable=PoopyTestTable
c3p0.acquireIncrement=2
#c3p0.acquireRetryDelay=1000
#c3p0.acquireRetryAttempts=60
#c3p0.connectionTesterClassName=com.mchange.v2.c3p0.test.AlwaysFailConnectionTester
c3p0.initialPoolSize=10
c3p0.jdbcUrl=jdbc:oracle:thin:@localhost:1521:ORCL
c3p0.driverClass=oracle.jdbc.driver.OracleDriver
c3p0.user=scott
c3p0.password=tiger
# com.mchange.v2.c3p0.impl.DefaultConnectionTester.isValidTimeout=1
#com.mchange.v2.log.MLog=com.mchange.v2.log.log4j.Log4jMLog
#com.mchange.v2.log.MLog=com.mchange.v2.log.jdk14logging.Jdk14MLog
#com.mchange.v2.log.MLog=com.mchange.v2.log.FallbackMLog
#com.mchange.v2.log.NameTransformer=com.mchange.v2.log.PackageNames
#com.mchange.v2.log.FallbackMLog.DEFAULT_CUTOFF_LEVEL=ALL
#com.mchange.v2.c3p0.VMID=poop
#com.mchange.v2.resourcepool.experimental.useScatteredAcquireTask=false
详细介绍请参考c3p0案例演示:https://github.com/swaldman/c3p0/blob/master/src/test-properties/c3p0.properties
示例3
定义SessionFactoryBean,可参考Spring的官网 代码如下:
<bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
org.hibernate.dialect.Oracle10gDialect
</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<!--Spring5 整合hibernate管理事务后,
由Spring的TransactionManager管理事务后,
currentSession是默认绑定到SpringSessionContext的,而不是thread。
所以此处应该改为SpringSessionContext,或者可以不用配置,使用默认的即可-->
<!--<prop key="hibernate.current_session_context_class">
org.springframework.orm.hibernate5.SpringSessionContext
</prop>-->
</props>
<!--也可以使用如下方式注入hibernate需要的参数 -->
<!--<value>
hibernate.dialect=org.hibernate.dialect.Oracle10gDialect
hibernate.show_sql=true
hibernate.format_sql=true
</value> -->
</property>
<!--添加对象关系映射文件-->
<property name="mappingResources">
<list>
<value>cn/houserent/entity/User.hbm.xml</value>
</list>
</property>
</bean>
如示例3代码所示 , 配置Hibernate的SessionFactory Bean时 , 需要定义好的数据源对象 , 还需要通过hibernateProperties
和 mappingResources
属性分别注入Hibernate的相关配置参数和对象关系映射文件的信息
官网如图所示:
测试代码如下:
@Test
public void test_02() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
SessionFactory sessionFactory = context.getBean(SessionFactory.class);
Session session = sessionFactory.getCurrentSession();
Transaction transaction =
session.beginTransaction();
//sysdm.cpl
List<User> list = session.createQuery("from User").list();
list.forEach(x -> System.out.println(x.getName()));
}
在实际开发中 , 对象关系映射文件可能会非常多 , 为每个文件中添加一条配置信息 是一项很繁琐的工作 , 也使得配置文件变得臃肿 . 为了解决这个问题 , 可以使用mappingLocations
指定任何文件路径 , 支持通配符 , 代码如下所示:
<!--mappingLocations:可以指定任何文件路径,
可以指定前缀:classpath、file等 ,支持通配符,
并且可以加载jar文件中的路径,一般采用此种方式。-->
<property name="mappingLocations" value="classpath*:cn/houserent/entity/*.hbm.xml"/>
运行示例3中的测试方法 , 结果正常输出
或者可以使用
mappingDirectoryLocations
属性指定映射文件所在的目录 , 简化映射文件的配置 . 代码如示例4所示:
示例4:
<!--添加对象关系映射文件所在的路径-->
<!--mappingDirectoryLocations:指定映射的文件路径 ,
这个只需指定目录,也可以指定文件,这个要注意的是不能加载jar中的目录及文件 多模块项目中慎用-->
<property name="mappingDirectoryLocations">
<list>
<value>classpath:/cn/houserent/entity</value>
</list>
</property>
运行示例3中的测试方法 , 结果正常输出
推荐使用第二种 , 即
mappingLocations
属性
> 备注:
Spring 整合Hibernate之后,由Spring的TransactionManager管理事务后, 所以currentSession是绑定到SpringSessionContext的,而不是thread。
因此
hibernate.current_session_context_class
的属性值应该是SpringSessionContext,而Spring又会在使用LocalSessionFactoryBean时自动的设置。所以也就不需要设置current_session_context_class
2.3 补充c3p0常用配置参数介绍
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate、Spring等。
c3p0官网:https://www.mchange.com/projects/c3p0
c3p0案例演示:https://github.com/swaldman/c3p0
在前面的c3p0的相关配置中,我们看到了c3p0的配置参数,这里我们介绍几个常用的c3p0的配置参数
> 最基础的参数配置:
- driverClass : 数据库驱动(比如mysql,或者oracle数据库的驱动)
- jdbcUrl: jdbc数据库连接地址(例如jdbc:mysql://localhost:3306/smbms)
- user:数据库用户名
- password:和数据库用户名对应的数据库密码
> 基础的参数配置:
-
initialPoolSize:连接池初始化时创建的连接数,default : 3(建议使用)
-
minPoolSize:连接池保持的最小连接数,default : 3(建议使用)
-
maxPoolSize:连接池中拥有的最大连接数,如果获得新连接时会使连接总数超过这个值则不会再获取新连接,而是等待其他连接释放,所以这个值有可能会设计地很大,default : 15(建议使用)
-
acquireIncrement:连接池在无空闲连接可用时一次性创建的新数据库连接数,default : 3(建议使用)
> 管理池大小和连接时间的配置
-
maxConnectionAge:配置连接的生存时间,超过这个时间的连接将由连接池自动断开丢弃掉。当然正在使用的连接不会马上断开,而是等待它close再断开。配置为0的时候则不会对连接的生存时间进行限制。default : 0 单位 s(不建议使用)
-
maxIdleTime:连接的最大空闲时间,如果超过这个时间,某个数据库连接还没有被使用,则会断开掉这个连接。如果为0,则永远不会断开连接,即回收此连接。default : 0 单位 s(建议使用)
-
maxIdleTimeExcessConnections:这个配置主要是为了快速减轻连接池的负载,比如连接池中连接数因为某次数据访问高峰导致创建了很多数据连接,但是后面的时间段需要的数据库连接数很少,需要快速释放,必须小于maxIdleTime。其实这个没必要配置,maxIdleTime已经配置了。default : 0 单位 s(不建议使用)
> 配置连接测试
-
automaticTestTable:配置一个表名,连接池根据这个表名用自己的测试sql语句在这个空表上测试数据库连接,这个表只能由c3p0来使用,用户不能操作。default : null(不建议使用)
-
referredTestQuery:与上面的automaticTestTable二者只能选一。自己实现一条SQL检测语句。default : null(建议使用)
-
idleConnectionTestPeriod:用来配置测试空闲连接的间隔时间。测试方式还是上面的两种之一,可以用来解决MySQL8小时断开连接的问题。因为它保证连接池会每隔一定时间对空闲连接进行一次测试,从而保证有效的空闲连接能每隔一定时间访问一次数据库,将于MySQL8小时无会话的状态打破。为0则不测试。default : 0(建议使用)
-
testConnectionOnCheckin:如果为true,则在close的时候测试连接的有效性。default : false(不建议使用)
-
testConnectionOnCheckout:性能消耗大。如果为true,在每次getConnection的时候都会测试,为了提高性能,尽量不要用。default : false(不建议使用)
> 配置PreparedStatement缓存
- maxStatements:连接池为数据源缓存的PreparedStatement的总数。由于PreparedStatement属于单个Connection,所以这个数量应该根据应用中平均连接数乘以每个连接的平均PreparedStatement来计算。同时maxStatementsPerConnection的配置无效。default : 0(不建议使用)
- maxStatementsPerConnection:连接池为数据源单个Connection缓存的PreparedStatement数,这个配置比maxStatements更有意义,因为它缓存的服务对象是单个数据连接,如果设置的好,肯定是可以提高性能的。为0的时候不缓存。default : 0(看情况而论)
> 重连相关配置
- acquireRetryAttempts:连接池在获得新连接失败时重试的次数,如果小于等于0则无限重试直至连接获得成功。default : 30(建议使用)
- acquireRetryDelay:连接池在获得新连接时的间隔时间。default : 1000 单位ms(建议使用)
- breakAfterAcquireFailure:如果为true,则当连接获取失败时自动关闭数据源,除非重新启动应用程序。所以一般不用。default : false(不建议使用)
- checkoutTimeout:配置当连接池所有连接用完时应用程序getConnection的等待时间。为0则无限等待直至有其他连接释放或者创建新的连接,不为0则当时间到的时候如果仍没有获得连接,则会抛出SQLException。其实就是acquireRetryAttempts*acquireRetryDelay。default : 0(与上面两个,有重复,选择其中两个都行)
> 定制管理Connection的生命周期
- connectionCustomizerClassName:用来定制Connection的管理,比如在Connection acquire 的时候设定Connection的隔离级别,或者在Connection丢弃的时候进行资源关闭,就可以通过继承一个AbstractConnectionCustomizer来实现相关方法,配置的时候使用全类名。有点类似监听器的作用。default : null(不建议使用)
> 配置未提交的事务处理
- autoCommitOnClose:连接池在回收数据库连接时是否自动提交事务。如果为false,则会回滚未提交的事务,如果为true,则会自动提交事务。default : false(不建议使用)
- forceIgnoreUnresolvedTransactions:这个配置强烈不建议为true。default : false(不建议使用)一般来说事务当然由自己关闭了,为什么要让连接池来处理这种不细心问题呢?
> 配置debug和回收Connection
- unreturnedConnectionTimeout:为0的时候要求所有的Connection在应用程序中必须关闭。如果不为0,则强制在设定的时间到达后回收Connection,所以必须小心设置,保证在回收之前所有数据库操作都能够完成。这种限制减少Connection未关闭情况的不是很适用。建议手动关闭。default : 0 单位 s(不建议使用)
- debugUnreturnedConnectionStackTraces:如果为true并且unreturnedConnectionTimeout设为大于0的值,当所有被getConnection出去的连接
unreturnedConnectionTimeout时间到的时候,就会打印出堆栈信息。只能在debug模式下适用,因为打印堆栈信息会减慢getConnection的速度default : false(不建议使用)
2.4 补充HikariCP连接池:
> HikariCP介绍:
HiKariCP是数据库连接池的一个后起之秀,号称性能最好,可以完美地PK掉其他连接池。
官网:https://github.com/brettwooldridge/HikariCP
HikariCP官网的性能测试对比:
从上述结果看到,HikariCP的性能甩其他c3p0、tomcat-jdbc连接池好几条街。以致后来BoneCP作者都放弃了维护,在Github项目主页推荐大家使用HikariCP。另外,Spring Boot在2.0版本中把HikariCP作为其默认的JDBC连接池
> HikariCP为什么这么快:
-
字节码级别优化(很多方法通过JavaAsist生成):扁平化继承关系、浅复制成员变量、消除强制转换。
-
微小优化:使用
FastList<Statement>
代替ArrayList<Statement>
(FastList替代ArrayList,去掉了范围检查和从尾部到头部删除元素); -
引入无锁集合CurrentBag(在无锁设计中提供了ThreadLocal缓存和队列窃取,提供高并发性并最小化错误共享的发生);
-
代理类的优化(去掉getstatic调度用,
invokestatic在JVM中的优化更简单,stack size变小了)。
具体原因,参考官网:https://github.com/brettwooldridge/HikariCP/wiki/Down-the-Rabbit-Hole
> HikariCP的性能测试:
关于数据库连接池性能测试(hikariCP,druid,tomcat-jdbc,dbcp,c3p0),可参考博文:http://blog.xujin.org/mw/dcp-test/?utm_source=tuicool&utm_medium=referral
结论:
- 性能方面 hikariCP>druid>tomcat-jdbc>dbcp>c3p0 。hikariCP的高性能得益于最大限度的避免锁竞争。
- druid功能最为全面,sql拦截等功能,统计数据较为全面,具有良好的扩展性。
- 可开启prepareStatement缓存,对性能会有大概10%的提升。
> HikariCP在Spring中的配置:
参考官网如下:https://github.com/brettwooldridge/HikariCP/wiki/Hibernate4
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 连接只读数据库时配置为true, 保证安全 -->
<property name="readOnly" value="false" />
<!-- 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQLException, 缺省:30秒 -->
<property name="connectionTimeout" value="30000" />
<!-- 一个连接idle状态的最大时长(毫秒),超时则被释放(retired),缺省:10分钟 -->
<property name="idleTimeout" value="600000" />
<!-- 一个连接的生命时长(毫秒),超时而且没被使用则被释放(retired),缺省:30分钟,建议设置比数据库超时时长少30秒,参考MySQL
wait_timeout参数(show variables like '%timeout%';) -->
<property name="maxLifetime" value="1800000" />
<!-- 连接池中允许的最大连接数。缺省值:10;推荐的公式:((core_count * 2) + effective_spindle_count) -->
<property name="maximumPoolSize" value="60" />
<property name="minimumIdle" value="10" />
</bean>
其他参数说明请参考官网:https://github.com/brettwooldridge/HikariCP
> Spring Hibernate
如果您使用的是旧版Hibernate (3.x),请查看Spring+Hibernate页面进行配置。参考官网如下:https://github.com/brettwooldridge/HikariCP/wiki/Spring-Hibernate
<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
<property name="poolName" value="springHikariCP" />
<!--mysql-->
<!--<property name="connectionTestQuery" value="SELECT 1" />
<property name="dataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlDataSource" />-->
<!--oracle-->
<property name="connectionTestQuery" value="SELECT 1 FROM DUAL" />
<property name="dataSourceClassName"
value="oracle.jdbc.pool.OracleDataSource" />
<property name="dataSourceProperties">
<props>
<prop key="url">${jdbc.url}</prop>
<prop key="user">${jdbc.username}</prop>
<prop key="password">${jdbc.password}</prop>
</props>
</property>
</bean>
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<constructor-arg ref="hikariConfig" />
</bean>
2.5 使用HibernateTemplate 简化 Hibernate DAO
配置好所需的SessionFactory Bean 后 就可以在此基础上进行DAO层开发了 . 针对使用Hibernate框架开发的DAO类 , Spring提供了一个模板类HibernateTemplate来简化编码过程 .
HibernateTemplate中心方法是execute,支持Hibernate访问代码实现HibernateCallback接口。它提供Hibernate会话处理,因此HibernateCallback实现和调用代码都不需要显式地关心创建/关闭Hibernate会话,或者处理会话生命周期异常。对于典型的单步操作,有各种方便的方法(查找、加载、保存或更新、删除)。
注意:Hibernate访问代码也可以针对本机Hibernate会话进行编码。因此,对于新启动的项目,可以考虑采用针对SessionFactory.getCurrentSession()的标准Hibernate风格的编码。
或者,对回调提供的会话使用Java 8 lambda代码块执行HibernateCallback,这也会产生优雅的代码,与Hibernate会话生命周期解耦。
与此同时,这个HibernateTemplate上的其余操作将被弃用,主要作为旧Hibernate 3.x/4的迁移助手而存在。现有应用程序中的数据访问代码。
HibernateTemplate的方法可以帮我们实现了流程化的代码 , 并调用Session的各种方法完成CURD的操作 . 常用的方法以及功能如下:
- void delete(Object entity):删除指定持久化实例
- deleteAll(Collection entities):删除集合内全部持久化类实例
- find(String queryString):根据HQL查询字符串来返回实例集合
- findByNamedQuery(String queryName):根据命名查询返回实例集合
- get(Class entityClass, Serializable id):根据主键加载特定持久化类的实例
- save(Object entity):保存新的实例
- saveOrUpdate(Object entity):根据实例状态,选择保存或者更新
- update(Object entity):更新实例的状态,要求entity是持久状态
- setMaxResults(int maxResults):设置分页的大小
- void delete(Object entity):删除指定持久化实例
- deleteAll(Collection entities):删除集合内全部持久化类实例
- load(Class var1, Serializable var2) : 延迟加载实例 , 调用Hibernate Sessoin对象的load()方法
使用HibernateTemplate如示例5所示:
示例5:
UserDao.java
代码如下:
/**
*数据持久层
*/
public interface UserDao {
/**
* 添加用户
* @param user 用户
*/
void save(User user);
/**
* 按属性值查询用户列表
* @param propertyName
* @param value
* @return
*/
List getUserByProperty(String propertyName, Object value);
/**
* 按条件查询用户列表
* @param instance 查询条件
* @return 返回用户列表
*/
List<User> findByExample(User instance);
//省略其他方法
}
UserDaoImpl.java
代码如下:
/**
* User数据访问层
*/
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
/**
* 根据对应属性名以及属性值查询数据
* @param propertyName
* @param value
* @return
*/
@Override
public List<User> getUserByProperty(String propertyName, Object value) {
//HQL: from User where id=:?0,name:name
//String hql = "from User u where u."+propertyName+"=?0";
//return this.getHibernateTemplate().find(hql,value);
String queryString = "from User u where u."+propertyName+" =:"+propertyName;
return this.getHibernateTemplate().execute((session)->
session.createQuery(queryString).setParameter(propertyName,value).list()
);
}
@Override
public void save(User user) {
this.getHibernateTemplate().save(user);
}
@Override
public List<User> findByExample(User instance) {
return this.getHibernateTemplate().findByExample(instance);
}
//省略其他方法
}
Spring配置文件applicationContext.xml
中配置UserDao
, 关键代码如下:
<?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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--省略数据源以及SessionFactory Bean的定义-->
<bean id="userDao" class="cn.houserent.dao.impl.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
</beans>
测试DAO的方法 , 测试代码如下:
@Test
public void test_03() {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = applicationContext.getBean("userDao", UserDao.class);
//sysdm.cpl
List<User> list = userDao.getUserByProperty("id",1001);
//list.forEach(x -> log.info("u:"+x));
list.forEach(x-> System.out.println(x));
}
至此 , 我们就实现了Spring对Hibernate的集成 , 下面对这一过程做一个小结:
- 1 . 首先在Spring配置文件中定义DataSource和SessionFactoryBean
- 2 . 定义和配置DAO
Spring为Hibernate DAO 提供了HibernateDaoSupport
类 , 该类以下的两个方法重要:
- public final void setSessionFactory(SessionFactory sessionFactory)
- public final HibernateTemplate getHibernateTemplate()
其中setSessionFactory()方法使DAO通过依赖注入方式获得SessionFactory的实例 , 并创建HibernateTemplate的实例 . 而getHibernateTemplate()方法则用来返回HibernateTemplate
的实例 , 帮助DAO类完成持久化操作
3 . 编写业务层并添加声明式事务
在使用Spring实现对DAO层的管理之后 , 就可以开始业务层的开发了
3.1 添加业务层
业务层接口及实现了的关代码如示例6所示:
示例6:
/**
* 用户业务接口
*/
public interface UserBiz {
/**
* 登录
* @param name
* @param password
* @return
*/
User login(String name, String password);
/**
* 添加用户
* @param user
*/
void addUser(User user);
//省略其他方法
}
User业务逻辑实现类 , 代码如下:
/**
* 用户业务逻辑实现类
*/
public class UserBizImpl implements UserBiz {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public User login(String name, String password) {
User user = null;
List<User> users = userDao.getUserByProperty("name", name);
Iterator<User> iterator = users.iterator();
if(iterator.hasNext()){
user = iterator.next();
if(password.equals(user.getPassword())){
return user;
}
}
return null;
}
@Override
public void addUser(User user) {
userDao.save(user);
//手动抛出异常 以测试事务是否起作用
//throw new RuntimeException("异常");
}
}
在Spring配置文件中配置业务Bean的关键代码如示例7所示:
示例7:
<!--省略数据源 , SessionFactory Bean 以及DAO 的配置-->
<bean id="userBiz" class="cn.houserent.biz.impl.UserBizImpl">
<property name="userDao" ref="userDao"/>
</bean>
3.2 为业务层添加声明式事务处理
声明式事务需要用到tx和aop两个命名空间下的标签 . 因此在配置事务前 , 需要在Spring配置文件中导入这两个命名空间 . 代码如示例8所示:
示例8:
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 省略具体配置-->
</beans>
接下来需要配置一个事务管理器对象 , 它提供对事务处理的全面支持和统一管理 .
Spring 为Hibernate提供了事务管理类
HibernateTransactionManager
, 其配置方式如示例9所示:
示例9:
<!--省略数据源 SessionFactoryBean DAO 以及 Service的配置 -->
<!--定义事务管理器-->
<bean id="transactionManager"
class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
注意 : 配置HibernateTransactionManager
时 , 要为其sessionFactory
属性注入事先定义好的SessionFactoryBean
然后通过
<tx:advice>
标签配置事务增强 , 设定事务的属性 , 为不同的业务方法指定具体的事务规则 . 关键代码如示例10所示
示例10:
<!--省略数据源 SessionFactoryBean DAO 以及 Service的配置 -->
<!--省略定义事务管理器-->
<!--定义事务增强 , 并指定事务管理器-->
<tx:advice id="transactionInterceptor"
transaction-manager="transactionManager">
<!--定义事务属性 , 声明事务规则-->
<tx:attributes>
<tx:method name="find*" read-only="true"/>
<tx:method name="search*" read-only="true" />
<tx:method name="query*" read-only="true" />
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="register" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="del*" propagation="REQUIRED"/>
<tx:method name="do*" propagation="REQUIRED" />
<tx:method name="*" read-only="true"/>
</tx:attributes>
</tx:advice>
提示 : <tx:advice>
元素中的transaction-manager
属性的默认值是:transactionManager
, 也就是说 , 如果定义的事务管理器Bean的名称是transactionManager
, 则可以不指定该属性值
设置完事务规则 , 最后还要定义切面 , 将事务规则应用到指定的方法上 , 代码如示例11所示:
示例11:
<!--定义切面-->
<aop:config>
<!--定义切入点-->
<aop:pointcut id="pointcut"
expression="execution(* cn.houserent.biz..*.*(..))"/>
<!--将事务增强与切入点组合-->
<aop:advisor advice-ref="transactionInterceptor"
pointcut-ref="pointcut"/>
</aop:config>
至此 , Spring的声明式事务就配置完成了 , 最后总结一下配置的步骤 :
- (1) 导入tx 和 aop 命名空间
- (2) 配置事务管理器 , 并为其注入SessionFactoryBean
- (3) 通过
<tx:advice>
配置事务增强 , 绑定事务管理器并定义事务规则- (4) 配置切面 , 将事务增强与方法切入点组合
关于Spring整合hibernate中的事务配置可参考官网,如图所示:
编写测试方法 , 测试事务是否起了作用 , 在UserBizImpl
实现类中的addUser
方法中手动抛出异常 , 代码如下:
@Override
public void addUser(User user) {
userDao.save(user);
//手动抛出异常 测试事务是否有效果
throw new RuntimeException("异常");
}
测试方法代码如下:
@Test
public void test_05() {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
UserBiz userBiz = applicationContext.getBean(UserBiz.class);
User user = new User();
user.setId(1010);
user.setName("jack");
userBiz.addUser(user);
}
通过观察数据库的的变化 , 发现事务已经起了作用 .
随着事务配置的完成 , 业务层的开发就结束了 , 接下来需要将业务Bean注入给Struts2的Action , 这样就进入了Spring和Struts 2 的整合阶段
4 . 实现 Spring 和 Struts 2 的整合
由于Struts 2在设计时就已经充分考虑了和Spring的整合问题并给出了解决方案 , 所以整合过程变得非常简单 .
要实现Struts2 和 Spring整合 , 需要将struts2-spring-plugin.jar
文件添加到工程中 , 这个由Struts2 提供的插件是Struts2和Spring整合的关键所在 . 该jar文件中有一个struts-plugin.xml
文件 , 其中有如下配置片段:
<bean type="com.opensymphony.xwork2.ObjectFactory" name="spring" class="org.apache.struts2.spring.StrutsSpringObjectFactory" />
<!-- Make the Spring object factory the automatic default -->
<constant name="struts.objectFactory" value="spring" />
这段配置代码通过设置常量struts.objectFactory,将Struts2的对象创建工厂类替换成了StrutsSpringObjectFactory
, 该工厂类集成了Spring , 可以为Struts2创建的Action对象注入Spring管理下的业务Bean.
Struts2提供的整合插件使用了Spring的自动装配机制 . Struts2 提供的整合插件默认情况下会使用按名称匹配的方式为Action自动注入业务Bean . 在此模式下 , 要求Action中业务类属性的setter方法和Spring配置文件中所需业务Bean的名称一定要匹配 .
例如 , 在本章示例7中 , 我们在Spring配置文件中添加了一个名为userBiz
的业务Bean , 为了将此业务Bean注入对应的Action , 该Action类中如示例12中添加setter方法 .
示例12:
/**定义业务接口属性*/
private UserBiz userBiz;
/**
* 定义setter方法,方法名应和Spring配置文件中业务Bean的名称相匹配
* @param userBiz
*/
public void setUserBiz(UserBiz userBiz) {
this.userBiz = userBiz;
}
Spring就能根据该setter方法的名称获知所需的业务Bean名为"useBiz" . 在创建该Action的示例时 , 就会自动将该业务Bean注入给Action实例
综上所述 , 在默认条件下 实现Struts 2 和 Spring 的整合 , 只需要以下两个步骤:
- (1) 在工程中添加
struts2-spring-plugin.jar
文件- (2) 按照名称匹配的原则定义业务Bean和Action中的方法
实现用户登录的Action类及其在struts.xml文件中的配置如示例13所示:
示例13:
UserAction.java
代码如下:
/**
* 用户action
*/
public class UserAction extends ActionSupport {
/**
* 定义业务接口属性
*/
private UserBiz userBiz;
private String name;
private String password;
private String message;
public String login() throws Exception {
User user = userBiz.login(name, password);
ActionContext context = ActionContext.getContext();
if (null == name || "".equals(name)) {
this.setMessage("登录失败,用户名不能为空");
return "login_input";
}
if (null == password || "".equals(password)) {
this.setMessage("登录失败,密码不能为空");
return "login_input";
}
Map<String,Object> session = context.getSession();
if(null != user){
session.put("login",user);
return "login_success";
}
this.setMessage("登录失败,用户名或者密码有误");
return "login_input";
}
/**
* 定义setter方法,方法名应和Spring配置文件中业务Bean的名称相匹配
*
* @param userBiz
*/
public void setUserBiz(UserBiz userBiz) {
this.userBiz = userBiz;
}
//省略其他属性的getter setter
}
struts.xml
代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
<constant name="struts.ui.theme" value="simple"/>
<constant name="struts.i18n.encoding" value="UTF-8"/>
<constant name="struts.devMode" value="true"/>
<!--配置静态资源的路径-->
<constant name="struts.action.excludePattern" value="/static/.*?" />
<constant name="struts.enable.DynamicMethodInvocation" value="true"/>
<package name="renthouse" namespace="/"
extends="struts-default">
<global-allowed-methods>regex:.*</global-allowed-methods>
<!--使用动态方法方法实现用户登录(login)、注册 (register(省略))-->
<action name="user" class="cn.houserent.action.UserAction">
<result name="login_input">/page/login_struts2.jsp</result>
<result name="login_success" type="redirectAction">manage</result>
</action>
<!-- 这里只为演示登录功能,故省略其他配置 -->
<action name="manage">
<result>/page/manage.jsp</result>
</action>
</package>
</struts>
提示 : 通过在struts.xml
配置中struts.objectFactory.spring.autoWire
常量 , 可以改变插件的自动装配策略 , 如:
<constant name="struts.objectFactory.spring.autoWire" value="type"/>
将自动装配策略调整为按类型 . 这时setter方法名和Bean的名称可以不再匹配 , Spring会根据setter方法的参数类型查找合适的Bean实现注入
5 . 配置 web.xml
完成了Spring和Struts 2的整合 , 我们还需要在
web.xml
中进行最后的配置.
5.1 配置ContextLoaderListener
Spring需要启动容器才能为其他框架提供服务 , 配置代码如示例14所示。
示例14:
<!--配置环境参数 , 指定Spring配置文件的位置-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--配置Spring的ContextLoaderListener监听器,初始化Spring容器-->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<!--省略web.xml其他配置项-->
5.2 配合OpenSessionInViewFilter
Spring 提供了一个名为OpenSessionInViewFilter
的过滤器 , 可以和前面提到的事务管理器及HibernateDapSupport
很好的配合 . 起作用是把一个Hibernate Session和一次完整的请求过程绑定 , 在请求开始时开启Session , 请求结束时关闭Session . 这使得在一次请求的完整周期中 , 所使用的HibernateSession是唯一的且一直保持开启的可用状态 , 比较简便的解决了诸如延迟加载等问题.
OpenSessionInViewFilter
是一个标准的Servlet Filter , 其配置方式如示例15所示:
示例15:
<!--省略Spring的配置-->
<!--配置OpenSessionInViewFilter-->
<filter>
<filter-name>openSessionInViewFilter</filter-name>
<filter-class>
org.springframework.orm.hibernate5.support.OpenSessionInViewFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>openSessionInViewFilter</filter-name>
<url-pattern>*.action</url-pattern>
</filter-mapping>
<!--省略web.xml其他配置项-->
注意:
-
(1) OpenSessionInViewFilter默认会去Spring容器中查找名为sessionFactory的SessionFactoryBean .
-
如果之前我们在Spring中定义SessionFactoryBean的名称不是sessionFactory , 而是如mySessionFactory , 按照示例15配置的OpenSessionInViewFilter将无法获取所需的SessionFactory Bean . 这时就需要在配置时通过参数指定SessionFactory Bean的名称 , 源码如图所示:
-
参考代码如下:
<!--配置OpenSessionInViewFilter--> <filter> <filter-name>openSessionInViewFilter</filter-name> <filter-class> org.springframework.orm.hibernate5.support.OpenSessionInViewFilter </filter-class> <init-param> <param-name>sessionFactoryBeanName</param-name> <!--spring IOC容器中定义的SessionFactory的id--> <param-value>mySessionFactory</param-value> </init-param> </filter> <filter-mapping> <filter-name>openSessionInViewFilter</filter-name> <url-pattern>*.action</url-pattern> </filter-mapping>
-
-
(2) OpenSessionInViewFilter要配置在Struts2 核心控制器StrutsPrepareAndExecuteFilter的前面,否则无法发挥作用
5.3 完整的web.xml配置如下所示:
示例16:
完整的web.xml
配置如下:
<?xml version="1.0" encoding="utf-8"?>
<web-app 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_3_0.xsd"
version="3.0"
metadata-complete="true">
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!--配置环境参数 , 指定Spring配置文件的位置-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--配置Spring的ContextLoaderListener监听器,初始化Spring容器-->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<!--配置OpenSessionInViewFilter-->
<filter>
<filter-name>openSessionInViewFilter</filter-name>
<filter-class>
org.springframework.orm.hibernate5.support.OpenSessionInViewFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>openSessionInViewFilter</filter-name>
<url-pattern>*.action</url-pattern>
</filter-mapping>
<!--Struts2 配置-->
<filter>
<filter-name>struts2</filter-name>
<filter-class>
org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter
</filter-class>
<!--加载指定位置的struts2配置文件-->
<!--<init-param>
<param-name>config</param-name>
<param-value>struts-default.xml,struts-plugin.xml,struts/struts.xml</param-value>
</init-param>-->
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
最后 , 添加测试页面并部署运行即大功告成 , 效果如图所示:
至此 , 我们搭建的多模块SSH架构已经可以稳定的运行 , 并且性能良好。
Struts2 主持交互 , Hibernate负责数据持久化 , Spring管理组件间的依赖关系并提供事务管理等服务。
SSH架构在稳定性与生产效率上获得了极佳的平衡 , 是比较流行的企业级开发架构
6 . 使用 HibernateCallback 实现自定义功能
HibernateTemplate还提供了一种更加灵活的方式来操作数据库,通过这种方式可以完全使用Hibernate的操作方式 , 但它也失去了直接使用Hibernate Session的灵活性。在实现一些特殊功能的时候 , HibernateTemplate可能就无法解决了.
为此Spring提供了HibernateCallBack接口 . HibernateTemplate则提供两个方法完成和该接口配合
- Object execute(HibernateCallback action)
- List execute(HibernateCallback action)
接下来就以使用HQL执行分页查询为例 . 介绍HibernateCallBack的用法 . 在DAO类中添加分页查询的方法 , 关键代码如下:
示例17:
@Override
public List<House> query(int first, int size) {
return this.getHibernateTemplate().execute(new HibernateCallback<List<User>>() {
@Override
public List<User> doInHibernate(Session session) throws HibernateException {
Query query = session.createQuery("from House");
query.setFirstResult(first);
query.setMaxResults(size);
return query.list();
}
});
}
在示例17的代码中 , 并没有事先定义一个HibernateCallback接口的实现类 , 而是把接口的实现以及实例化的过程合二为一 , 这种用法叫做匿名内部类 . 当一个类不会在其他地方重用 , 仅用于唯一的功能时 , 采用此种做法可以减少不必要的类文件并可控制该类的可见范围.
Java8以及之后的版本可使用lambda表达式简化 , 简化代码如下:
@Override
public List<House> query(int first, int size) {
return this.getHibernateTemplate().execute(session->session.createQuery("from House").setFirstResult(first).setMaxResults(size).list());
}
这两个方法都需要一个HibernateCallback的实例,HibernateCallback实例可在任何有效的Hibernate数据访问中使用。程序开发者通过HibernateCallback,可以完全使用Hibernate灵活的方式来访问数据库,解决Spring封装 Hibernate后灵活性不足的缺陷。
HibernateCallback 是一个接口,该接口包含一个方法doInHibernate(org.hibernate. Session session),该方法只有一个参数Session。在开发中提供HibernateCallback实现类时,必须实现接口里包含的 doInHibernate方法,在该方法体内即可获得Hibernate Session的引用,一旦获得了Hibernate Session的引用,就可以完全以Hibernate的方式进行数据库访问。
注意:doInHibernate方法内可以访问Session,该Session对象是绑定在该线程的Session实例。该方法内的持久层操作,与不使用Spring时的持久层操作完全相同。这保证了对于复杂的持久层访问,依然可以使用Hibernate的访问方式。
**HibernateTemplate和HibernateCallback是一种典型的设计模式 , 模板模式在Spring框架中的应用还有很多 , 它的设计思想 : HibernateTemplate提供了常用方法的封装 , 以简化编程 ; **
HibernateCallback提供了对原始Sesion API的调用 , 解决了HibernateTemplate灵活性不足的问题 . 二者相互配合 , 兼顾开发效率和灵活性 . 是一种很好的解决方案.
7 . Struts 2 和 Spring 整合的第二种方式
7.1 配置过程
Spring可以把Action像业务类和DAO一样声明在它的配置文件中 , 这样就可以更加灵活的配置Action了 . 关键代码如示例18所示:
示例18:
applicationContext.xml
关键代码如下:
<!--省略数据源 SessionFactoryBean DAO 以及 Service的配置 -->
<!--在Spring配置文件中配置ActionBean,注意scope="prototype"属性 -->
<bean id="userAction" class="cn.houserent.action.UserAction" scope="prototype">
<property name="userBiz" ref="userBiz"/>
</bean>
<!--省略定义事务管理器-->
<!--省略定义事务增强 , 并指定事务管理器-->
<!--省略定义切面-->
在struts.xml
中配置Action:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
<constant name="struts.ui.theme" value="simple"/>
<constant name="struts.i18n.encoding" value="UTF-8"/>
<constant name="struts.devMode" value="true"/>
<!--配置静态资源的路径 静态文件的目录为static-->
<constant name="struts.action.excludePattern" value="/static/.*?" />
<constant name="struts.enable.DynamicMethodInvocation" value="true"/>
<package name="renthouse" namespace="/"
extends="struts-default">
<global-allowed-methods>regex:.*</global-allowed-methods>
<!--使用动态方法方法实现用户登录(login)、注册 (register(省略))-->
<action name="user" class="userAction">
<result name="login_input">/page/login_struts2.jsp</result>
<result name="login_success" type="redirectAction">manage</result>
</action>
<!--省略其他配置-->
</package>
</struts>
注意:
1. 采用此种配置方式时,Action的实例由Spring创建,Struts 2插件的工厂类只需根据Action Bean的id查找该组件使用即可
2. 此种方式可以为Action进行更灵活的配置,但代价是在Spring配置文件中需要定义很多Action Bean,增加了配置工作量,如非必需并非首选
3. 采用此种配置方式,同样需要添加struts2-spring-plugin-xxx.jar文件
最后 , 解释一下在Spring中配置ActionBean时出现的scope属性:
作用域 | 说 明 |
---|---|
singleton | 默认值。Spring以单例模式创建Bean的实例,即容器中该Bean的实例只有一个 |
prototype | 每次从容器中获取Bean时,都会创建一个新的实例 |
request | 用于Web应用环境,针对每次HTTP请求都会创建一个实例 |
session | 用于Web应用环境,同一个会话共享同一个实例,不同的会话使用不同的实例 |
global session | 仅在Portlet的Web应用中使用,同一个全局会话共享一个实例。对于非Portlet环境,等同于session |
> 注意:
与SpringMVC不同,Struts2是基于类的属性进行发的,定义属性可以整个类通用。
所以Struts2的Action是多实例的并非单例,也就是每次请求产生一个Action的对象。
Action类中往往包含了数据属性,例如在页面填写的form表单的字段,
Action中有对应的的属性来绑定页面form表单字段。
显然如果Action是单实例的话,那么多线程的环境下就会相互影响,
例如造成别人填写的数据被你看到了。
因为在和Spring一起使用的时候,Action交给Spring进行管理,默认的就是单例,所以在Spring整合Struts2开发时,如果需要用使用Struts2多例,就在spring的action bean配置的时候设置scope=”prototype”。
8 . 使用注解实现 SSH 集成
XML配置方式与注解方式对比如下:
- XML配置方式 : 可读性好 , 可扩展性好 , 可维护性好 , 但是开发效率低
- 注解方式 : 可以减少配置工作 , 提高开发效率 . 不足的是 , 注解和Java代码在同一文件中 , 虽然可提高内聚性 , 但如果对系统配置信息等进行调整 , 修改 , 则不得不修改Java文件 , 不够灵活 , 可扩展性差
由此看出 , 两种兼有优缺点 , 通常我们在项目开发过程中是将两种方式取长补短 , 结合使用 . 比较好的做法是将配置项中不会变化的或极少变化的部分使用注解完成 , 而经常变化或可能变化的部分采用XML配置方式 . 事实证明经常变化的部分是少数 , 对其使用XML配置 , 而对大量的不变化的部分采用注解方式 , 这样既能提高开发效率 , 又不失去灵活性和可扩展性.
8.1 Hibernate注解实现ORM映射
对租房系统中持久化类进行改造 , 使用注解ORM映射 , 如示例19所示:
示例19:
User.java
代码如下:
/**
* 用户
*/
@Entity
@Table(name="USERS")
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "seq_house")
@SequenceGenerator(name = "seq_house",sequenceName = "SEQ_ID")
private Integer id;
@Column
private String name;
@Column
private String password;
@Column
private String telephone;
@Column
private String username;
@Column
private String isadmin;
//省略getter setter
}
使用Hibernate注解后 , 需要在配置SessionFactory的对象关系映射时 , 使用packagesToScan
属性替代mappingLocations
来完成注解映射类添加 , 如示例20所示:
示例20:
<!-- 省略数据源的配置 -->
<!-- 定义SessionFactory Bean-->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
org.hibernate.dialect.Oracle10gDialect
</prop>
<prop key="show_sql">true</prop>
<prop key="format_sql">true</prop>
<!--注意此处是基于事务管理的,没有配置事务是不能使用currentSession的-->
<!--<prop key="current_session_context_class">thread</prop>-->
</props>
</property>
<!--加载由注解定义的持久化类-->
<property name="packagesToScan" value="cn.houserent.entity"/>
</bean>
-
通过
packagesToScan
属性 , 可以添加某个包下的所有带注解的持久化类 , 对于持久化类数量很多的情况 , 可以简化配置 . -
packageToScan
也可以接受多个包路径 , 用英文逗号隔开 . -
通过以上改造便完成了Spring容器对Hibernate注解的支持 , 减少了对ORM映射文件的依赖
8.2 Spring注解实现框架整合
开启Spring注解扫描 , 在DAO,业务类中使用注解
applicationContext.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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--Spring整合hibernate第一种方式:配置sessionFactory-->
<!--<bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="configLocation">
<value>classpath:hibernate.cfg.xml</value>
</property>
</bean>-->
<!--Spring整合hibernate的第二种方式:定义数据C3P0源-->
<context:property-placeholder location="classpath:c3p0.properties"/>
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close" >
<property name="user" value="${c3p0.user}"/>
<property name="password" value="${c3p0.password}"/>
<property name="driverClass" value="${c3p0.driverClass}"/>
<property name="jdbcUrl" value="${c3p0.jdbcUrl}"/>
<!--minPoolSize :最小连接数(default 3)-->
<property name="minPoolSize" value="${c3p0.minPoolSize}"/>
<!--maxPoolSize :最大连接数(default 15)-->
<property name="maxPoolSize" value="${c3p0.maxPoolSize}"/>
<!--initialPoolSize : 初始连接数(default 3)-->
<property name="initialPoolSize" value="${c3p0.initialPoolSize}"/>
<!--maxIdleTime :最大闲置时间, 未使用的连接在被丢弃之前在连接池存活时间,单位为秒(default 0, 永久存活)-->
<property name="maxIdleTime" value="${c3p0.maxIdleTime}"/>
<!--acquireIncrement : 获取连接数量, 连接不足时,c3p0尝试一次获取新连接的个数(default 3)-->
<property name="acquireIncrement" value="${c3p0.acquireIncrement}"/>
<!-- 空闲检查时间间隔, 每隔60秒检查连接池里的空闲连接 ,单位是秒-->
<property name="idleConnectionTestPeriod" value="${c3p0.idleConnectionTestPeriod}"/>
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
org.hibernate.dialect.Oracle10gDialect
</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<!--<prop key="hibernate.current_session_context_class">thread</prop>-->
<!--spring 整合hibernate管理事务后,由Spring的TransactionManager管理事务后,
currentSession是绑定到SpringSessionContext的,而不是thread。
此时hibernate.current_session_context_class应该是SpringSessionContext,
而它又会在使用LocalSessionFactoryBean时自动的设置。
所以就不需要你去设置current_session_context_class-->
<!-- <prop key="hibernate.current_session_context_class">
org.springframework.orm.hibernate5.SpringSessionContext
</prop>-->
</property>
<!--加载由注解定义的持久化类-->
<property name="packagesToScan" value="cn.houserent.entity"/>
</bean>
<!--启用注解扫描:-->
<context:component-scan base-package="cn.houserent"/>
<!-- 省略定义事务管理器-->
<!--省略定义事务增强 , 并指定事务管理器-->
<!--省略定义切面-->
</beans>
> 备注
Spring 整合Hibernate之后,由Spring的TransactionManager管理事务后, 所以currentSession是绑定到SpringSessionContext的,而不是thread。
因此
hibernate.current_session_context_class
的属性值应该是SpringSessionContext,而Spring又会在使用LocalSessionFactoryBean时自动的设置。所以也就不需要设置current_session_context_class
DAO中的关键代码如示例21所示:
/**
* 用户DAO
*/
@Repository("userDao")
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
//保留无参构造
public UserDaoImpl() {}
@Autowired//使用注解构造注入sessionFactory
public UserDaoImpl(@Qualifier("sessionFactory") SessionFactory sessionFactory){
this.setSessionFactory(sessionFactory);
}
//省略其他实现方法
}
注意:必须为DAO实现类注入sessionFactory
业务类中的关键代码如示例22所示:
示例22:
@Service("userBiz")
public class UserBizImpl implements UserBiz {
@Autowired
@Qualifier("userDao")
private UserDao userDao;
//省略其他实现方法
}
前面提过Struts 2 与 Spring 集成时有两种方式:
- 方式1:Struts 2 自己创建 Action , Action所依赖的业务对象交由Spring来管理
- 方式2:Action及其依赖的业务对象都交由Spring来创建管理
通常我们推荐使用方式1 , 此时在IoC容器中不存在Action配置项 , 故在Action中就没有使用使用Spring注解的必要了 , 在struts.xml中<action>
元素的class属性直接使用全类名配置即可 , 到此使用Spring注解便完成了SSH框架的整合.
但在第二种方式中 , Action也可以使用注解完成 , 而不在applicationContext.xml
中配置 , Action关键代码如下:
/**
* 用户action
*/
@Controller("userAction")
//@Scope("prototype")
@Scope(ConfigurableListableBeanFactory.SCOPE_PROTOTYPE)
public class UserAction extends ActionSupport {
/**
* 定义业务接口属性
*/
@Autowired
@Qualifier("userBiz")
private UserBiz userBiz;
//省略其他代码
}
struts.xml
中关键代码如下:
<!--使用动态方法方法实现用户登录(login)、注册 (register(省略))-->
<action name="user" class="userAction">
<result name="login_input">/page/login_struts2.jsp</result>
<result name="login_success" type="redirectAction">manage</result>
</action>
<!--省略其他代码-->
通过以上的改造后 , 即可将如下代码从applicationContext.xml
中删除:
<!--配置DAO -->
<bean id="userDao" class="cn.houserent.dao.impl.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!--省略其他DAO-->
<!--配置业务层-->
<bean id="userBiz" class="cn.houserent.biz.impl.UserBizImpl">
<property name="userDao" ref="userDao"/>
</bean>
<!--省略其他业务层-->
<!--Spring管理Action -->
<bean id="userAction" class="cn.houserent.action.UserAction" scope="prototype">
<property name="userBiz" ref="userBiz"/>
</bean>
到此便完成了有XML配置向注解配置的转换 , 可以看出目前只需要在applicationContext.xml
中配置少量的关键代码即可完成SSH框架的集成
8.3 Spring注解实现声明式事务处理
具体步骤如下:
- (1) 定义事务管理器 , 与XML配置方式相同
- (2) 在applicationContext.xml中开启事务注解的支持 , 如示例24所示
- (3) 在需要加入事务的业务类或方法前添加@Transactional注解 , 如示例25所示
示例24:
开启事务注解的支持 , applicationContext.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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--Spring整合hibernate的第二种方式:定义数据源 -->
<context:property-placeholder location="classpath:database.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="${jdbc_driver}"/>
<property name="jdbcUrl" value="${jdbc_url}"/>
<property name="user" value="${jdbc_user}"/>
<property name="password" value="${jdbc_password}"/>
<!--maxPoolSize :最大连接数(default 15)-->
<property name="maxPoolSize" value="20"/>
<!--minPoolSize :最小连接数(default 3)-->
<property name="minPoolSize" value="5"/>
<!--initialPoolSize : 初始连接数(default 3)-->
<property name="initialPoolSize" value="10"/>
<!--maxIdleTime :最大闲置时间, 未使用的连接在被丢弃之前在连接池存活时间,单位为秒(default 0, 永久存活)-->
<property name="maxIdleTime" value="120"/>
<!--acquireIncrement : 获取连接数量, 连接不足时,c3p0尝试一次获取新连接的个数(default 3)-->
<property name="acquireIncrement" value="2"/>
<!-- 空闲检查时间间隔, 每隔120秒检查连接池里的空闲连接 ,单位是秒-->
<property name="idleConnectionTestPeriod" value="60"/>
</bean>
<!-- 定义sessionFactory -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
org.hibernate.dialect.Oracle10gDialect
</prop>
<prop key="show_sql">true</prop>
<prop key="format_sql">true</prop>
<!--注意此处是基于事务管理的,没有配置事务是不能使用currentSession的
是在单独使用Hibernate时才需要的配置,这里不用配置-->
<!--<prop key="current_session_context_class">thread</prop>-->
</props>
</property>
<!--加载由注解定义的持久化类-->
<property name="packagesToScan" value="cn.houserent.entity"/>
</bean>
<!--省略启用注解扫描:-->
<context:component-scan base-package="cn.houserent"/>
<!--定义事务管理器-->
<bean id="transactionManager"
class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!--开启事务的注解支持-->
<tx:annotation-driven />
</beans>
示例25:
@Service("userBiz")
@Transactional
public class UserBizImpl implements UserBiz {
@Autowired
@Qualifier("userDao")
private UserDao userDao;
@Override
public User login(String name, String password) {
User user = null;
List<User> users = userDao.getUserByProperty("name", name);
Iterator<User> iterator = users.iterator();
if(iterator.hasNext()){
user = iterator.next();
if(password.equals(user.getPassword())){
return user;
}
}
return null;
}
//@Transactional(rollbackForClassName = "RuntimeException")
@Override
public void addUser(User user) {
userDao.save(user);
//throw new RuntimeException("异常");
}
//省略其他的实现方法
}
由此可见 , 相比XML配置方式 , 省去了事务切面的配置 , 简单了许多 . @Transactional
提供的默认事务属性能够满足大部分应用情况 , 如果需要调整 , 可以通过@Transactional
属性来指定 . 如下代码:
@Transactional(propagation = Propagation.REQUIRED,
isolation =Isolation.DEFAULT,readOnly = true)
> 表示事务属性如下:
- 传播行为 : 如果存在一个事务则加入当前事务
- 隔离级别 : 使用数据库默认的隔离级别
- 事务类型 : 只读事务