一、Spring的数据访问哲学
1、Spring自带一组数据访问框架,集成多种数据访问技术;
2、服务对象通过接口来访问Repository,具体数据持久化技术独立于Repository,只有数据访问方法才通过接口进行暴露;
好处:切换持久化框架对应用程序其他部分带来的影响最小,如果将数据访问层的实现细节渗透到应用程序的其他部分中,那整个应用程序将与数据访问层耦合在一起,导致僵尸设计;
3、Spring提供了多种数据访问异常,直接使用Spring的异常体系即可,可以避免与具体持久化方案耦合(如hibernate自己抛的异常)
4、数据访问模板化
Spring将数据访问过程中固定的和可变的部分明确划分为两个不同的类:模板(template)和 回调(callback);模板管理过程中固定的部分,而回调处理自定义的数据访问代码。
针对不同的持久化平台,Spring提供多种可选模板。如使用JDBC,可使用JdbcTemplate;
下表是Spring所提供的所有数据访问模板及其用途:
模板类(org.springframework.*) | 用途 |
---|---|
jca.cci.core.CciTemplate | JCA CCI连接 |
jdbc.core.JdbcTemplate | JDBC连接 |
jdbc.core.namedparam.NamedParameterJdbcTemplate | 支持命名参数的JDBC连接 |
jdbc.core.simple.SimpleJdbcTemplate | 通过Java 5简化后的JDBC连接(Spring 3.1中已经废弃) |
orm.hibernate3.HibernateTemplate | Hibernate 3.x以上的Session |
orm.ibatis.SqlMapClientTemplate | iBATIS SqlMap客户端 |
orm.jdo.JdoTemplate | Java数据对象(Java Data Object)实现 |
orm.jpa.JpaTemplate | Java持久化API的实体管理器 |
二、配置数据源
Spring提供了在Spring上下文中配置数据源bean的多种方式,包括:
- 通过JDBC驱动程序定义的数据源;
- 通过JNDI查找的数据源;
- 连接池的数据源。
1、使用JNDI数据源
Web服务器允许配置通过JNDI获取数据源;
好处:
- 数据源可以在应用程序之外进行管理,应用程序只需在访问数据库的时候查找数据源就可以;
- 在应用服务器中管理的数据源通常以池的方式组织,具备更好的性能,并且还支持系统管理员对其进行热切换
(1)xml配置方式
<!--
jee:jndi-lookup:用于检索JNDI中的任何对象(包括数据源)将其作为Spring中的bean;
jndi-name:指定JNDI中资源名称
resource-ref=true:将对给定的jndi-name自动添加前缀“java:comp/env/”-->
-->
<jee:jndi-lookup id="dataSource"
jndi-name="/jdbc/SpitterDS"
resource-ref="true" />
(2)java配置方式
@Bean
public JndiObjectFactoryBean dataSource(){
JndiObjectFactoryBean jndiObjectFB = new JndiObjectFactoryBean();
jndiObjectFB.setJndiName("jdbc/SpitterDS");
jndiObjectFB.setResourceRef(true);
jndiObjectFB.setProxyInterface(javax.sql.DataSource.class);
return jndiObjectFB;
}
2、使用数据源连接池
Spring并没有提供数据源连接池实现,但有多项可用方案,包括如下开源的实现:
- Apache Commons DBCP(http://jakarta.apache.org/commons/dbcp)
- c3p0 (http://sourceforge.net/projects/c3p0/)
- BoneCP (http://jolbox.com/)
DBCP配置方式:
<!-- xml方式 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
p:driverClassName="org.h2.Driver"
p:url="jdbc:h2:tcp://localhost/~/spitter"
p:username="sa"
p:password=""
p:initialSize="5"
p:maxActive="10"/>
// Java方式
@Bean
public BasicDataSource dataSource(){
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("org.h2.Driver");// 指定JDBC驱动类的全限定名
ds.setUrl("jdbc:h2:tcp://localhost/~/spitter");
ds.setUsername("sa");
ds.setPassword("");
ds.setInitalSize(5);
ds.setMaxActive(10);
return ds;
}
其他可配置属性如下所示:
池配置属性 | 所指定的内容 |
---|---|
initialSize | 池启动时创建的链接数量 |
maxActive | 同一时间可从池中分配的最多连接数。如果设置为0,表示无限制 |
maxIdle | 池里不会被释放的最多空闲连接数。如果设置为0,表示无限制 |
maxOpenPreparedStatements | 在同一时间能够从语句池中分配的预处理语句(prepared statement)的最大数量,如果设置为0,表示无限制 |
maxWait | 在抛出异常之前,池等待连接回收的最大时间(当没有可用连接时)。如果设置为-1,表示无限等待 |
minEvictableIdleTimeMillis | 连接在池中保持空闲而不被回收的最大时间 |
minIdle | 在不创建新连接的情况下,池中保持空闲的最小连接数 |
poolPreparedStatements | 是否对预处理语句(prepared statement)进行池管理(boolean值) |
3、基于JDBC驱动的数据源
在Spring中,通过JDBC驱动定义数据源是最简单的配置方式。Spring提供了三个这样的数据源类(均位于org.springframework.jdbc.datasource包中)供选择:
- DriverManagerDataSource:在每个连接请求时都会返回一个新建的连接。与DBCP的BasicDataSource不同,由DriverManagerDataSource提供的连接并没有进行池化管理。
- SimpleDriverDataSource:
与DriverManagerDataSource的工作方式类似,但是它直接使用JDBC驱动,来解决在特定环境下的类加载问题,这样的环境包括OSGi容器; - SingleConnectionDataSource:在每个连接请求时都会返回同一个的连接。尽管SingleConnectionDataSource不是严格意义上的连接池数据源,但是可以将其视为只有一个连接的池。
(1)配置方式
以上数据源配置方式同DBCP类似,如下为DriverManagerDataSource配置方式:
a、java方式
@Bean
public DataSource dataSource(){
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spitter");
ds.setUsername("root");
ds.setPassword("123456");
return ds;
}
b、xml方式
<bean id="dataSource" class="com.springframework.jdbc.datasource.DriverManangerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spitter"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
(2)特点:
a、与具备池功能的数据源相比,唯一的区别在于这些数据源bean都没有提供连接池功能,所以没有可配置的池相关的属性。
b、适用于小应用或开发环境来,不要用于生产环境;
SingleConnectionDataSource有且只有一个数据库连接,不适合用于多线程的应用程序,最好只在测试时候使用。
DriverManagerDataSource和SimpleDriverDataSource尽管支持多线程,但是在每次请求连接的时候都会创建新连接,这是性能为代价的。
4、使用嵌入式的数据源
特点:嵌入式数据库作为应用的一部分运行,而不是应用连接的独立数据库服务器。对于开发和测试来讲,嵌入式数据库是很好的可选方案。
配置方式:
a、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:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/aop/spring-jdbc-3.1.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
...
<!-- 可以像其他数据源那样使用它-->
<jdbc:embedded-database id = "dataSource" type="H2">
<!-- type=H2表明嵌入式数据库是H2数据库
type=DERBY表明使用嵌入式的Apache Derby数据库
-->
<!-- jdbc:script可配置数 >=0 -->
<jdbc:script location="com/habuma/spitter/db/jdbc/schema.sql" /> <!-- 创建表的sql-->
<jdbc:script location="com/habuma/spitter/db/jdbc/test-data.sql" /><!-- 创建测试数据的sql-->
</jdbc:embedded-database>
...
</beans>
b、java方式
@Bean
public DataSource dataSource(){
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}
5、使用profile选择数据源
解决不同环境下使用不同数据源问题;
a、java方式
@Configuration
public class DataSourceConfiguration {
@Profile("development")
@Bean
public DataSource embeddedDataSource(){
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}
@Profile("qa")
@Bean
public DataSource Data(){
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("org.h2.Driver");
ds.setUrl("jdbc.h2:tcp://localhost/~/spitter");
ds.setUsername("sa");
ds.setPassword("");
ds.setInitialSize(5);
ds.setMaxActive(10);
return ds;
}
@Profile("production")
@Bean
public DataSource dataSource(){
JndiObjectFactoryBean jndiObjectFactoryBean
= new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName("jdbc/SpittrDS");
jndiObjectFactoryBean.setResourceRef(true);
jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
return (DataSource) jndiObjectFactoryBean.getObject();
}
}
}
b、xml方式
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
....
http://www.springframework.org/schema/beans/spring-beans.xsd">
<beans profile="development">
<jdbc:embedded-database id="dataSource" type="H2">
<jdbc:script location="com/habuma/spitter/db/jdbc/schema.sql"/>
<jdbc:script location="com/habuma/spitter/db/jdbc/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="qa">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
p:driverClassName="org.h2.Driver"
p:url="jdbc:h2:tcp://localhost/~/spitter"
p:username="sa"
p:password=""
p:initialSize="5"
p:maxActive="10"/>
</beans>
<beans profile="production">
<jee:jndi-lookup id="dataSource"
jndi-name="/jdbc/SpitterDS"
resource-ref="true"/>
</beans>
</beans>
三、在Spring中使用JDBC
使用JDBC模板:Spring的JDBC框架承担了资源管理和异常处理的工作,从而简化了JDBC代码,让我们只需编写从数据库读写数据的必须代码。
Spring为JDBC提供了三个模板类供选择:
- JdbcTemplate:最基本的Spring JDBC模板,这个模板支持简单的JDBC数据库访问功能以及基于索引参数的查询;(推荐)
- NameParameterJdbcTemplate:使用该模板类执行查询时可以将值以命名参数的形式绑定到SQL中,而不是使用简单的索引参数。(只有在需要使用命名参数的时候使用)
- SimpleJdbcTemplate:该模板类利用Java 5的一些特性自动装箱、泛型以及可变参数列表来简化JDBC模板的使用。(废弃)
1、使用JdbcTemplate操作数据:
(1)配置
// 所引用的dataSource bean可以是javax.sql.DataSource的任意实现,包括了之前文中所创建的
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
// 方案一:
@Repository
public class JdbcSpitterRepository implements SpitterRepository{
private JdbcOperations jdbcOperations;
@Inject // 注解表示在创建的时候,会自动获得一个JdbcOperations对象
public JdbcSpitterRepository(JdbcOperations jdbcOperations){
this.jdbcOperations = jdbcOperations;
}
...
}
方案二:
@Bean
public SpitterRepository spitterRepository(JdbcTemplate jdbcTemplate){
return new JdbcSpitterRepository(jdbcTemplate);
}
(2)插入数据
public void addSpitter(Spitter spitter) {
jdbcOperations.update("insert into Spitter (username, password, firstname) values (?, ?, ?)",
spitter.getUsername(),
spitter.getPassword(),
spitter.getFirstName();
}
(3)读取数据
/*
queryForObject() 方法有三个参数:
String对象,包含了要从数据库中查找数据的SQL;
RowMapper对象,用来从ResultSet中提取数据并构建域对象(本例中为Spitter);
可变参数列表,列出了要绑定到查询上的索引参数值。
*/
public Spitter findOne(long id) {
return jdbcOperations.queryForObject(
"select username, password, firstname from Spitter where id=?",
new SpitterRowMapper(), // 将查询结果映射到对象
id);
}
...
/*
SpitterRowMapper对象实现了RowMapper接口。对于查询返回的每一行数据,JdbcTemplate将会调用
RowMapper的mapRow() 方法,并传入一个ResultSet和包含行号的整数。在SpitterRowMapper的
mapRow()方法中,我们创建了Spitter对象并将ResultSet中的值填充进去。
*/
public static final class SpitterRowMapper implements RowMapper<Spitter>{
public Spitter mapRow(ResultSet rs, int rowNum) throw SQLException {
return new Spitter (
rs.getLong("id"),
rs.getString("username"),
rs.getString("password"),
rs.getString("firstname"));
}
}
(4)java8方式使用 见P307
2、命名参数使用 - NameParameterJdbcTemplate
命名参数可以赋予SQL中的每个参数一个明确的名字,在绑定值在查询语句的时候就通过该名字来引用参数。
配置方式:
@Bean
public NamedParameterJdbcTemplate jdbcTemplate(DataSource dataSource) {
return new NamedParameterJdbcTemplate(dataSource);
}
private static final String SQL_INSERT_SPITTER =
"insert into spitter (username, password, firstname) " +
"values (:username, :password, :firstname)";
public void addSpitter(Spitter spitter) {
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("username", spitter.getUsername()); // 绑定参数
paramMap.put("password", spitter.getPassword());
paramMap.put("firstname", spitter.getFirstName());
jdbcOperations.update(SQL_INSERT_SPITTER, paramMap);
}