Spring系统学习笔记二
@(Experience)[java|Spring]
本笔记学习自Spring in Action 第三版
Spring 应用程序核心组件,将介绍如下方面:
- 数据库部分
- 事务管理
- Spring MVC
- Spring Web Flow
5 数据库
内容
- 定义Spring对数据访问的支持
- 配置数据库资源
- 使用Spring的JDBC模板
- Spring与Hibernate和JPA集成使用
5.1 Spring的数据访问哲学
DAO框架
5.1.1 了解Spring的数据访问异常体系
可能导致抛出SQLException的常见问题包括:
- 应用程序无法连接数据库;
- 要执行的查询有语法错误;
- 查询中所使用的表或列不存在;
- 试图插入或更新的数据违反了数据库的完整性约束。
Spring的平台无关持久化异常
Spring提供了多个访问异常,但是他并没有与特定的持久化方式相关联。
所有的异常都继承自DataAccessException,并且DataAccessException是非检查型异常,所以没有必要捕获Spring所抛出的数据访问异常。
5.1.2 数据访问模板化
Spring使用模板模式来设计Spring的数据访问。他将数据访问过程中固定的和可变的部分明确划分为两个不同的类:模板(template)和回调(callback)。
Spring提供了多个可选的模板:
5.1.3 使用DAO支持类
5.2 配置数据源
Spring提供了在AppliactionContext中配置数据源Bean的多种方式:
- 通过JDBC驱动程序定义的数据源;
- 通过JNDI查找的数据源;
- 连接池的数据源。
5.2.1 使用JNDI数据源
样例:
<jee:jndi-lookup id="dataSource" jndi-name="/jdbc/SpitterDS" resource-ref="true"/>
5.2.2 使用数据源连接池
Spring并没有提供数据源连接池实现,DBCP项目是一个非常不错的选择。
DBCP包含了多个提供连接池功能的数据源,最常用的是BasicDataSource。
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:hsql://localhost/spitter/spitter"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
<property name="initialSize" value="5"/>
<property name="maxActive" value="10"/>
</bean>
5.2.3 基于JDBC驱动的数据源
Spring提供了2种数据源对象:
DriverManagerDataSource :每个连接请求都会返回一个新建的连接。他没有进行池化管理。
SingleConnectionDataSource : 每个连接请求返回同一个连接。
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:hsql://localhost/spitter/spitter"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
5.3 在Spring中使用JDBC
5.3.1 使用JDBC模板
Spring为JDBC提供了3个模板类供使用。
- JdbcTemplate:最基本的JDBC模板,这个模板支持最简单的JDBC数据库访问功能以及简单的索引参数查询。
- NamedParameterJdbcTemplate:可以将查询值以命名参数的形式绑定到SQL中,而不是使用简单的索引参数。
- SimpleJdbcTemplate:该模板利用自动装箱、泛型、可变参数列表来简化JDBC模板的使用。
使用SimpleJdbcTemplate访问数据
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.simple.SimpleJdbcTemplate">
<constructor-arg ref="dataSource"/>
</bean>
将template装配入Dao:
public class JdbcSpitterDAO implements SpitterDAO{
...
private SimpleJdbcTemplate jdbcTemplate;
public void setJdbcTemplate(SimpleJdbcTemplate jdbcTemplate){
this.jdbcTemplate = jdbcTemplate;
}
}
<bean id="spitterDao" class="com.habuma.spitter.persistence.SimpleJdbcTemplateSpitterDao">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
基于template的增加:
public voidaddSpitter(Spitterspitter){
jdbcTemplate.update(SQL_INSERT_SPITTER,
spitter.getUsername(),
spitter.getPassword(),
spitter.getFullName(),
spitter.getEmail(),
spitter.isUpdateByEmail());
spitter.setId(queryForIdentity());
}
命名参数方式不占篇幅解释。
5.4 在Spring中集成Hibernate
延迟加载、预先抓取、级联。这些超越JDBC能力的特性在ORM工具中得到服务。
(延迟加载,预先抓取也是iBATIS的重要概念)。
Spring对ORM提供了集成点,以及一些附加的服务:
- Spring声明式事务的集成支持;
- 透明的异常处理;
- 线程安全的、轻量级的模板类;
- DAO支持类;
- 资源管理。
5.4.1 Hibernate预览
HibernateTemplate的职责之一是管理Hibernate的Session。
5.4.2 声明Hibernate的Session工厂
获取Hibernate的Session接口的标准方式是借助于Hibernate的SessionFactory接口的实现类。
如果要使用XML来定义对象与数据之间的映射,那么需要在Spring中配置LocalSessionFactoryBean。
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mappingResources">
<list>
<value>Spitter.hbm.xml </value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="dialect">org.hibernate.dialect.HSQLDialect</prop>
</props>
</property>
</bean>
如果希望用注解的方式,那就需要使用AnnotationSessionFactoryBean来代替LocalSessionFactoryBean:
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="packagesToScan" value="com.habuma.spitter.domain"/>
<property name="hibernateProperties">
<props>
<prop key="dialect">org.hibernate.dialect.HSQLDialect</prop>
</props>
</property>
</bean>
扫描packagesToScan指定的包中,含有JPA@Entity或@MappedSuperclass注解以及Hibernate的@Entity注解。
其他写法:
<property name="packagesToScan">
<list>
<value>com.habuma.spitter.domain</value>
</list>
</property>
<property name="annotatedClasses">
<list>
<value>com.habuma.spitter.domain.Spitter</value>
<value>com.habuma.spitter.domain.Spittle</value>
</list>
</property>
5.4.3 构建不依赖于Spring的Hibernate代码
package com.habuma.spitter.persistence;
import java.util.List;
import org.hibernate.SessionFactory;
import org.hibernate.classic.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.habuma.spitter.domain.Spitter;
import com.habuma.spitter.domain.Spittle;
@Repository
public class HibernateSpitterDao implements SpitterDao{
private SessionFactory sessionFactory;
@Autowired
public HibernateSpitterDao(SessionFactory sessionFactory){
this.sessionFactory = sessionFactory;
}
private Session currentSession(){
return sessionFactory.getCurrentSession();
}
public void addSpitter(Spitter spitter){
currentSession().save(spitter);
}
public Spitter getSpitterById(longid){
return(Spitter)currentSession().get(Spitter.class,id);
}
public void saveSpitter(Spitterspitter){
currentSession().update(spitter);
}
...
}
@Autowired注解将SessionFactory注入到sessionFactory中
@Repository注解一能被Spring的<context:component-scan>所扫描,二会在类上添加一个通知器,会捕获任何平台相关的异常并以Spring的非检查型数据访问异常重新抛出。
5.5 Spring与Java持久化API
Spring放弃了对EJB的支持,支持了JPA。
JPA诞生于EJB规范。
5.5.1 配置实体管理器工厂
基于JPA的应用程序使用EntityManagerFactory的实现类来获取EntityManager实例。JPA定义了两种类型的实体管理器:
- 应用程序管理类型
- 容器管理类型
这两种实体管理器工厂分别由对应的Spring工厂Bean创建的:
- LocationEntityManagerFactoryBean生成应用程序管理类型的Entity-ManagerFactory;
- LocalContainerEntityManagerFactoryBean生成容器管理类型的EntityManagerFactory。
使用应用程序管理类型的JPA
persistence.xml配置文件:
<persistencexmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<persistence-unit name="spitterPU">
<class>com.habuma.spitter.domain.Spitter</class>
<class>com.habuma.spitter.domain.Spittle</class>
<properties>
<property name="toplink.jdbc.driver" value="org.hsqldb.jdbcDriver"/>
<property name="toplink.jdbc.url" value="jdbc:hsqldb:hsql://localhost/spitter/spitter"/>
<propertyn ame="toplink.jdbc.user" value="sa" />
<propert yname="toplink.jdbc.password" value="" />
</properties>
</persistence-unit>
</persistence>
Spring bean配置:
<bean id="emf" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<property name="persistenceUnitName" value="spitterPU"/>
</bean>
使用容器管理类型的JPA
我们可以在容器中生成EntityManagerFactory,即可以将数据源信息配置在Spring应用的上下文,而不是persistence.xml中
<bean id="emf" class= "org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter" ref="jpaVendorAdapter"/>
</bean>
Spring提供了多个JPA厂商适配器:
- EclipseLinkJpaVendorAdapter
- HibernateJpaVendorAdapter
- OpenJpaVendorAdapter
- TopLinkJpaVendorAdapter
本例中,我们使用Hibernate作为JPA实现
<bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="database" value="HSQL"/>
<property name="showSql" value="true"/>
<property name="generateDdl" value="false"/>
<property name="databasePlatform" value="org.hibernate.dialect.HSQLDialect"/>
</bean>
从JNDI获取实体管理器工厂
<jee:jndi-lookup id="emf" jndi-name="persistence/spitterPU" />
5.5.2 编写基于JPA的DAO
一旦获取了EntityManagerFactory,就可以开始编写DAO了
package com.habuma.spitter.persistence;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.habuma.spitter.domain.Spitter;
import com.habuma.spitter.domain.Spittle;
@Repository("spitterDao")
@Transactional
public class JpaSpitterDao implements SpitterDao{
private static final String RECENT_SPITTLES="SELECTsFROMSpittles";
private static final String ALL_SPITTERS="SELECTsFROMSpitters";
private static final String SPITTER_FOR_USERNAME="SELECTsFROMSpittersWHEREs.username=:username";
private static final String SPITTLES_BY_USERNAME=
"SELECTsFROMSpittlesWHEREs.spitter.username=:username";
@PersistenceContext
private EntityManager em;
public void addSpitter(Spitter spitter){
em.persist(spitter);
}
public Spitter getSpitterById(long id){
returnem.find(Spitter.class,id);
}
public void saveSpitter(Spitter spitter){
em.merge(spitter);
}
...
}
6 事务管理
内容:
- 集成事务管理
- 编码方式管理事务
- 使用声明式事务
- 以注解的方式描述事务
在软件开发领域,全有或全无的操作被称为事务
6.1 理解事务
作为事务,这些过程将被视为一个操作,从而保证所有操作要么都成功要么全部回滚,就像这些操作从未发生过。
6.1.1 用4个词来表示事务
在传统的软件开发中,人们创建了一个术语来描述事务:A C I D。
原子性 Atomic,一致性 Consistent,隔离性 Isolated,持久性 Durable。
6.1.2 理解Spring对事务管理的支持
Spring提供了编码式和声明式事务管理的支持。
6.2 选择事务管理器
Spring并不直接管理事务,而是提供了多种事务管理器。每个事务管理器都会充当某一特定平台的事务实现的门面。
6.2.1 JDBC事务
DataSourceTransactionManager
<bean id="transactionManager" class="org.springframework.jdbc.
➥datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
6.2.2 Hibernate事务
<bean id="transactionManager" class="org.springframework.
➥orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
6.2.3 持久化API事务
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.EclipseLinkJpaDialect"/>
6.4 声明式事务
Spring提供3种方式来声明事务式边界。以前,Spring只能使用Spring AOP和TransactionProxyFactoryBean的代理Bean来实现声明式事务。现在使用Spring的tx命名空间和@Transactional注解。
6.4.1 定义事务属性
在Spring中,声明式事务是通过事务属性(transaction attribute)来定义的。
传播行为
事务的第一个方面是传播行为(propagation behavior)。传播行为定义了客户端与被调用方法之间的事务边界。
隔离级别
隔离级别定义了一个事务可能受其他并发事务影响的程度。
只读
声明事务的第三个特性是否是只读事务。
事务超时
定义事务的执行时间
回滚规则
默认情况下,遇到运行期异常回滚,二遇到检查型异常不会回滚
6.4.2 在XML中定义事务
命名空间:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/
spring-beans-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/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
tx配置事务:
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
6.4.3 定义注解驱动的事务
<tx:annotation-driven/>
指定特定的事务管理器
<tx:annotation-driven transaction-manager="txManager"/>
通过<tx:annotation-driven>元素高速Spring检查上下文中所有的Bean并查找使用@Transactional注解的Bean。
7 使用Spring MVC构建Web应用程序
内容:
- 映射请求到Spring控制器
- 透明地绑定表单参数
- 校验表单提交
- 上传文件
7.1 Spring MVC起步
7.1.1 跟踪Spring MVC请求
7.1.2 搭建Spring MVC
Spring MVC的核心是DispatcherServlet,这个组件充当了Spring MVC的前段控制器,与其他Servlet一样,DispatcherServlet必须在Web应用程序的web.xml中进行配置。
<servlet>
<servlet-name>spitter</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
因为servlet的名字为spitter,DispatcherServlet将尝试从一个名为spitter-servlet.xml文件(在WEB-INF目录下)中加载应用上下文。
<servlet-mapping>
<servlet-name>spitter</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
创建spitter-servlet.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:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<mvc:resources mapping="/resources/**" location="/resources/"/>
</beans>
mvc:resources建立了一个服务于静态资源的处理器。根据以上配置,所有以/resources路径开头的请求都会自动由应用程序根目录下的/resources目录提供服务。所有图片,样式表,JavaScript以及其他静态资源都必须放在/resources。
7.2 编写基本的控制器
7.2.1配置注解驱动的Spring MVC
Spring自带了多个处理器映射实现供我们选择:
- BeanNameUrlHandlerMapping :根据控制器Bean的名字将控制器映射到URL。
- ControllerBeanNameHandlerMapping:与上类似,Bean的名字不需要遵守URL的约定
- ControllerClassNameHandlerMapping:使用控制器的类名作为URL基础,将控制器映射到URL
- DefaultAnnotationHandlerMapping:将请求映射给使用@RequestMapping注解的控制器和控制器方法
- SimpleUrlHandlerMapping:使用定义在Spring应用上下文的属性集合将控制器映射到URL
7.2.2 定义首页的控制器
HomeController是一个基本的Spring MVC控制器,用来处理首页的请求
package com.habuma.spitter.mvc;
import javax.inject.Inject;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.habuma.spitter.service.SpitterService;
@Controller
public class HomeController{
public static final int DEFAULT_SPITTLES_PER_PAGE=25;
private SpitterService spitterService;
@Inject
public HomeController(SpitterService spitterService){
this.spitterService=spitterService;
}
@RequestMapping({"/","/home"})
public String showHomePage(Map<String,Object>model){
model.put("spittles",spitterService.getRecentSpittles(
DEFAULT_SPITTLES_PER_PAGE));
return "home";
}
}
测试控制器:
public class HomeControllerTest{
@Test
public void shouldDisplayRecentSpittles(){
List<Spittle> expectedSpittles= asList(newSpittle(),newSpittle(),newSpittle());
SpitterService spitterService=mock(SpitterService.class);
when(spitterService.getRecentSpittles(DEFAULT_SPITTLES_PER_PAGE)).
thenReturn(expectedSpittles);
HomeControllercontroller=
new HomeController(spitterService);
HashMap<String,Object> model =new HashMap<String,Object>();
String viewName=controller.showHomePage(model);
assertEquals("home",viewName);
assertSame(expectedSpittles,model.get("spittles"));
verify(spitterService).getRecentSpittles(DEFAULT_SPITTLES_PER_PAGE);
}
7.2.3 解析视图
Spring自带了多个视图解析器实现供选择。
解析内部视图
InternalResourceViewResolver是一个面向约定的元素。它将逻辑视图名称解析为View对象。
它通过逻辑视图名称添加前缀和后缀来确定Web应用程序中的模板的路径。
比如:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
如果home.jsp使用JSTL标签:
<bean class=
"org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
解析Tiles视图
Apache Tiles是一个模板框架,它将页面分成片段并在运行时组装成完整的页面。
<bean class= "org.springframework.web.servlet.view.tiles2.TilesViewResolver"/>
<bean class= "org.springframework.web.servlet.view.tiles2.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/views/**/views.xml</value>
</list>
</property>
</bean>
7.2.4 定义首页的视图
<%@ taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglibprefix="s"uri="http://www.springframework.org/tags"%>
<%@ taglibprefix="t"uri="http://tiles.apache.org/tags-tiles"%>
<%@ taglibprefix="fmt"uri="http://java.sun.com/jsp/jstl/fmt"%>
<div>
<h2>A globalcommunityoffriendsandstrangersspittingouttheir
inner-mostandpersonalthoughtsonthewebforeveryoneelseto
see.</h2>
<h3>Lookatwhatthesepeoplearespittingrightnow...</h3>
<ol class="spittle-list">
<c:forEach var="spittle" items="${spittles}">
<s:url value="/spitters/{spitterName}" var="spitter_url">
<s:param name="spitterName" value="${spittle.spitter.username}"/>
</s:url>
<li>
<span class="spittleListImage">
<img src=
"http://s3.amazonaws.com/spitterImages/${spittle.spitter.id}.jpg"
width="48"
border="0"
align="middle"
onError=
"this.src='<s:urlvalue="/resources/images"/>/spitter_avatar.png';"/>
</span>
<span class="spittleListText">
<a href="${spitter_url}">
<c:out value="${spittle.spitter.username}"/></a>
- <c:out value="${spittle.text}"/><br/>
<small><fmt:formatDate value="${spittle.when}"
pattern="hh:mmaMMMd,yyyy"/></small>
</span>
</li>
</c:forEach>
</ol>
</div>
7.2.5 完成Spring应用上下文
Web.xml配置ContextLoaderListener
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
必须制定ContextLoaderListener需要加载哪些配置文件,如果没有规定就会加载/WEB-INF/applicationContext.xml这个配置文件。
servlet配置:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spitter-security.xml
classpath:service-context.xml
classpath:persistence-context.xml
classpath:dataSource-context.xml
</param-value>
</context-param>