Spring JdbcTemplate 快速上手

105 篇文章 2 订阅
100 篇文章 0 订阅

前言

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

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

概述

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

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

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

步骤

Spring

用户

DataSource 配置

获取 Connection

定义 SQL 语句

创建 Statement

设置参数

执行 SQL

遍历 ResultSet

完成每次迭代

处理异常

处理事务

关闭 Statement、ResultSet、Connection

实例化

使用 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* 表示,包括 queryqueryForListqueryForMapqueryForObjectqueryForRowSet
  • 更新:类似 Statement.executeUpdate 方法,用于 insert、update、delete 操作,使用重载方法 update 表示。
  • 批量更新:类似 Statement.executeBatch 方法,用于批量更新操作,使用重载方法 batchUpdate 表示。
  • 存储过程:存储过程方法底层会调用 CallableStatement.execute,由方法 call 表示。

回调接口

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

其中 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,这个接口可用于 executequeryupdate 方法中。示例代码如下。

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 参数,这两个回调接口是 CallableStatementCreatorCallableStatementCallback。使用示例如下。

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;

查询记录,并转换为自定义类型,可以使用如下方法。

<T> List<T> query(String sql, RowMapper<T> rowMapper, @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;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值