# Spring JDBC

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>${spring.version}</version>    <!-- 5.1.17.RELEASE -->
</dependency>

Spring 为了提供对 Jdbc 的支持,在 Jdbc API 的基础上封装了一套实现,以此建立一个 JDBC 存取框架。

(作为 Spring JDBC 框架的核心)JDBC Template 的设计目的主要是两个:

  1. 简化 JDBC 的操作代码。

  2. 可以将事务的管理工作委托给 Spring,进一步简化代码。

JdbcTemplate 使用很简单:要求 Spring『帮』我们创建一个 JdbcTemplate 的单例对象,随后,我们在代码(DAO)中,注入这个 JdbcTemplate 单例对象,使用它即可。

需要注意的是,Spirng 创建 JdbcTemplte 单例对象时,需要传入 DataSource 单例对象。传入 DataSource 的目的在于,JdbcTempate 会自己从 DataSource 中取 Connection 对象进行数据库操作。从而不再需要我们从 Service 层中传入 Connection 对象。

# 配置文件

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">


    <!-- for Service -->
    <context:component-scan base-package="xxx.yyy.zzz.service"/>
    <tx:annotation-driven transaction-manager="transactionManager"/>
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="ds"/>
    </bean>

    <!-- for DAO -->
    <context:component-scan base-package="xxx.yyy.zzz.dao" />
    <bean id="ds" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/scott?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=Asia/Shanghai"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="ds"/>
    </bean>
</beans>

# Dao

@Slf4j
@Repository
public class DepartmentDAO {

    @Autowired
    private JdbcTemplate template;

    public void delete(int id) {
        log.info("DAO: delete");
        template.update("delete from department where id = ?", id);
        if (id % 2 == 0)
            throw new RuntimeException();
    }
}

# Spring-JDBC API:增删改

JdbcTemplate 为 DAO 中的 增删改操作提供了 .update 方法。

String sql = "INSERT INTO exam_user VALUE(NULL, ?, ?)";
template.update(sql, username, password);

String sql = "DELETE FROM exam_user WHERE uid = ?";
template.update(sql, uid);

String sql = "UPDATE exam_user SET username = ?, password = ? WHERE uid = ?";
template.update(sql, newUsername, newPassword, uid);

# Spring-JDBC API:查询

String sql = "SELECT * FROM exam_user WHERE username = ?";
User user = template.queryForObject(
                sql, 
                new BeanPropertyRowMapper<>(User.class), 
                username);

String sql = "select * from exam_user";
List<User> list = template.query(
                    sql, 
                    new BeanPropertyRowMapper<>(User.class));

在 JavaBean 的属性名与数据库名一致的情况下,Spring Jdbc 提供了自带的一个 BeanPropertyRowMapper 类,用于将 ResultSet 中的数据库数据『映射/转换』成 JavaBean 。

JdbcTemplate#queryForObject 方法有一个『问题』,由于涉及到 ResultSet 到 JavaBean 的转换,JdbcTemplate#queryForObject 方法强制要求查询结果『应该』有数据。如果你的 SQL 语句在数据库中查不到任何数据(也许本来就没有这样的一条数据),那么 JdbcTemplate#queryForObject 方法会抛出异常:EmptyResultDataAccessException

当然,你也可以全部使用 JdbcTemplate#query 方法查询,得到一个 List 后,再通过 ListList#size 方法的返回结果来确定查没查到数据,并进行后续处理。

# 自定义映射结果集

对于数据库中的字段的名字与 JavaBean 的属性名『不一致』的情况,如果无法将其两者统一,那么在使用 .query 方法和 .queryForObject 方法时,就需要自己『定制』ResultSet 到 JavaBean 的转换规则,即,实现 RowMapper 接口。

template.query("...", (resultSet, n) -> {
    User user = new User();
    user.setUid(resultSet.getLong("uid"));
    user.setUsername(resultSet.getString("username"));
    user.setPassword(resultSet.getString("password"));
    return user;
});

spring-jdbc 会循环遍历 ResultSet,对于每一轮循环它将调用你这里的第二个参数,并将 ResultSet 和当前的『轮数』传入进去。