为了避免应用与特定的数据访问策略耦合在一起,编写良好的Repository应该以接口的方式暴露功能。
服务对象本身并不会处理数据访问,而是将数据访问委托给Repository。Repository接口确保其与服务对象的松耦合
这样做会有几个好处。第一,它使得服务对象易于测试,因为它们不再与特定的数据访问实现绑定在一起。实际上,你可以为这些数据访问接口创建mock实现,这样无需连接数据库就能测试服务对象,而且会显著提升单元测试的效率并排除因数据不一致所造成的测试失败。
此外,数据访问层是以持久化技术无关的方式来进行访问的。持久化方式的选择独立于Repository,同时只有数据访问相关的方法才通过接口进行暴露。这可以实现灵活的设计,并且切换持久化框架对应用程序其他部分所带来的影响最小。如果将数据访问层的实现细节渗透到应用程序的其他部分中,那么整个应用程序将与数据访问层耦合在一起,从而导致僵化的设计。
原生JDBC报错,SQLException的常见问题主要有:
1.应用程序无法连接数据库;
2.要执行的查询存在语法错误;
3.查询中所使用的表和/或列不存在;
4.试图插入或更新的数据违反了数据库约束。
Spring所提供的平台无关的持久化异常
Spring的异常体系比JDBC简单的SQLException丰富得多而且它并没有与特定的持久化方式相关联。这意味着我们可以使用Spring抛出一致的异常,而不用关心所选择的持久化方案。这有助于我们将所选择持久化机制与数据访问层隔离开来。
这些异常都继承自DataAccessException。DataAccessException的特殊之处在于它是一个非检查型异常。(可以不捕获)
数据访问模板化
Spring将数据访问过程中固定的和可变的部分明确划分为两个不同的类:模板(template)和回调(callback)。模板管理过程中固定的部分,而回调处理自定义的数据访问代码。
Spring的模板类处理数据访问的固定部分——事务控制、管理资源以及处理异常。同时,应用程序相关的数据访问——语句、绑定参数以及整理结果集——在回调的实现中处理。
1.配置数据源
Spring提供了在Spring上下文中配置数据源bean的多种方式,包括:
1. 通过JDBC驱动程序定义的数据源;
2. 通过JNDI查找的数据源;
3.连接池的数据源。
1.使用JNDI数据源
利用Spring,我们能像使用Spring bean那样配置JNDI中数据源的引用并将其装配到需要的类中。位于jee命名空间下的<jee:jndi-lookup>元素可以用于检索JNDI中的任何对象(包括数据源)并将其作为Spring的bean。如果应用程序的数据源配置在JNDI中,我们可以使用<jee:jndi-lookup>元素将其装配到Spring中,如下所示:
jndi-name属性用于指定JNDI中资源的名称。如果只设置了jndi-name属性,那么就会根据指定的名称查找数据源。但是,如果应用程序运行在Java应用服务器中,你需要将resource-ref属性设置为true,这样给定的jndi-name将会自动添加“java:comp/env/”前缀。
java代码配置如下:
2.使用数据源连接池
Spring并没有提供数据源连接池实现,但是我们有多项可用的方案,包括如下开源的实现:
1.Apache Commons DBCP (http://jakarta.apache.org/commons/dbcp);
2.c3p0 (http://sourceforge.net/projects/c3p0/) ;
3.BoneCP (http://jolbox.com/) 。
大致配置如下:
前四个属性是配置BasicDataSource所必需的。属性driverClassName指定了JDBC驱动类的全限定类名。属性url用于设置数据库的JDBC URL。最后,username和password用于在连接数据库时进行认证。以上四个基本属性定义了BasicDataSource的连接信息。
<!--2.数据库连接池-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--配置连接池属性-->
<property name="driverClass" value="${driver}" />
<!-- 基本属性 url、user、password -->
<property name="jdbcUrl" value="${url}" />
<property name="user" value="${user}" />
<property name="password" value="${password}" />
<!--c3p0私有属性-->
<property name="maxPoolSize" value="30"/><!-- 最多保存30个连接对象 -->
<property name="minPoolSize" value="10"/>
<!--关闭连接后不自动commit-->
<property name="autoCommitOnClose" value="false"/>
<!--获取连接超时时间-->
<property name="checkoutTimeout" value="1000"/>
<!--当获取连接失败重试次数-->
<property name="acquireRetryAttempts" value="20"/>
</bean>
3.基于JDBC驱动的数据源
在Spring中,通过JDBC驱动定义数据源是最简单的配置方式。Spring提供了三个这样的数据源类(均位于org.springframework.jdbc.datasource包中)供选择:
1.DriverManagerDataSource:在每个连接请求时都会返回一个新建的连接。与DBCP的BasicDataSource不同,由DriverManagerDataSource提供的连接并没有进行池化管理;
2.SimpleDriverDataSource:与DriverManagerDataSource的工作方式类似,但是它直接使用JDBC驱动,来解决在特定环境下的类加载问题,这样的环境包括OSGi容器;
3.SingleConnectionDataSource:在每个连接请求时都会返回同一个的连接。尽管SingleConnectionDataSource不是严格意义上的连接池数据源,但是你可以将其视为只有一个连接的池。
以上这些数据源的配置与DBCPBasicDataSource的配置类似。
如:
与具备池功能的数据源相比,唯一的区别在于这些数据源bean都没有提供连接池功能,所以没有可配置的池相关的属性。
4.使用嵌入式数据源(不多)
嵌入式数据库作为应用的一部分运行,而不是应用连接的独立数据库服务器。
如:
将<jdbc:embedded-database>的type属性设置为H2,表明嵌入式数据库应该是H2数据库(要确保H2位于应用的类路径下)。另外将type设置为DERBY,以使用嵌入式的Apache Derby数据库。在<jdbc:embedded-database>中,我们可以不配置也可以配置多个<jdbc:script>元素来搭建数据库。上面包含了两个<jdbc:script>元素:第一个引用了schema.sql,它包含了在数据库中创建表的SQL;第二个引用了test-data.sql,用来将测试数据填充到数据库中。除了搭建嵌入式数据库以外,<jdbc:embedded-database>元素还会暴露一个数据源,我们可以像使用其他的数据源那样来使用它。在这里,id属性被设置成了dataSource,这也是所暴露数据源的bean ID。因此,当我们需要javax.sql.DataSource的时候,就可以注入dataSourcebean。
java配置方式:
setType()方法等同于<jdbc:embedded-database>元素中的type属性,此外,我们这里用addScript()代替<jdbc:script>元素来指定初始化SQL。
5.使用profile选择数据源
假如你配置了多个数据源,如:
通过使用profile功能,会在运行时选择数据源,这取决于哪一个profile处于激活状态。
配置如上所示,当且仅当development profile处于激活状态时,会创建嵌入式数据库,当且仅当qa profile处于激活状态时,会创建DBCP BasicDataSource,当且仅当production profile处于激活状态时,会从JNDI获取数据源。
使用XML选择数据源
2.在Spring中使用JDBC
更新功能:
查询功能:
可以看出,大量的JDBC代码都是用于创建连接和语句以及异常处理的样板代码。
于是,这就是我们要使用JDBC模板的原因。
2.使用JDBC模板
Spring为JDBC提供了三个模板类供选择:
1.JdbcTemplate:最基本的Spring JDBC模板,这个模板支持简单的JDBC数据库访问功能以及基于索引参数的查询;
2.NamedParameterJdbcTemplate:使用该模板类执行查询时可以将值以命名参数的形式绑定到SQL中,而不是使用简单的索引参数;
3.SimpleJdbcTemplate:该模板类利用Java 5的一些特性如自动装箱、泛型以及可变参数列表来简化JDBC模板的使用;
从Spring 3.1开始,SimpleJdbcTemplate已经被废弃了,其Java 5的特性被转移到了JdbcTemplate中,并且只有在你需要使用命名参数的时候,才需要使用NamedParameterJdbcTemplate。所以对于大多数的JDBC任务来说,JdbcTemplate就是最好的可选方案。
使用JdbcTemplat要先设置数据源,如:
在这里,DataSource是通过构造器参数注入进来的。这里所引用的dataSource bean可以是javax.sql.DataSource的任意实现,也可以是上面提到的数据源
进行装配:
在这里,JdbcSpitterRepository类上使用了@Repository注解,这表明它将会在组件扫描的时候自动创建。它的构造器上使用了@Inject注解,因此在创建的时候,会自动获得一个JdbcOperations对象。JdbcOperations是一个接口,定义了JdbcTemplate所实现的操作。通过注入JdbcOperations,而不是具体的JdbcTemplate,能够保证JdbcSpitterRepository通过JdbcOperations接口达到与JdbcTemplate保持松耦合。
使用组件扫描和自动装配进行装配:
基于JdbcTemplate的addSpitter()方法
当update()方法被调用的时候JdbcTemplate将会获取连接、创建语句并执行插入SQL。在这里,你也看不到对SQLException处理的代码。在内部,JdbcTemplate将会捕获所有可能抛出的SQLException,并将通用的SQLException转换为Spring的异常处理(上面的图)所列的那些更明确的数据访问异常,然后将其重新抛出。因为Spring的数据访问异常都是运行时异常,所以我们不必在addSpring ()方法中进行捕获。
查询例子:
使用命名参数
addSpitter()方法使用了索引参数。这意味着我们需要留意查询中参数的顺序,在将值传递给update()方法的时候要保持正确的顺序。如果在修改SQL时更改了参数的顺序,那我们还需要修改参数值的顺序。除了这种方法之外,我们还可以使用命名参数。命名参数可以赋予SQL中的每个参数一个明确的名字,在绑定值到查询语句的时候就通过该名字来引用参数。例如,假设SQL_INSERT_SPITTER查询语句是这样定义的:
使用命名参数查询,绑定值的顺序就不重要了,我们可以按照名字来绑定值。如果查询语句发生了变化导致参数的顺序与之前不一致,我们不需要修改绑定的代码。
参考《Spring实战》