1 JdbcTemplate

Wu Jun 2018-12-18 21:53:15
06 Spring > 05 Data.md

为了避免业务层模块强依赖于某种类型的数据库,Spring 数据库访问层以接口形式对外提供服务。

使用 Spring JDBC 的异常体系,描述能力强,且平台无关。

1 数据访问模板化

数据访问过程中的固定步骤和变量分为两类:

Spring数据访问架构

针对不同的持久化平台, 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,提供了多种配置方式:

  1. JDBC 驱动;
  2. JNDI 查询;
  3. 数据库连接池;

生产环境建议使用从连接池获取连接的数据源;如果有可能,倾向于通过应用服务器的 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 包中提供了三个数据源类:

  1. DriverManagerDataSource:每次请求连接时都返回新的连接,用过的连接会马上关闭并释放资源;
  2. SimpleDriverDataSource:与 DriverManagerDataSource 类似,但直接使用JDBC驱动,免去了类在特定环境(如 OSGi 容器)中可能遇到的类加载问题。
  3. 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);
String sql = "insert into user (username, password) values (?, ?)";
jdbcTemplate.update(sql, username, password);

当调用 update() 方法时,JdbcTemplate 调用 jdbc 获取一个连接、创建一个 statement,并执行插入语句。内部捕获了可能抛出的 SQLException 异常,然后转为更具体的数据库访问异常,并重新抛出。

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() 方法,从结果集中取出对应属性的值,并构造对象。