Spring JdbcTemplate 快速上手

简介: 前言数据访问作为 Spring Framework 的特性之一,提供了事务、DAO 支持、JDBC、O/R 映射等能力。针对关系型数据库的访问,Spring 提供了一个 spring-jdbc 模块,JdbcTemplate 是这个模块的核心类,封装了复杂的 JDBC 操作。

前言


数据访问作为 Spring Framework 的特性之一,提供了事务、DAO 支持、JDBC、O/R 映射等能力。针对关系型数据库的访问,Spring 提供了一个 spring-jdbc 模块,JdbcTemplate 是这个模块的核心类,封装了复杂的 JDBC 操作。


日常开发中,如果不想引入第三方 ORM 框架或者业务比较简单,可以将 JdbcTemplate 作为首选。


概述


JdbTemplate 只是一个普通的类,并非是一个完整的 ORM 框架,目的仅仅是消除 JDBC 使用的样板式代码。JdbcTemplate 支持 Spring 事务管理,并且会将 SQLException 异常转换为 DataAccessException。


从命名及实现来看,它的设计有点类似设计模式中的模板方法,不过它是通过回调控制算法中的特定步骤。它将一些 JDBC 操作的通用流程封装到内部,并将一些必须由用户提供的步骤封装为接口,由用户通过方法参数提供。


从下面的表中可以看出 JDBC 操作过程中,JdbcTemplate 封装的部分与用户需要提供的部分。


image.png


实例化


使用 JdbcTemplate,首先需要对其实例化,JdbcTemplate 唯一的依赖是 DataSource,Spring Framework 环境可以将其声明为 bean 。


@Configuration
public class JdbcConfig {
    @Bean
    public DataSource dataSource(){
        SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
        dataSource.setDriverClass(Driver.class);
        dataSource.setUsername("root");
        dataSource.setPassword("12345678");
        dataSource.setUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setDriverClass(Driver.class);
        return dataSource;
    }
    @Bean
    public JdbcTemplate jdbcTemplate(){
        return new JdbcTemplate(dataSource());
    }
}

然后直接注入即可。

@Service
public class UserService {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    public void addUser() {
        jdbcTemplate.update("insert into user(username, password) values('hkp','123')");
    }
}


Spring Boot 环境下可以直接直接引入相关 starter。


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
    <version>2.2.7.RELEASE</version>
</dependency>


然后在 applicaiton.properties 文件中进行数据源配置即可。


spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test
spring.datasource.username=root
spring.datasource.password=12345678
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=org.springframework.jdbc.datasource.SimpleDriverDataSource


Spring Boot 会自动配置 JdbcTemplate 为 bean,应用可以直接注入。


方法分类


JdbcTemplate 类定义如下。


public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
}


数据库操作的方法由其实现的接口 JdbcOperations 定义,大概可以分为如下几类:


通用执行:类似 Statement.execute 方法,这类方法可以进行任意 CRUD 操作,支持普通 SQL 和存储过程,使用重载方法 execute 表示。

查询:类似 Statement.executeQuery 方法,用于 select 操作,使用方法 query* 表示,包括 query、queryForList、queryForMap、queryForObject、queryForRowSet。

更新:类似 Statement.executeUpdate 方法,用于 insert、update、delete 操作,使用重载方法 update 表示。

批量更新:类似 Statement.executeBatch 方法,用于批量更新操作,使用重载方法 batchUpdate 表示。

存储过程:存储过程方法底层会调用 CallableStatement.execute,由方法 call 表示。

回调接口


JdbcTemplate 方法较多,用户不必记住每个方法签名,使用时在 IDE 中输入关键字 execute、query、update、call、batch,通过代码提示选择合适的方法即可。


其中 SQL 是用户必须提供的,参数设置是可选的,如果是查询操作也可以自定义映射关系,这些自定义的部分由用户通过回调接口提供。下面是一些可能会用到的回调接口。


Connection 回调


Connection 回调对应的接口是 ConnectionCallback,这个接口用于通用执行,JdbcTemplate 内部获取到 Connection 之后就会回调这个接口,由用户控制 Statement 获取、SQL 执行、结果处理,JdbcTemplate 会自动处理 Connection 的关闭而无需用户操作。


使用示例如下:


Integer count = jdbcTemplate.execute(new ConnectionCallback<Integer>() {
    @Override
    public Integer doInConnection(Connection con) throws SQLException, DataAccessException {
        PreparedStatement statement = con.prepareStatement("select count(1) as c from user");
        ResultSet resultSet = statement.executeQuery();
        int count = resultSet.getInt("c");
        resultSet.close();
        statement.close();
        return count;
    }
});


Statement 回调


Connection 回调还需要手动创建 Statement,如果想省去创建 Statement 的工作可以使用 StatementCallback 接口,这个接口也是用于通用查询,JdbcTemplate 会自动处理 Statement 的关闭。


示例代码如下:


Integer count = jdbcTemplate.execute(new StatementCallback<Integer>() {
    @Override
    public Integer doInStatement(Statement stmt) throws SQLException, DataAccessException {
        ResultSet resultSet = stmt.executeQuery("select count(1) as c from user");
        int count = resultSet.getInt("c");
        resultSet.close();
        return count;
    }
});


ResultSet 抽取


结果抽取用于将 RestultSet 转换为所需的类型,JdbcTemplate 中有三个接口。


1. ResultSetExtractor

首先是 ResultSetExtractor,很明显,这个接口也是用于查询,JdbcTemplate 获取到 ResultSet 后就会回调这个接口。


Integer count = jdbcTemplate.query(sql, new ResultSetExtractor<Integer>() {
    @Override
    public Integer extractData(ResultSet rs) throws SQLException, DataAccessException {
        return rs.getInt("c");
    }
});


2. RowCallbackHandler


使用 ResultSetExtractor 还需要对 ResultSet 进行遍历,如果想省去遍历的工作,并且不需要返回值可以使用 RowCallbackHandler,这个接口可以处理每次迭代,示例代码如下。


String sql = "select count(1) as c from user";
jdbcTemplate.query(sql, new RowCallbackHandler() {
    @Override
    public void processRow(ResultSet rs) throws SQLException {
        int count = rs.getInt("c");
    }
});


3. RowMapper


通常情况下,我们查询还是需要将结果映射为 Java 类的,因此更常用的一个回调接口是 RowMapper,这个接口可以将每行记录转换为一个 Java 对象。示例如下:


String sql = "select username,password from user";
List<User> list = jdbcTemplate.query(sql, new RowMapper<User>() {
    @Override
    public User mapRow(ResultSet rs, int rowNum) throws SQLException {
        User user = new User().setUsername(rs.getString("username"))
                .setPassword(rs.getString("password"));
        return user;
    }
});


PreparedStatement 回调


PreparedStatement 相关的回调接口在 JdbcTemplate 内部比较多,可以大概做如下划分。


1. PreparedStatement 创建


自定义 PreparedStatement 创建逻辑的回调接口是 PreparedStatementCreator,这个接口可用于 execute、query、update 方法中。示例代码如下。


Integer count = jdbcTemplate.query(new PreparedStatementCreator() {
    @Override
    public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
        return con.prepareStatement("select count(1) as c from user");
    }
}, new ResultSetExtractor<Integer>() {
    @Override
    public Integer extractData(ResultSet rs) throws SQLException, DataAccessException {
        return rs.getInt("c");
    }
});


2. PreparedStatement 参数设置


JdbcTemplate 中有很多重载方法的参数都支持传入 SQL 中使用的参数,例如下面的方法。


public interface JdbcOperations {
  <T> List<T> query(String sql, RowMapper<T> rowMapper, @Nullable Object... args) throws DataAccessException;
  int update(String sql, @Nullable Object... args) throws DataAccessException;
}


如果想手动设置参数可以使用 PreparedStatementSetter 回调方法,JdbcTemplate 创建 PreparedStatement 之后就会回调这个接口。示例代码如下。


String sql = "update user set password = '321' where id = ?";
int count = jdbcTemplate.update(sql, new PreparedStatementSetter() {
    @Override
    public void setValues(PreparedStatement ps) throws SQLException {
        ps.setInt(1, 1);
    }
});


3. PreparedStatement 回调


StatementCallback 回调获取到的是一个 Statement 对象,如果想使用 PreparedStatement 对象,可以使用 PreparedStatementCallback 回调接口,这个接口用于 execute 方法。示例代码如下。


int count = jdbcTemplate.execute("update user set password = '321' where id = 1", new PreparedStatementCallback<Integer>() {
    @Override
    public Integer doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
        return ps.executeUpdate();
    }
});


4. 批量更新回调


批量更新时有两种设置参数的方式,一种是通过 JdbcTemplate.batchUpdate 方法参数直接设置 SQL 中的参数值,另一种是通过回调的方式,具体又有两种。

BatchPreparedStatementSetter 用于批量更新时手动设置参数。

List<User> list = Arrays.asList(new User().setUsername("zhangsan").setPassword("123"),
        new User().setUsername("lisi").setPassword("456"));
String sql = "update user set password = ? where username = ?";=
int[] count = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
    @Override
    public void setValues(PreparedStatement ps, int i) throws SQLException {
        ps.setString(1, list.get(i).getPassword());
        ps.setString(2, list.get(i).getUsername());
    }
    @Override
    public int getBatchSize() {
        return list.size();
    }
});


如果批量更新的数据量比较大, 可以将其进行拆分,例如 100 条数据,每 10 条做一次批量更新操作,这时可以使用 ParameterizedPreparedStatementSetter 接口设置参数。


List<User> list = Arrays.asList(new User().setUsername("zhangsan").setPassword("123"),
        new User().setUsername("lisi").setPassword("456"));
String sql = "update user set password = ? where username = ?";
int[][] counts = jdbcTemplate.batchUpdate(sql, list, 1, new ParameterizedPreparedStatementSetter<User>() {
    @Override
    public void setValues(PreparedStatement ps, User argument) throws SQLException {
        ps.setString(1, argument.getPassword());
        ps.setString(2, argument.getUsername());
    }
});


CallableStatement 回调


与 PreparedStatement 回调类似,CallableStatement 也有两个接口分别用户创建 CallableStatement 和设置 CallableStatement 参数,这两个回调接口是 CallableStatementCreator 和 CallableStatementCallback。使用示例如下。


Integer count = jdbcTemplate.execute(new CallableStatementCreator() {
    @Override
    public CallableStatement createCallableStatement(Connection con) throws SQLException {
        return con.prepareCall("customFun()");
    }
}, new CallableStatementCallback<Integer>() {
    @Override
    public Integer doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException {
        return cs.getInt("c");
    }
});


常用操作


上面介绍了一些回调接口,这些回调接口在大多数场景下使用并不多,只有在极端场景下才会使用,JdbcTemplate 将这些回调接口进一步封装,例如需要创建 Statement 可以直接在方法参数中指定 SQL、需要设置 SQL 参数值也可以直接通过方法参数传入,只有映射关系可能需要通过 RowMapper 手动配置。


下面总结一些在某些场景下可能会用到的方法。


查询


1. 查询单行单列数据


例如查询符合某些条件的记录数量,可以使用如下方法。


<T> T queryForObject(String sql, Class<T> requiredType, @Nullable Object... args) throws DataAccessException;


2. 查询单行多列数据


查询某条记录,并转换为 Map ,可以使用如下方法。

Map<String, Object> queryForMap(String sql, @Nullable Object... args) throws DataAccessException;

查询某条记录,并转换为所需类型,可以使用如下方法。

<T> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args) throws DataAccessException;


3. 查询多行单列数据


<T> List<T> queryForList(String sql, @Nullable Object[] args, Class<T> elementType) throws DataAccessException;


4. 查询多行多列数据


查询记录,并转换为 Map 可以使用如下方法。

List<Map<String, Object>> queryForList(String sql, @Nullable Object... args) throws DataAccessException;


5. 小技巧


由于查询最为复杂,如果不确定用哪个方法,可以先查找 query* 开头的方法,然后根据方法返回值类型选择。


更新


这里的更新包含 insert、update、delete 操作。常用方法如下。

int update(String sql, @Nullable Object... args) throws DataAccessException;


批量更新


单条 SQL,不同参数批量更新,,可以使用如下方法。

int[] batchUpdate(String sql, List<Object[]> batchArgs) throws DataAccessException;

如果数据量过大,可以拆分成多次批量更新,使用如下方法。

<T> int[][] batchUpdate(String sql, Collection<T> batchArgs, int batchSize,
      ParameterizedPreparedStatementSetter<T> pss) throws DataAccessException;


支持命名参数的 JdbcTemplate


JdbcTemplate 中使用的 SQL 参数使用 ? 表示,设置参数时需要注意按照参数的顺序提供值,如果参数比较多不太方便。


spring-jdbc 模块还提供了一个 NamedParameterJdbcTemplate 类,支持为参数命名。可以使用 :paramName、:{paramName} 或者 &paramName 的形式为 SQL 参数指定名称。例如:

select * from user where username  = :username

NamedParameterJdbcTemplate 底层使用 JdbcTemplate,使用前面我们提到的 PreparedStatementCreator 回调接口解析 SQL 并设置参数。


使用 NamedParameterJdbcTemplate 时不能将命名参数和 ? 混合使用,可以使用 Map 提供参数值。类定义如下。


public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations {
  private final JdbcOperations classicJdbcTemplate;
  public NamedParameterJdbcTemplate(DataSource dataSource) {
    Assert.notNull(dataSource, "DataSource must not be null");
    this.classicJdbcTemplate = new JdbcTemplate(dataSource);
  }
}


很明显,它的设计与 JdbcTemplate 类似,由接口 NamedParameterJdbcOperations 提供 JDBC 操作的方法。部分常用方法如下。

<T> List<T> query(String sql, Map<String, ?> paramMap, RowMapper<T> rowMapper) throws DataAccessException;
int update(String sql, Map<String, ?> paramMap) throws DataAccessException;
目录
相关文章
|
2月前
|
Java Spring
使用JDBCTemplate实现与Spring结合,方法公用 ——测试(EmpDaoImplTest)
使用JDBCTemplate实现与Spring结合,方法公用 ——测试(EmpDaoImplTest)
|
2月前
|
XML Java 数据库
【Spring】通过JdbcTemplate实现CRUD操作
【Spring】通过JdbcTemplate实现CRUD操作
47 0
|
17天前
|
SQL Java 数据库连接
Spring5系列学习文章分享---第四篇(JdbcTemplate+概念配置+增删改查数据+批量操作 )
Spring5系列学习文章分享---第四篇(JdbcTemplate+概念配置+增删改查数据+批量操作 )
16 0
|
2月前
|
XML Java 关系型数据库
Spring6 JdbcTemplate和事务
Spring6 JdbcTemplate和事务
|
2月前
|
SQL Java 数据库连接
jpa、hibernate、spring-data-jpa、jdbcTemplate
jpa、hibernate、spring-data-jpa、jdbcTemplate
|
2月前
|
Java Spring
使用JDBCTemplate实现与Spring结合,方法公用 ——共用实现类(BaseImpl)
使用JDBCTemplate实现与Spring结合,方法公用 ——共用实现类(BaseImpl)
|
2月前
|
Java 数据库连接 数据库
Spring系列文章:Spring使用JdbcTemplate
Spring系列文章:Spring使用JdbcTemplate
|
2月前
|
Java Spring
使用JDBCTemplate实现与Spring结合,方法公用 ——Emp实现类(EmpDaoImpl)
使用JDBCTemplate实现与Spring结合,方法公用 ——Emp实现类(EmpDaoImpl)
|
2月前
|
Java Spring
使用JDBCTemplate实现与Spring结合,方法公用 ——接口(BaseDao)
使用JDBCTemplate实现与Spring结合,方法公用 ——接口(BaseDao)
|
2月前
|
Java Spring
使用JDBCTemplate实现与Spring结合,方法公用
使用JDBCTemplate实现与Spring结合,方法公用