不管是使用JDBC或者Hibernate MyBatis这种对象关系映射(ORM)框架实现数据持久化,Spring都能帮你消除持久化代码中那些单调枯燥的访问逻辑。
Spring的数据访问哲学
为了避免持久化的逻辑分散到应用的各个组件中,最好将数据访问的功能放到一个或多个专注于此项任务的组件中。这样的组件通常成为数据访问对象(data access object, DAO)或者Repository。
我们应该以下面的形式设计数据访问层:
如图所示,服务对象通过接口来访问Repository,这样做会有几个好处:
- 使得服务对象易于测试。因为它们不再与特定的数据访问实现绑定再一起,可以通过mock测试。
- 数据访问层以持久化技术无关的方式来进行访问。持久化方式的选择独立于Repository,同时只有数据访问相关方法才通过接口进行暴露。这可以实现灵活的设计,并且切换持久化框架对应用程序其他部分所带来的影响最小。
了解Spring的数据访问异常体系
SQLException的常见问题包括:
- 应用程序无法连接数据库。
- 要执行的查询存在语法错误
- 查询中所使用的表或列不存在
- 试图插入或更新的数据违反了数据库约束
SQLException的问题在于捕获到它的时候,应该如何处理。实际上能够出发SQLException的问题通常是不能在catch代码块中解决的,大多数抛出的错误代表了致命性的错误。既然无法恢复错误,那么为什么我们还要捕捉他们呢?
因为,我们只有捕获了它们,才能够获知问题根源的更多信息。我们可以更具不同的Exception了解不同的信息。比如Hibernate就提供了二十个左右的异常。
数据访问模版化
模版化很容易理解,比如,在机场办理托运,整个过程是将行李从出发地运送到目的地,过程本身是固定不变的。处理行李的过程中的每个事件都会以同样的方式进行。但是,由于每个乘客的行李登记不一样,所以这个过程是由旅客决定的,这是变化的部分。
逝者如斯,而未尝往也,盈虚者如彼而卒莫消长也
Spring将变化和固定的部分明确划分为两个不同的类:模版和回调。
针对不同的持久化平台,Spring为多种持久化框架提供了支持,但在这之前,我们需要在Spring中配置一个数据源用来链接数据库。
配置数据源
Spring上下文中配置数据源bean的方法有多种:
- 通过JDBC驱动程序定义的数据源
- 通过JNDI查找的数据源
- 连接池的数据源
使用JNDI数据源
利用Spring,可以使用JndiObjectFacotoryBean从JNDI中查找DataSource:
@Bean
JndiObjectFactoryBean dataSource(){
JndiObjectFactoryBean jndiObjectFB= new JndiObjectFactoryBean();
jndiObjectFB.setJndiName("jdbc/spittrDs");
jndiObjectFB.setResourceRef(true);
jndiObjectFB.setProxyInterface(javax.sql.DataSource.class);
return jndiObjectFB;
}
jndi name表示指定jndi中资源的名字。
resource ref设定为true,表示jndi name自动添加“java:comp/env/”前缀。
或者使用<jeeLjndi-lookup>
元素:
<jee:jndi-lookup id="dataResource"
jndi-name="/jdbc/SpitterDS"
resource-ref="true">
使用数据源连接池
尽管Spring没有提供数据源连接池的实现,但是我们有一些开源实现,比如Apache Commons DBCP,c3p0,BoneCP等等。这些连接池中的大多数能够配置为Spring的数据源,在一定程度上与Spring自带的DriverManagerDataSource或SingleConnectionDataSource很类似。
如下就是配置DBCP BasicDataSource的方式:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
p:driverClassName="org.h2.Driver"
p:url="jdbc:h2:tcp://localhost/~/spitter"
p:username="root"
p:password="root"
p:initialSize="5"
p:maxActive="10">
也可以用Java配置:
@Bean
public BasicDataSource dataSource(){
BasicDataSource ds=new BasicDataSource();
ds.setDriverClassName("org.h2.Driver");
ds.setUrl("jdbc:h2:tcp://localhost/~/spitter");
ds.setUsername("root");
ds.setPassword("root");
ds.setInitialSize(5);
ds.setMaxActive(10);
return ds;
}
基于JDBC驱动的数据源
在Spring中,通过JDBC驱动定义数据源是最简单的配置方式。Spring提供了三个这样的数据源类(均位于org.springframework.jdbc.datasource包中):
- DriverManagerDataSource:在每个连接请求时都会返回一个新建的连接,与DBCP的BasicDataSource不同,DriverManagerDataSource提供的连接没有进行池化管理。
- SimpleDriverDataSource:与DriverManagerDataSource类似,但是它直接使用JDBC驱动,来解决在特定环境下的类加载问题,这样的环境包括OSGi容器。
- SingleConnectionDataSource:在每个连接请求时都会返回同一个连接,尽管它不是严格意义上的连接池数据源,但是可以把他视作只有一个连接的池。
配置方法与DBCP类似:
@Bean
public DataSource dataSource(){
DriverManagerDataSource ds=new DriverManagerDataSource();
ds.setDriverClassName("org.h2.Driver");
ds.setUrl("jdbc:h2:tcp://localhost/~/spitter");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
由于没有连接池服务,所以不推荐。
池化服务:
选取池化服务时,服务器对象在服务器运行时预先创建了指定数目的,并且只在用户请求服务器对象时,优先使用已创建的服务器对象,如果没有空闲的服务器对象才会创建新的服务器对象返回给用户进程进行相应的操做,并且当用户操作结束后立即将服务器对象释放回到服务器对象池中等待下一个用户会话。
池化服务的特点:
1.池化服务器对象是预先创建的服务器对象,他在用户请求服务器对象前就已预先获取了服务器中包含的空间信息,并在内存中创建并维持了数目服务器对象。有效的降低了每次用户请求时重复获取空间信息的时间与创建服务器对象的系统开销。
2.池化服务器对象是可供多个用户共享。由于池化服务器对象仅仅在用户请求时返回给用户,当用户的一次操作结束后立即释放服务器对象到服务器对象池中,所以池化服务器对象允许多个应用程序进程共享同一个服务器对象,即多个用户可以在其操作进程中使用同一个服务器对象,从而达到资源的最大化利用,而且由于在用户获取服务器对象前已经创建了对象。
非池化服务:
用户第一次请求服务器对象时创建,并当用户会话结束后才会释放服务器对象,所以一个非池化服务器对象对应一个用户进程。
非池化服务器对象是有状态的,有服务器状态的对象时可读写的对象,意味着应用可以对服务器对象及其相关对象改动。
如果全部使用非池化的服务器对象将会使得服务器在大并发访问时产生严重的性能问题,甚至瘫痪。
所以当不需要修改服务器对象中的要素集内容的情况下使用池化服务器对象来改善系统性能。
使用嵌入式的数据源
嵌入式数据库作为应用的一部分运行,而不是应用连接的独立数据库服务器,尽管在生产环境中,它并没有太大用处,但是对于开发和测试来说,是一个很好的可选方案。
使用Profile选择数据源
package spittr.config;
import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jndi.JndiObjectFactoryBean;
import javax.sql.DataSource;
@Configuration
public class DataSoueceConfiguration {
@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("root");
ds.setPassword("root");
ds.setInitialSize(5);
ds.setMaxActive(10);
return ds;
}
@Profile("production")
@Bean
public DataSource dataSource(){
JndiObjectFactoryBean jndiObjectFB= new JndiObjectFactoryBean();
jndiObjectFB.setJndiName("jdbc/spittrDs");
jndiObjectFB.setResourceRef(true);
jndiObjectFB.setProxyInterface(javax.sql.DataSource.class);
return (DataSource) jndiObjectFB.getObject();
}
}
当然也可以使用<beans profile="qa">
等等来区分,最后在Configuration类里使用ContextConfiguration("classpath:datasource_configuration.xml")
。
在Spring中使用JDBC
你对JDBC应该不会陌生:
看看这些冗长的代码,为了进行一个插入操作,需要写这么多。
所以这里我们使用JDBC模版。
使用JDBC模版
Spring的JDBC框架承担了资源管理和异常处理的操作,从而简化了JDBC代码,让我们只需要编写从数据库读写数据的必要代码。
Spring提供了3个模版类:
- JdbcTemplate:最基本的Spring JDBC模版,这个模版支持简单的JDBC数据库访问功能以及基于索引参数的查询。
- NamedParameterJdbcTemplate:使用该模版类执行查询时,可以将值以命名参数的形式绑定到SQL中,而不是使用简单的索引参数。
- SimpleJdbcTemplate:该模版利用Java5的一些特性如自动装箱、泛型以及可变参数列表来简化JDBC的使用。
对于大多数方案来说,JdbcTemplate就是最好的可选方案(因为第3个方案已经弃用了,并且功能移至了JdbcTemplate。第二个方案只有在你需要使用命名参数的时候才有效)。
使用JdbcTemplate来插入参数
为了让JdbcTemplate正常工作,只需要为其设置DataSource就可以了,这使得Spring中配置JdbcTemplate非常容易,如下面的@Bean
方法所示:
@Bean
public JdbcTemplate jdbcTemplate(DataSource datasource){
return new JdbcTemplate(datasource);
}
在这里DataSource是通过构造器参数注入。
现在我们可以将jdbcTemplate装配到Repository中并使用它里访问数据库。
如:
package spittr.data;
import org.springframework.jdbc.core.JdbcOperations;
import spittr.bean.Spitter;
import javax.inject.Inject;
@Repository
public class JdbcSpitterRepository implements SpitterRepository {
private JdbcOperations jdbcOperations;
@Inject
public JdbcSpitterRepository(JdbcOperations jdbcOperations){
this.jdbcOperations=jdbcOperations;
}
...
}
@Repository注解表明它将会在组件扫描的时候自动创建,他也是基于@Component的。由于我们已经在 @Configuration注解的 DataSoueceConfiguration里使用@Bean显示的创建了JdbcOperations的依赖注入。 所以,接下来我们可以使用一个@Autowired,实现这个注入。
这里使用了@Inject注解而不是@Autowired,他们实际上是一样的。因此在创建的时候,会自动获得一个JdbcOperations对象。通过注入JdbcOperations,而不是具体的JdbcTemplate能够保证JdbcSpitterRepository通过JdbcOperations接口达到与JdbcTemplate的松耦合。
在Repository中具备可用的JdbcTemplate后,我们可以极大地简化save()方法:(插入方法)
@Override
public Spitter save(Spitter spitter) {
jdbcOperations.update(
"insert into Spitter (username, password, first_name, last_name, email)" +
" values (?, ?, ?, ?, ?)",
spitter.getUsername(),
spitter.getPassword(),
spitter.getFirstName(),
spitter.getLastName(),
spitter.getEmail());
return spitter;
}
那么查询方法 findByUsername()呢?
@Override
public Spitter findByUsername(String username) {
return jdbcOperations.queryForObject(
"select id, username, null, first_name, last_name, email from Spitter where username=?",
new SpitterRowMapper(),
username);
}
private static class SpitterRowMapper implements RowMapper<Spitter> {
public Spitter mapRow(ResultSet rs, int rowNum) throws SQLException {
return new Spitter(
rs.getLong("id"),
rs.getString("username"),
null,
rs.getString("first_name"),
rs.getString("last_name"),
rs.getString("email"));
}
}
这里的 findByUsername()方法中使用了queryForObject()
方法,这个方法有三个参数:
- String对象,也就是要查询的SQL
- RowMapper对象,用来从ResultSet中提取数据并构建域对象(本例中为Spitter)
- 可变参数列表,列出绑定到查询上的索引参数值(本例中为username)
使用命名参数
为什么会有命名参数呢?
- 能够打破参数赋值的顺序和个数。
- 能够设置默认值。
命名参数赋予SQL中每个参数一个明确的名字,绑定到查询语句的时候,通过该名字来引用参数。
例如:
private static final String SQL_INSERT_SPITTER=
"insert into spitter{username,password,fullname}"+
"values(:username,:password,:fullname)";
NamedParameterJdbcTemplate就支持使用命名参数:
它的显示声明与常规的JdbcTemplate几乎完全相同
@Bean
public NamedParameterJdbcTemplate jdbcTemplate(DataSource dataSource){
return new NamedParameterJdbcTemplate(dataSource);
}
我们这样插入:
jdbc术语解析:
https://blog.csdn.net/lizhen314/article/details/79388193