为了避免业务层模块强依赖于某种类型的数据库,Spring 数据库访问层以接口形式对外提供服务。
使用 Spring JDBC 的异常体系,描述能力强,且平台无关。
1 数据访问模板化
数据访问过程中的固定步骤和变量分为两类:
- 模板(templates):处理数据访问的固定部分——事务控制、 管理资源以及处理异 常。
- 回调(callbacks):处理应用程序相关的数据访问——语句、 绑定参数以及整理结果集
针对不同的持久化平台, Spring提供了多个可选的模板。
ORM 持久化技术 | 模板类(org.springframework.*) |
---|---|
JDBC | jdbc.core.JdbcTemplate |
JCA CCI | jca.cci.core.CciTemplate |
Hibernate | orm.hibernate5.HibernateTemplate |
iBATIS | orm.ibatis.SqlMapClientTemplate |
JDO | orm.jdo.JdoTemplate |
JPA | orm.jpa.JpaTemplate |
2 配置 DataSource
使用 JdbcTemplate 需设置 DataSource,提供了多种配置方式:
- JDBC 驱动;
- JNDI 查询;
- 数据库连接池;
生产环境建议使用从连接池获取连接的数据源;如果有可能,倾向于通过应用服务器的 JNDI 来获取数据源。
2.1 JNDI 数据源
在 Spring 应用部署的服务器中配置数据源,通过 JNDI 获取。数据源配置在应用外部,允许在访问数据库的时再查找数据源,且支持热切换。
public JndiObjectFactoryBean dataSource() {
JndiObjectFactoryBean jndiObjectFB = new JndiObjectFactoryBean();
jndiObjectFB.setJndiName("/jdbc/xxxx");
jndiObjectFB.setResourceRef(true);
jndiObjectFB.setProxyInterface(javax.sql.DataSource.class);
return jndiObjectFB;
}
2.2 数据库连接池
有多项开源数据源连接池实现:Apache Commons DBCP、c3p0、BoneCP
以 DBCP 为例,BasicDataSource 配置连接池
public BasicDataSource dataSource() {
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("org.h2.Driver");
ds.setUrl("jdbc:h2:tcp://localhost/~/xxxx");
ds.setUsername("sa");
ds.setPassword("");
ds.setInitialSize(5);
return ds;
}
2.3、JDBC 驱动
通过 JDBC 驱动定义数据源是最简单的配置方式。 org.springframework.jdbc.datasource 包中提供了三个数据源类:
- DriverManagerDataSource:每次请求连接时都返回新的连接,用过的连接会马上关闭并释放资源;
- SimpleDriverDataSource:与 DriverManagerDataSource 类似,但直接使用JDBC驱动,免去了类在特定环境(如 OSGi 容器)中可能遇到的类加载问题。
- SingleConnectionDataSource:每次都返回同一个连接对象,可以理解为只有 1 个连接的数据源连接池。
跟之前配置 DBCP 的 BasicDataSource 类似,例如配置 DriverManagerDataSource
@Bean
public DataSource dataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("org.h2.Driver");
ds.setUrl("jdbc:h2:tcp://localhost/~/xxxx");
ds.setUsername("sa");
ds.setPassword("");
return ds;
}
已上三数据源对多线程支持都不好,强烈建议使用数据库连接池。
2.4 选择数据源
借助 Spring 的 bean-profiles 特性,在不同的环境中配置不同的数据源。利用 @Profile 注解,在运行时根据激活的 profile 选择指定的数据源。
@Configuration
public class DataSourceConfiguration {
@Profile("development")
@Bean
public DataSource embeddedDataSource() {
return new EmbeddedDatabaseBuilder()
...
}
@Profile("qa")
@Bean
public BasicDataSource basicDataSource() {
BasicDataSource ds = new BasicDataSource();
...
return ds;
}
@Profile("production")
@Bean
public DataSource dataSource() {
JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
...
return (DataSource)jndiObjectFactoryBean.getObject();
}
}
3 使用 JdbcTemplate
JDBC 直接操作数据库, 处理与数据库访问相关的所有事情, 样板代码繁琐,但很重要。Spring 的 JDBC 框架承担了资源管理和异常处理的样板代码,开发者只需编写读写数据的必需代码。
JdbcTemplate 是最主要的 JDBC 模板,支持简单的 JDBC 数据库访问功能以及基于索引参数的查询;还提供了 NameParameterJdbcTemplate 支持命名参数。
3.1 CRUD
使用 JdbcTemplate 前需设置 DataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
- 增删改
- update(final String sql)
String sql = "insert into user (username, password) values (?, ?)";
jdbcTemplate.update(sql, username, password);
当调用 update() 方法时,JdbcTemplate 调用 jdbc 获取一个连接、创建一个 statement,并执行插入语句。内部捕获了可能抛出的 SQLException 异常,然后转为更具体的数据库访问异常,并重新抛出。
- 查
- queryForObject(String sql, RowMapper
rowMapper, Object… args)
- queryForObject(String sql, RowMapper
User user = jdbcOperations.queryForObject(sql,new UserMapper (),param);
public class UserMapper implements RowMapper<User>{
@Override
public User mapRow(ResultSet resultSet, int rows) throws SQLException {
User user = new User();
user.setUsername(resultSet.getString(1));
user.setPassword(resultSet.getString(2));
return user;
}
}
需要实现一个 RowMapper 对象,JdbcTemplate 会调用 mapRow() 方法,从结果集中取出对应属性的值,并构造对象。