1 使用Spring提供的模板简化DAO开发
Spring内置了一组DAO组件,可以针对JDBC、Hibernate、iBATIS等常见数据访问技术提供简化操作,让我们把精力集中在核心的数据操作上。
(1)DAO模板
我们日常编写的数据访问代码中,大部分都是管道代码(重复不变的),只要少数几句核心代码是每个方法不相同的。Spring利用“模板方法”设计模式,把管道代码预先定义好,然后通过委托的方式,把方法的特定细节(变化部份)委托到外部交给程序员去实现,基本上消除了数据访问的冗余代码。
所谓“模板方法”设计模式,GoF的定义是:在一个方法里定义算法的骨架,将一些步骤延迟到其子类。下图显示,Spring的DAO模板(DAO Template)中定义了公共的DAO管道代码(如连接的开关和事务的开关),对于特定任务(如执行不同的SQL语句)则调用自定义DAO的回调对象(Java中的委托使用接口来实现)。
1.1 Spring的HibernateTemplate
Spring针对Hibernate提供了如下所示模板方法类,用于简化Hibernate操作。
org.springframework.orm.hibernate3.HibernateTemplate
下表为hibernateTemplate中的常用方法。
方法 | 描述 |
---|---|
T get(Class<T> entityClass, Serializable id) | 根据id查询单个持久化对象 |
Serializable save(final Object entity) | 保存(添加)实体对象并返回id |
void update(Object entity) | 更新实体对象 |
void delete(Object entity) | 删除持久化对象 |
List<?> find(String hql, Object... values) | 使用hql和顺序参数(对象数组,Object[])values查询持久化对象,并返回List集合 |
List<?> findByNamedParam(String hql, String[] paramNames, Object[] values) | 使用hql和命名参数(名数组和值数组)查询持久化对象,并返回List集合 |
List<?> findByCriteria(DetachedCriteria criteria, int firstResult, int maxResults) | 使用DetachedCriteria查询持久化对象,可以传入控制返回记录数(分页)的起始行号和最大返回行数 |
T execute(HibernateCallback<T> action) | 使用回调HibernateCallback接口控制session执行的全过程,整个执行过程由委托出来的实现类控制,适用于各种复杂场景 |
int bulkUpdate(String hql, Object... values) | 使用hql实现批处理(批量删除或更新) |
通过下面示例可以看到,使用了HibernateTemplate类的“模板方法”简化后的DAO开发,我们无需关心Session如何获取及其Transaction如何提交,只需要编写核心的数据访问代码即可(Session的打开关闭和事务处理等管道代码都由模板本身提供了)。
代码语言:javascript复制 public void updateUser(User user) {
hibernateTemplate().update(user);
}
public User fetchById(int id) {
return hibernateTemplate().get(User.class, id);
}
public User fetchUserByUsername(String username){
List<User> list = (List<User>)hibernateTemplate()
.find("from User u where u.username=?", username);
if(list.size()==0)
return null;
else
return list.get(0);
}
需要注意的是HibernateTemplate内部是依赖于Session的,因此需要为它注入SessionFactory对象。
在Spring整合Hibernate3开发时,我们可以通过两种方式来获得HibernateTemplate的支持。
(1)在DAO实现类中声明hibernateTemplate属性
定义DAO:
代码语言:javascript复制public class CategoryDaoImpl implements CategoryDao {
private HibernateTemplate hibernateTemplate;
public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
this.hibernateTemplate = hibernateTemplate;
}
public Category fetchById(int id){
return hibernateTemplate.get(Category.class, id);
}
……
}
实现对DAO类和HibernateTemplate类的依赖注入
代码语言:javascript复制 <bean id="hibernateTemplate"
class="org.springframework.orm.hibernate3.HibernateTemplate">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<bean id="categoryDao" class="mycinema.dao.impl.CategoryDaoImpl">
<property name="hibernateTemplate" ref="hibernateTemplate" />
</bean>
(2)继承HibernateDaoSupport基类,使用该基类内置的hibernateTemplate属性
代码语言:javascript复制public class MovieDaoImpl extends HibernateDaoSupport implements MovieDao {
public List<Movie> getAll() {
return (List<Movie>)getHibernateTemplate().find("from Movie");
}
……
}
实现对DAO类(继承自基类)的sessionFactory属性的依赖注入
代码语言:javascript复制 <bean id="movieDao" class="mycinema.dao.impl.MovieDaoImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
1.2 Spring整合Hibernate3的实现步骤
(1)添加相关依赖
以下示例使用了如下依赖:Hibernate3.6、MySQL驱动、DBCP数据源、Spring DI和Spring ORM。其中DBCP数据源和SpringORM为新增依赖。
代码语言:javascript复制 <!-- JDBC 驱动,必须 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.8</version>
</dependency>
<!-- dbcp 数据源(连接池),必须 -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.2</version>
</dependency>
<!-- Hibernate 3.6.10, Core Annotation,必须 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>3.6.10.Final</version>
</dependency>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.9.0.GA</version>
</dependency>
<!-- Spring DI容器 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.5.RELEASE</version>
</dependency>
<!-- Spring ORM -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>4.2.5.RELEASE</version>
</dependency>
(2)在Spring中配置SessionFactory
此前,我们使用hibernate.cfg.xml配置SessionFactory,现在要用Spring整合,所有功能bean都应由Spring提供,包括Hibernate的Session,因此SessionFactory也应该配置在Spring的applicationContext.xml中,而hibernate.cfg.xml则可以去掉。
为了加强数据库连接的管理,我们还应该配置数据源(DataSource),使用数据源和连接池提供连接对象给SessionFactory,这里使用DBCP作为数据源。
代码语言:javascript复制
<?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.xsd">
<!-- 配置数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/MyCinema" />
<property name="username" value="root" />
<property name="password" value="1234" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="0"></property>
<!-- 连接池最大数量 -->
<property name="maxActive" value="20"></property>
<!-- 连接池最大空闲 -->
<property name="maxIdle" value="20"></property>
<!-- 连接池最小空闲 -->
<property name="minIdle" value="1"></property>
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="60000"></property>
</bean>
<!-- 配置SessionFactory -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mappingResources">
<list>
<value>mappings/User.hbm.xml</value>
……
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hiberante.dialect">org.hibernate.dialect.MySQLInnoDBDialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
</beans>
(3)使用HibernateTemplate实现DAO功能
有两种方式可以使用HibernateTemplate:一、直接在DAO中包含一个HibernateTemplate对象,然后用Spring依赖注入;二、继承HibernateDaoSupport,父类中已经包含了HibernateTemplate,为DAO注入SessionFactory。
方式一:
代码部分,
代码语言:javascript复制public class UserDaoImpl implements UserDao {
private HibernateTemplate hibernateTemplate;
public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
this.hibernateTemplate = hibernateTemplate;
}
public User checkLogin(String username, String password) {
List<User> list= (List<User>)getHibernateTemplate()
.find("from User u where u.username=? and u.password=?", username, password);
return list.size()==0?null:list.get(0);
}
}
配置部分。
代码语言:javascript复制<!-- 配置hibernateTemplate对象 -->
<bean id="hibernateTemplate"
class="org.springframework.orm.hibernate3.HibernateTemplate">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- 将hibernateTemplate注入DAO类对象 -->
<bean id="userDao" class="mycinema.dao.impl.UserDaoImpl">
<property name="hibernateTemplate" ref="hibernateTemplate" />
</bean>
……
方式二:
代码部分,
代码语言:javascript复制public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
public User checkLogin(String username, String password) {
List<User> list= (List<User>)getHibernateTemplate()
.find("from User u where u.username=? and u.password=?", username, password);
return list.size()==0?null:list.get(0);
}
}
配置部分。
代码语言:javascript复制<!-- 将sessionFactory注入继承自HibenrateDaoSupport的DAO类对象 -->
<bean id="userDao" class="mycinema.dao.impl.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory " />
</bean>
1.3 使用Callback实现复杂的DAO操作
如果所需操作比较复杂,无法通过简单的模板方法调用来实现(如使用HQL分页或更复杂的查询),HibernateTemplate还提供了execute()方法,提供HibernateCallback类型的回调(委托)对象作为参数,让外界全程控制数据操作过程(完全控制Session和查询过程)。
T HibernateTemplate.execute (HibernateCallback<T> callback);
使用值得注意的是HibernateCallback参数:
(1)HibernateCallback是一个接口,该接口只有一个方法, doInHibernate (session),该方法的参数正是数据操作所需的Hibernate的Session。
- 方法 doInHibernate 的方法体就是Spring执行的Hibernate数据访问操作。
(3)使用HibernateTemplate执行execute (new HibernateCallback())方法,从doInHibernate得到session,并用session完成所需的数据访问操作。
(4)HibernateCallback回调对象实际就是一种事件委托模式,给使用者预留下了全程编码控制数据访问的位置。
下面是唱片标题的自动完成下拉提示的数据查询方法,其中使用了executeFind():
代码语言:javascript复制 public List<String> getAlbumTitlesByPrefix(final String prefix, final int count) {
List<String> list = getHibernateTemplate().executeFind(new HibernateCallback<List>() {
public List doInHibernate(Session sess){
String hql = "select a.title from Album a where a.title like :title";
return sess.createQuery(hql).setString("title", prefix "%")
.setMaxResults(count).list();
}
});
return list;
}
2 使用Spring的声明式事务管理
Spring利用AOP切面技术,为数据访问提供了基于业务层(一个业务方法往往代表一个事务,可以包含多个DAO方法)的声明式事务管理,完全透明地解决了事务难题。所谓声明式的事务管理:即只需配置,无须编程,利用AOP技术,把事务代码横切织入到数据访问代码中。
Spring针对不同的数据访问方式,提供了不同的事务管理器,如下所示:
2.1 使用Hibernate3的事务管理器
这里讨论的是Hibernate3的事务管理器:orm.hibernate3.HibernateTransactionManager。
(1)导入所需要依赖。
这里需要用到AOP和切面描述,因此需要在原来基础上添加Spring的切面依赖。
代码语言:javascript复制<!-- Spring 切面,可用于配置事务切面 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${springframework.version}</version>
</dependency>
(2)在Spring配置文件的文档声明中加入aop和tx(事务)配置声明。
代码语言:javascript复制
<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.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>
(3)配置Hibernate事务管理器。
代码语言:javascript复制 <bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
(4)配置AOP事务通知。
代码语言:javascript复制 <tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true" timeout="60"/>
<tx:method name="fetch*" read-only="true" timeout="60"/>
<tx:method name="add*" propagation="REQUIRED" timeout="60"/>
<tx:method name="update*" propagation="REQUIRED" timeout="60"/>
<tx:method name="delete*" propagation="REQUIRED" timeout="60"/>
<tx:method name="register" propagation="REQUIRED" timeout="60"/>
</tx:attributes>
</tx:advice>
(5)配置AOP切面(通知 切入点)。
代码语言:javascript复制 <aop:config>
<aop:advisor
pointcut="execution(* mycinema.biz..*.*(..))"
advice-ref="txAdvice"/>
</aop:config>
3 Spring整合Hibernate并使用注解配置
3.1 Hibernate实体注解配置
(1)持久化实体注解
Hibernate的注解配置其实是Java EE 官方JPA规范(在SUN制定EJB3.0的JPA规范时,Hibernate的作者受邀成为主要起草者)的一个实现;因此,我们下面看到的注解均来自于Java EE的官方包“javax. persistence.*”(ejb3-persistence.jar)。
注解 | 描述 |
---|---|
@Entity | 用于标注该类型是持久化类 |
@Table | 用于标注该持久化类所映射的数据库表 |
@Id | 用于标注该属性是持久化对象的主键属性 |
@GeneratedValue | 用于描述主键生成方式(主键值生成器,默认为auto) |
@SequenceGenerator | 用于描述主键生成器的序列(Oracle中的Sequence)信息 |
@Column | 用于标注该对象属性所映射的数据库表的字段信息 |
@ManyToOne | 用于标注该属性是多对一映射属性 |
@OneToOne | 用于标注该属性是一对一映射属性 |
@OneToMany | 用于标注该属性是一对多映射属性 |
@JoinColumn | 用于描述连接字段(外键字段)信息 |
@Transient | 用于标记某一个属性不需要要持久化 |
… | … |
(2)配置实体映射
示例:Category对象(One)的映射配置
代码语言:javascript复制import javax.persistence.*;
@Entity
@Table(name="Category")
public class Category {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
@Column(name="Name")
private String name;
@OneToMany(mappedBy="category")
private Set<Movie> movies = new HashSet<Movie>();
//…省略属性getter/setter…
}
示例:Movie对象的映射配置。
代码语言:javascript复制import java.sql.Date;
import javax.persistence.*;
@Entity
public class Movie {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
@Column
private String movieCode;
@Column
private String title;
@ManyToOne
@JoinColumn(name="CategoryId")
private Category category;
@Column
private String director;
@Column
private Date dateReleased;
//…省略属性getter/setter…
}
3.2 Spring整合Hibernate注解配置
如果Hibernate需要使用注解配置则,需要在SessionFactory配置中做以下修改。请注意红色字体部分。
代码语言:javascript复制 <!-- SessionFactory -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="annotatedClasses">
<list>
<value>mycinema.entity.Category</value>
<value>mycinema.entity.Movie</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
<!-- Hibernate Template -->
<bean id="hibernateTemplate"
class="org.springframework.orm.hibernate3.HibernateTemplate">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- 注解扫描范围 -->
<context:component-scan base-package="mycinema" />
</beans>
3.3 Open Session In View模式
Hibernate中虽然提供了Lazy load 延迟加载机制,但因为延时加载须要保证Session在不关闭的情况下才能进行,而我们往往在数据库事务结束时就已经吧Session关掉了,所以界面无法获得延时加载的外键属性。
Spring的orm包中包含了一个可以实现OpenSessionInView功能的过滤器,可以实现在界面层延时加载Hibernate实体中的外键属性。所谓OpenSessionInView,就是确保在用户请求(request)开始时打开Hibernate Session,直到请求结束返回了视图结果后Session才关闭,在此之间Session一直开着,并由该同一个Session完成请求中所有的数据操作。
在Spring Hibernate整合中,使用OpenSessionInView,只要在web.xml中配置以下过滤器即可(红字部份)。
<!--过滤器openSessionInViewFilter应位于struts2过滤器之前, 该过滤器类位于spring-orm包-->
代码语言:javascript复制<?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">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-beans.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter>
<filter-name>openSessionInViewFilter</filter-name>
<filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>openSessionInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
OpenSessionInView模式虽然可以发挥Hibernate中的延时加载特性,但也会带来另一个问题,就是Session打开的时间变长了,延长了Connection被占用的时间,这会对数据库性能有一些影响,是否应该使用须要具体问题具体分析;另外,Hibernate的延时加载策略也有其优缺点,也须要对症下药。