第五章 征服数据库(Spring对DB的使用)——开发持久层

第五章 征服数据库(Spring对DB的使用)——开发持久层

本章内容:

  • 定义Spring对数据库访问的支持
  • 配置数据库资源
  • 使用Spring的JDBC模板
在几乎所有的企业级应用中,都需要构建数据持久层。现在意义上的数据持久层是指把对象或者数据保存到数据库中,以及从数据库中取出数据。Spring提供了一组数据访问框架,它集成了多种数据访问技术。不管是JDBC、iBATIS还是Hibernate。


一、Spring的数据访问哲学

Spring开发者一直坚持的一个目标之一就是:允许开发人员在开发应用软件时能够遵循面向对象原则的“针对接口编程”。在Spring设计对数据库的支持中,特别能体现这样的思想。
DAO是指数据访问对象(data access object),它提供了数据读取和写入到数据库中的一种方式。Spring认为,它应该以接口的方式发布功能,而应用程序的其他部分需要通过接口的形式进行访问。
图5.1 数据访问层的合理方式


通过接口访问的方式设计有几点好处:
  • 使得服务对象易于测试。因为他们不再与特定的数据库访问实现——DAO实现——绑定在一起,你可以为这些数据访问接口创建mock 实现(假实现),这样会显著提高单元测试的效率。
  • 数据访问层是以持久化技术无关的方式来进行访问的。(不是很理解)

为了实现数据访问层与应用程序的的其他部分的解耦,Spring采用的一个方式就是提供贯穿整个DAO框剪的统一异常体系。


1、了解Spring的数据访问异常

JDBC中的SQLException,它表示尝试访问数据库时出现异常,但是却无法做出相对应的处理,太模糊了
Hibernate提供了20个左右的异常,分别对应特定的数据库访问问题。太多了,需要写20个左右的catch块。而且,Hibernate异常时其特有的,试想下,如果持久层抛出特定的Hibernate的异常,那么应用程序层必须解释并处理该异常,使得Hibernate持久层与程序应用层耦合,不符合我们的“针对接口编程”的思想原则
因此,我需要的是数据访问异常具有描述性有与特定的持久化框架无关(减少耦合)

Spring的平台无关持久化异常且不用写catch代码块
Spring的异常都继承自DataAccessException,它是一个非检查型异常。至于是否有必要捕获异常,决定权在开发人员手中


为了使用Spring的数据访问异常,就需要使用Spring所提供的数据访问模板(Spring的数据访问模板具体是一个什么样的概念?)


2、数据访问模板化

在搭乘飞机旅行时,很重要的一件事就是行李托运。这件事包含很多步骤:
    • 在柜台办理行李托运(需要自己参与)
    • 安保人员对行李进行检查
    • 行李通过行李车送往飞机
    • 如果你需要转机,行李也需要转机
    • 到达目的地后,行李被取下来,放到传送带上
    • 到行李认领处收取行李(需要自己参与)
虽然这个步骤很繁琐,但是需要我们自己参与的只有两步。这个示例其实讲述了一个很强大的设计模式:模板方法
模板方法定义了过程中的主要框架。过程中的某些步骤是不变的(步骤2、3、4、5),过程执行的顺序也是不变的,在过程中的特定步骤是变化的(步骤1、6),在这个时候,过程会将工作委托给子类来完成一些特定的实现细节。这是过程中的可变化部分。从软件的角度上看,模板方法将过程中特定实现相关的部分委托给接口,而这个接口的不同实现定义了过程中的具体行为。 这也是Spring在访问数据时采用的模式。
Spring将数据的访问过程中固定的和可变的部分明确的划分为两个不同的类:模板( template)和回调( callbak)。Spring的DAO模板类和自定义的DAO回调类。

使用这个模板框架,我们只需要关注自己的数据访问逻辑就好。
针对不同的持久化平台,Spring提供了多个可选的模板。JDBC就算这JdbcTemplate等

3、使用DAO支持类
数据访问模板并不是Spring数据访问框架的全部。每个模板提供了一些简便方法,使得我们不必创建明确的回调实现,从而简化数据访问。——DAO支持类。Spring提供了DAO支持类,而将业务自己的DAO类作为它的子类。
图3 应用程序DAO、Spring的DAO支持类以及模板类的关系


当编写应用程序自己的DAO实现时,可以继承DAO支持类并获取访问方法来直接访问底层的数据访问模板。
例如,应用程序的DAO继承自JdbcDaoSupport,那么它只要盗用getConnection()方法就可以获得JdbaTemplate并使用它。如果你需要访问底层的持久化平台,则每个DAO支持类都能够访问其与数据库进行通信的类。例如,JdbaDaoSupport包含一个getConnection()方法,可以直接处理JDBC连接。
总结一句话:Spring DAO支持类提供了方便的方式来使用数据访问模板
表3 

在后续介绍Spring如何继承持久化框架时,我们首先介绍JDBC(因为他是最基础的读写方式),而后会介绍hibernate和JPA,它们是现在最流行的基于POJO和ORM的解决方案
但是Speing支持大多数持久化功能都依赖于数据源,因此,在创建模板和DAO之前,我们需要在Spring中配置一个数据源以便DAO可以访问数据库。

二、 配置数据源

Spring提供了在上下文中配置数据源Bean的多种方式:
  • 通过JDBC驱动程序定义数据源
  • 通过JNDI查找的数据源
  • 连接池数据源
我们建议使用连接池获取连接,如果可以,也建议通过应用服务器的JNDI来获取池中的数据源
1、使用JNDI数据源
Spring应用程序经常部署在JavaEE应用服务器上,如Tomcat的Web容器。这些服务器允许你配置通过JNDI获取数据源。这样的好处是使数据源完全独立于应用程序之外;另外,应用服务器中管理数据源通常以池的方式组织,从而具备更好的性能,支持系统管理员对其进行热切换。
在Spring中如何配置JNDI数据源引用?——位于jee命名空间下的<jee:jndi-lookup>元素可以减速JNDI中的任何对象(包括数据源)并将其装配到Spring Bean中
eg:
<jee:jndi-lookup id="dataSource" 
	  jndi-name="/jdbc/SpitterDS"
	  resource-ref="ture"/>
这里需要注意的是:
jndi-name="/jdbc/SpitterDS"指定了JNDI中的资源名称,如果只设置了jndi-name,则会根据指定的名称查找数据源。但是如果应用程序运行在Java应用程序服务器中,这需要将resource-ref设置为true,这样给定的jndi-name将会uzidong添加java:comp/env/前缀

2、使用数据源连接池
相比于JNDI查找数据源,数据库连接池是直接配置在Spring中(也就是说系统管理员不能进行热切换)
使用数据源连接池推荐集成DBCP项目:https://commons.apache.org/proper/commons-dbcp/
DBCP包含了多个数据源类,其中最常用的是BasicDataSource,它类似于Spring直达udeDriverManagerDataSource
在Spring中配置DBCP的数据源方式:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	default-init-method="turnOnLights"
	default-destroy-method="turnOffLights"
	xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
	
	<!-- Bean declarations go here -->
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost:3306/mysql" />
		<property name="username" value="root" />
		<property name="password" value="mysql" />
		<property name="initialSize" value="5" />
		<property name="maxActive" value="10" />
	</bean>
</beans>

3、基于JDBC驱动的数据源
在Spring中,通过JDBC驱动配置数据源最简单。Spring提供了两种数据源对象供选择:
  • DriverMavagerDataSource:在每个连接请求时会返回一个新的新建连接,并没有和DBCP一样采用池化管理。
  • SimpleConnectionDataSource:在每个连接请求时都会返回同一个连接。尽管他不是严格的连接池,但是你可以看成是initialSize和maxActive都为1的连接池。
上述的配置与BasciDataSource配置类似,略。
介绍完数据源后,接下来就是要实际访问数据库了。在下一节会介绍如何使用Spring对JDBC的支持为应用程序构建持久层。如果是使用Hibernate和JPA构建,看最后两节。
三、在Spring中使用JDBC——JDBC虽然比较原始,但是他允许用户使用数据库的所有特性,而其他持久层框架不鼓励甚至是禁止的。
1、应对时空的JDBC代码
直接使用JDBC向数据库中插入数据,那么需要创建连接,执行语句,处理异常和关闭连接。它十分繁琐,我们的目标是专注业务逻辑。
package com.springinaction.Spitter;

import java.sql.*;
import javax.sql.DataSource;

public class SpitterDAO {
	public static final String SQL_SELECT_SPITTER = "select id, uusername, fullname from spitter where id =?";
	public static final String SQL_INSERT_SPITTER = "insert into spitter(username, password, fullname) values (?,?,?)";
	public static final String SQL_UPDATE_SPITTER = "update spitter set username = ? , password = ? , fullname = ? where id = ?";
	
	public static final String SQL_INSERT_SPITTER2 = "insert into spitter(username, password, fullname) values (:username, :password, :fullname)";
	
	private DataSource datasource;
	public DataSource getDatasource() {
		return datasource;
	}
	public void setDatasource(DataSource datasource) {
		this.datasource = datasource;
	}

	public Spitter getSpittterById(long id){
		Connection conn = null;
		PreparedStatement stmt = null;
		ResultSet rs = null;
		try {
			conn = datasource.getConnection();
			stmt = conn.prepareStatement(SQL_SELECT_SPITTER);
			stmt.setLong(1, id);
			rs = stmt.executeQuery();
			Spitter spitter = null;
			if(rs.next()){
				spitter = new Spitter();
				spitter.setId(rs.getLong("id"));
				spitter.setUsername(rs.getString("username"));
				spitter.setPassword(rs.getString("password"));
				spitter.setFullname(rs.getString("fullname"));
			}
			return spitter;
		} catch (SQLException e) {
			
		}finally{
			if(rs != null){
				try{rs.close();}catch(SQLException e){}
			}
			if(stmt != null){
				try{stmt.close();}catch(SQLException e){}
			}
			if(conn != null){
				try{conn.close();}catch(SQLException e){}
			}
		}
		return null;
	}

	public void addSpitter(Spitter spitter){
		Connection conn = null;
		PreparedStatement stmt = null;
		try {
			conn = datasource.getConnection();
			
			stmt = conn.prepareStatement(SQL_INSERT_SPITTER);
			
			stmt.setString(1, spitter.getUsername());
			stmt.setString(2, spitter.getPassword());
			stmt.setString(3, spitter.getFullname());
			
			stmt.execute();
		} catch (SQLException e) {
			
		}finally{
			if(stmt != null){
				try{stmt.close();}catch(SQLException e){}
			}
			if(conn != null){
				try{conn.close();}catch(SQLException e){}
			}
		}
	}
	
	public void saveSpitter(Spitter spitter){
		Connection conn = null;
		PreparedStatement stmt = null;
		try {
			conn = datasource.getConnection();
			
			stmt = conn.prepareStatement(SQL_UPDATE_SPITTER);
			
			stmt.setString(1, spitter.getUsername());
			stmt.setString(2, spitter.getPassword());
			stmt.setString(3, spitter.getFullname());
			
			stmt.execute();
		} catch (SQLException e) {
			
		}finally{
			if(stmt != null){
				try{stmt.close();}catch(SQLException e){}
			}
			if(conn != null){
				try{conn.close();}catch(SQLException e){}
			}
		}
	}

}

2、使用JDBC模板
Spring的JDBC模板承担了资源管理和异常处理的工作,简化了JDBC的代码。Spring为JDBC提供了3个模板类:
  • JdbcTemplate:最基本的Spring JDBC模板,它支持最简单的JDBC数据库访问功能意见简单的缩影参数查询。
  • NamedParameterJdbaTemplate:使用该模板类执行查询操作时,可以将查询值以命名参数的形式绑定到SQL中,而不是使用简单的索引参数。
  • SimpleJdbcTemplate:该模块利用Java 5的一些特性(如:自动装箱、泛型和可变参数列表)来简化JDBC模板的使用
使用那个模板是个问题。但是,Spring2.5中NamedParameterJdbaTemplate所具备的命名参数功能  被合并到SimpleJdbcTemplate中;Spring 3.0对Java 5之前的版本已经不再支持——所以相对于SimpleJdbcTemplate,我们没有任何理由再选择JdbcTemplate。我们只需关注SimpleJdbcTemplate。
使用SimpleJdbcTemplate访问数据

在DAO中具备可用的SimpleJdbcTemplate后,我们就可以简化我们的JDBC程序了。
其中上面SpitterDAO的addSpitter()方法改写如下:

使用SimpleJdbcTemplate也可以简化数据读取操作

相对于之前的JDBC代码,现在的getSpitterById方法不需要关注资源管理和异常处理,只需专注业务逻辑。
使用命名参数
在上面利用SimpleJdbcTemplate改写addSpitter()方法时使用了索引参数,这意味着我们必须关注SQL_INSERT_SPITTER语句的参数顺序。
除了使用索引参数外,我们还可以使用命名参数:命名参数可以赋予SQL中每一个参数一个明确的名字,在绑定值到查询语句的时候通过该名字来引用参数。
例如:SQL_INSERT_SPITTER的语句是这样的
public static final String SQL_INSERT_SPITTER2 = "insert into spitter(username, password, fullname) values (:username, :password, :fullname)";
那么addSpitter方法可以这么改写:

使用Spring的JDBC DAO支持类

思考一个问题,我们在定义JdbcSpitterSDAO.java类时,DAO类需要添加一个SinpleJdbcTemplate的属性以及对于的get/set方法用来注入。如果一个应用程序只有一个DAO类,添加这个属性没什么问题,但是实际上我们会面对很多对象的DAO,这样就会产生大量重复的工作。

解决方案是为所有的DAO类创建一个通用的父类,在其中有SinpleJdbcTemplate属性,子类通过父类进行数据库访问。如图:


实际上,Spring框架提供了3个支持类:JdbcDaoSupport、SimpleJdbcDaoSupport和NamedParameterJdbcDaoSupport。通过使用Spring的JDBC DAO 支持类来使用JDBC模板。

通过SimpleJdbcDaoSupport类的getSimpleJdbcTemplate()方法来获取JDBC模板。所以上面的addSpitter()方法可以改成成:

在装配JdbcSpitterDao中,可以直接将一个SimpleJdbcTemplate Bean装配到父类的jdbcTemplate属性中:

上面这个例子与没有继承SimpleJdbcDaoSupport的DAO没有差别(实际上是由dataSource--->jdbcTenplate--->getSimpleJdbcTemplat),另一种方法是跳过中间Bean直接将数据源装配给JdbcSpitterDao的dataSource属性,直接俄格属性是继承自SimpleJdbcDaoSupport的
私下认为上面这段文字不能理解!
四、在Spring中集成Hibernate

在持久化的世界中,JDBC就像一辆自行车——尽管我们还怀念骑自行车的日子(JDBC比较简单实用),但是我们现在需要的不仅仅是一辆自行车了。
现在我们需要考虑更复杂的特性了:
  • 延迟加载(Lazy loading)
  • 预先抓取(Eager fetching)
  • 级联(Cascading)
对于这些功能,一些ORM框架提供。持久层使用ORM技术可以节省数千行代码和大量开发时间,把你的注意力专注到程序的真正需求上。Spring对多个ORM框架都提供了支持,包括Hibernate、iBATIS、Java数据对象以及JPA(Java持久化API)。
对于这些框架,与Spring对JDBC那样,Spring对ORM框架提供了 与这些框架的集成点一些附加服务。主要有:
  • Spring声明式事务的集成支持
  • 同名的异常处理
  • 线程安全的、轻量级的模板类
  • DAO支持类
  • 资源管理
这里我们重点介绍Spring与Hibernate的集成,其他的ORM框架大同小异。
1、Hibernate 概览
在以前,Spring对Hibernate的支持也是通过类似的模板类来 抽象Hibernate的持久化功能。Spring使用Hibernate是通过HibernateTemplate进行的,HibernateTemplate简化了Hibernate的繁琐工作,捕获Hibernate的特定异常然后转换成Spring的非检查型异常并重新抛出实现的。从这一点上是讲,HibernateTemplate与SimpleJdbcTemplate类似。(HibernateTemplate也管理着Session,包括打开和关闭每一个Session、确保每个事务使用相同的Session)
尽管HibernateTemplate还是存在,但是它却不是Spring与Hibernate集成的最佳方式了。Hibernate 3引入了上下文Session——这是Hibernate本身提供的保证每一个事务使用同一Session的方案,这种方案可以让你的DAO类不包含特定的Spring代码。我们这里重点介绍上下文Session的使用
在介绍Hibernate的上下文Session之前,我们需要为Hibernate做一些准备工作,即在Spring里配置Session Factory。
2、声明Hibernate的Session工厂
使用Hibernate的主要接口是org.hibernate.Session。Session接口提供了基本的数据访问功能,如保存、更新、删除以及从数据库加载对象的功能。通过Session接口,应用程序的DAO可以满足业务持久化的需求。
获取HIbernate Session对象的标准方式是将诶主Hibernate 的SessionFactory接口的实现类。这个接口主要负责Hibernate Session的打开、关闭以及管理。
在Spring应用程序中,我们要通过Spring的某个Hibernate Session工厂Bean来获取Hibernate的SessionFactory。在Spring的应用上下文中,我们可以把它当作普通的Bean来配置,获取SessionFactory。
选择Spring的那个Hibernate SessionFactory来配置bean有点讲究:如果我们选择XML文件中定义对象与数据库之间的映射,我们需要在Spring上下文中配置LocalSessionFactoryBean;如果我们更倾向与使用注解的方式来定义对象与数据库之间的关系,选择AnnotationSessionFactoryBean。
LocalSessionFactoryBean:
<!-- Bean declarations go here -->
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost:3306/mysql" />
		<property name="username" value="root" />
		<property name="password" value="mysql" />
		<property name="initialSize" value="5" />
		<property name="maxActive" value="10" />
	</bean>
	
	<bean id="sessFoatory" 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:
<!-- Bean declarations go here -->
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost:3306/mysql" />
		<property name="username" value="root" />
		<property name="password" value="mysql" />
		<property name="initialSize" value="5" />
		<property name="maxActive" value="10" />
	</bean>
	
	<bean id="sessFoatory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="namingStrategy" value="com.springinaction.Spitter" />
		<property name="hibernateProperties">
			<props>
				<prop key="dialect">org.hibernate.dialect.HSQLDialect</prop>
			</props>
		</property>
	</bean>

3、构建不依赖与Spring的Hibernate代码
正如前面介绍的一样,在没有上下文Session时,Spring的Hibernate模板用于保证每个事务使用同一个Session。既然Hibernate自己能够管理,那么就没必要使用模板类,你可以直接将Hibernate Session装配到DAO类中。
五、Spring与Java持久化API

总结:
数据是应用程序的血液。鉴于数据的重要地位,健壮、简单、清晰的开发应用程序访问数据部分就显得举足轻重。Spring对JDBC和ORM框架的支持简化了各种持久化机制都存在的样板代码,我们只需要关注与应用程序相关的数据访问即可。
Spring简化数据访问的方式之一就是管理数据库连接的生命周期和ORM框架的Session,以确保他们能根据需要打开关闭。通过这种方式,持久化机制的管理对应用程序代码完全是透明的。
另外,Spring能够捕获框架的特定异常并转换为异常体系中非检查型异常,对于Spring支持的所有持久化框架,这个异常体系都是一致的。
在数据库操作中,Spring能够简化和透明化的另一个重要的方面就是事务管理。下一章,会介绍Spring AOP实现声明式事务管理。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值