实现JdbcTemplate

廖雪峰
资深软件开发工程师,业余马拉松选手。

本节我们来实现JdbcTemplate。在Spring中,通过JdbcTemplate,基本封装了所有JDBC操作,可以覆盖绝大多数数据库操作的场景。

配置DataSource

使用JdbcTemplate之前,我们需要配置JDBC数据源。Spring本身只提供了基础的DriverManagerDataSource,但Spring Boot有一个默认配置的数据源,并采用HikariCP作为连接池。这里我们仿照Spring Boot的方式,先定义默认的数据源配置项:

summer:
  datasource:
    url: jdbc:sqlite:test.db
    driver-class-name: org.sqlite.JDBC
    username: sa
    password: 

再实现一个HikariCP支持的DataSource

@Configuration
public class JdbcConfiguration {

    @Bean(destroyMethod = "close")
    DataSource dataSource(
            // properties:
            @Value("${summer.datasource.url}") String url,
            @Value("${summer.datasource.username}") String username,
            @Value("${summer.datasource.password}") String password,
            @Value("${summer.datasource.driver-class-name:}") String driver,
            @Value("${summer.datasource.maximum-pool-size:20}") int maximumPoolSize,
            @Value("${summer.datasource.minimum-pool-size:1}") int minimumPoolSize,
            @Value("${summer.datasource.connection-timeout:30000}") int connTimeout
    ) {
        var config = new HikariConfig();
        config.setAutoCommit(false);
        config.setJdbcUrl(url);
        config.setUsername(username);
        config.setPassword(password);
        if (driver != null) {
            config.setDriverClassName(driver);
        }
        config.setMaximumPoolSize(maximumPoolSize);
        config.setMinimumIdle(minimumPoolSize);
        config.setConnectionTimeout(connTimeout);
        return new HikariDataSource(config);
    }
}

这样,客户端引入JdbcConfiguration就自动获得了数据源:

@Import(JdbcConfiguration.class)
@ComponentScan
@Configuration
public class AppConfig {
}

定义JdbcTemplate

下一步是定义JdbcTemplate,唯一依赖是注入DataSource

public class JdbcTemplate {
    final DataSource dataSource;

    public JdbcTemplate(DataSource dataSource) {
        this.dataSource = dataSource;
    }
}

JdbcTemplate基于Template模式,提供了大量以回调作为参数的模板方法,其中以execute(ConnectionCallback)为基础:

public <T> T execute(ConnectionCallback<T> action) {
    try (Connection newConn = dataSource.getConnection()) {
        T result = action.doInConnection(newConn);
        return result;
    } catch (SQLException e) {
        throw new DataAccessException(e);
    }
}

即由JdbcTemplate处理获取连接、释放连接、捕获SQLException,上层代码专注于使用Connection

@FunctionalInterface
public interface ConnectionCallback<T> {
    @Nullable
    T doInConnection(Connection con) throws SQLException;
}

其他方法其实也是基于execute(ConnectionCallback),例如:

public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action) {
    return execute((Connection con) -> {
        try (PreparedStatement ps = psc.createPreparedStatement(con)) {
            return action.doInPreparedStatement(ps);
        }
    });
}

上述代码实现了ConnectionCallback,内部又调用了传入的PreparedStatementCreatorPreparedStatementCallback,这样,基于更新操作的update就可以这么写:

public int update(String sql, Object... args) {
    return execute(
        preparedStatementCreator(sql, args),
        (PreparedStatement ps) -> {
            return ps.executeUpdate();
        }
    );
}

基于查询操作的queryForList()就可以这么写:

public <T> List<T> queryForList(String sql, RowMapper<T> rowMapper, Object... args) {
    return execute(preparedStatementCreator(sql, args),
        (PreparedStatement ps) -> {
            List<T> list = new ArrayList<>();
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    list.add(rowMapper.mapRow(rs, rs.getRow()));
                }
            }
            return list;
        }
    );
}

剩下的一系列查询方法都是基于上述方法的封装,包括:

  • queryForList(String sql, RowMapper rowMapper, Object... args)
  • queryForList(String sql, Class clazz, Object... args)
  • queryForNumber(String sql, Object... args)

总之,就是一个工作量的问题,开发难度基本为0。

测试时,可以使用Sqlite这个轻量级数据库,测试用例覆盖到各种SQL操作,最后把JdbcTemplate加入到JdbcConfiguration中,就基本完善了。

参考源码

可以从GitHubGitee下载源码。

GitHub



Comments

Loading comments...