Using JdbcTemplate
JdbcTemplate 是 JDBC 核心包中的中心类。它处理资源的创建和释放,这可以帮助你避免常见的错误,比如忘记关闭连接。它执行核心 JDBC 工作流程的基本任务(如语句的创建和执行),让应用程序代码提供 SQL 和提取结果。JdbcTemplate 类:
- 运行 SQL 查询
- 更新语句和存储过程调用
- 对 ResultSet 实例进行迭代并提取返回的参数值。
- 捕捉 JDBC 异常,并将其转换为
org.springframework.dao包中定义的通用的、信息量更大的异常层次结构。(参见 一致的异常层次结构)。
当你为你的代码使用 JdbcTemplate 时,你只需要实现回调接口,给它们一个明确定义的契约。给定一个由 JdbcTemplate 类提供的Connection,PreparedStatementCreator回调接口会创建一个准备好的语句,提供 SQL 和任何必要参数。CallableStatementCreator接口也是如此,它创建可调用语句。RowCallbackHandler接口从 ResultSet的每一行提取数值。
你可以通过直接实例化 DataSource引用在 DAO 实现中使用 JdbcTemplate,也可以在 Spring IoC 容器中配置它并将其作为 Bean 引用给 DAO。
:::info 数据源应该总是被配置为 Spring IoC 容器中的一个 Bean。在第一种情况下,Bean 被直接给了服务;在第二种情况下,它被给了准备好的模板(template)。 :::
这个类发出的所有 SQL 都会被记录在 DEBUG 级别,在与模板实例的全限定类名(通常是 JdbcTemplate,但如果你使用 JdbcTemplate 类的自定义子类,它可能会有所不同)相对应的类别下。
下面几节提供了一些使用 JdbcTemplate 的例子。这些例子并不是 JdbcTemplate 所暴露的所有功能的详尽清单。请参见相关的 javadoc。
准备工作 - 构建 JdbcTemplate 对象
前面都没有单独的写过项目,一直都在 spring boot 环境中写的测试,这里只引入需要的类来实现这些测试,创建一个测试项目,然后引入下面的这些依赖(我使用的是 gradle 管理的依赖)
// junit 测试testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.8.2'// spring 测试框架implementation group: 'org.springframework', name: 'spring-test', version: '5.3.15'// 核心和上下文在implementation group: 'org.springframework', name: 'spring-core', version: '5.3.15'implementation group: 'org.springframework', name: 'spring-context', version: '5.3.15'// aopimplementation group: 'org.springframework', name: 'spring-aop', version: '5.3.15'// jdbc 包 + mysql 驱动implementation group: 'org.springframework', name: 'spring-jdbc', version: '5.3.15'implementation group: 'mysql', name: 'mysql-connector-java', version: '8.0.28'
核心和上下文在 Core/核心 中讲到过,就是在这两个依赖里面。junit 和 spring 测试框架在 Testing/测试 中讲到过。
然后准备一个 mysql 数据库,准备一个数据库 spring-read-docs,创建一个测试表:
CREATE TABLE `t_user` (`id` int(11) NOT NULL AUTO_INCREMENT,`username` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;-- 插入一条测试语句INSERT INTO `spring-read-docs`.`t_user` (`id`, `username`) VALUES (1, '张三');
构建 JdbcTemplate 对象
package cn.mrcode.study.springdocsread;import com.mysql.cj.jdbc.MysqlDataSource;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.jdbc.core.JdbcTemplate;import javax.sql.DataSource;/*** @author mrcode*/@Configurationpublic class AppConfig {// 构建 mysql 的数据源对象@Beanpublic DataSource dataSource() {final MysqlDataSource dataSource = new MysqlDataSource();dataSource.setURL("jdbc:mysql://127.0.0.1:3306/spring-read-docs?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=true");dataSource.setUser("root");dataSource.setPassword("root");return dataSource;}// 构建 JdbcTemplate@Beanpublic JdbcTemplate jdbcTemplate(DataSource dataSource) {return new JdbcTemplate(dataSource);}}
写一个测试类,使用 junit 来执行测试用例
package cn.mrcode.study.springdocsread.test;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;import cn.mrcode.study.springdocsread.AppConfig;/*** @author mrcode*/@SpringJUnitConfig({AppConfig.class})public class DemoTest {@Autowiredprivate JdbcTemplate jdbcTemplate;@Testpublic void fun1() {// 如果正常打印出 1 ,就说明环境已经构建好了final Integer count = jdbcTemplate.queryForObject("select count(*)from user", Integer.class);System.out.println(count);}}
:::tips
说明:
如果嫌麻烦,可以不用这里的项目构建,自己使用 spring boot 环境,然后像上面那样创建出 JdbcTemplate ,或则直接通过 new 的方式使用好像也是可以的
:::
准备一张表,用于后面的练习使用
CREATE TABLE `t_actor` (`id` int(11) NOT NULL AUTO_INCREMENT,`first_name` varchar(255) DEFAULT NULL,`last_name` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
:::tips
说明:后面练习中出现的语句,有可能这里没有给出来创建表的 SQL,那么要测试的话,就直接按需增加或则减少相关表。
因为这个教程是介绍如何使用、语法之类的,并不是练习查询的
:::
查询(SELECT)
Querying (SELECT)
下面的查询得到符合条件的行数:
int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class);
下面的查询使用了一个绑定变量:
int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject("select count(*) from t_actor where first_name = ?", Integer.class, "Joe");
下面查询返回了一个字符串:
String lastName = this.jdbcTemplate.queryForObject("select last_name from t_actor where id = ?",String.class, 1212L);
下面的查询找到并填充了一个单一的域对象:
Actor actor = jdbcTemplate.queryForObject("select first_name, last_name from t_actor where id = ?",(resultSet, rowNum) -> {Actor newActor = new Actor();newActor.setFirstName(resultSet.getString("first_name"));newActor.setLastName(resultSet.getString("last_name"));return newActor;},1212L);
下面的查询找到并填充了一个域对象的列表:
List<Actor> actors = this.jdbcTemplate.query("select first_name, last_name from t_actor",(resultSet, rowNum) -> {Actor actor = new Actor();actor.setFirstName(resultSet.getString("first_name"));actor.setLastName(resultSet.getString("last_name"));return actor;});
如果最后两个代码片断实际存在于同一个应用程序中,那么去除两个 RowMapper lambda 表达式中存在的重复,并将其提取为一个字段(其实是一个匿名 RowMapper 实现对象),然后可以根据需要由 DAO 引用。例如,将前面的代码片断写成下面的样子可能更好:
private final RowMapper<Actor> actorRowMapper = (resultSet, rowNum) -> {Actor actor = new Actor();actor.setFirstName(resultSet.getString("first_name"));actor.setLastName(resultSet.getString("last_name"));return actor;};public List<Actor> findAllActors() {return this.jdbcTemplate.query("select first_name, last_name from t_actor", actorRowMapper);}
其实我们可以使用内置的 BeanPropertyRowMapper 来帮助我们映射成 bean 对象
List<Actor> actors = jdbcTemplate.query("select first_name as firstName, last_name as lastName from t_actor", new BeanPropertyRowMapper<>(Actor.class));
用 JdbcTemplate 进行更新(INSERT、UPDATE 和 DELETE)
Updating (INSERT, UPDATE, and DELETE) with JdbcTemplate
你可以使用 update(.)方法来执行插入、更新和删除操作。参数值通常作为变量参数提供,或者作为一个对象数组提供。
下面的例子是插入一条新的数据:
this.jdbcTemplate.update("insert into t_actor (first_name, last_name) values (?, ?)","Leonor", "Watling");
下面的例子更新了一条现有数据:
this.jdbcTemplate.update("update t_actor set last_name = ? where id = ?","Banjo", 5276L);
下面的例子删除了一条数据:
this.jdbcTemplate.update("delete from t_actor where id = ?",Long.valueOf(actorId));
其他 JdbcTemplate 操作
Other JdbcTemplate Operations
你可以使用 execute(.)方法来运行任何任意的 SQL。因此,该方法经常被用于 DDL 语句。它被大量地重载,有一些变体,可以采取回调接口、绑定变量数组等等。下面的例子创建了一个表:
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
下面的例子调用了一个存储过程:
// 虽然调用的是 update,本质上最后还是执行一条 SQLthis.jdbcTemplate.update("call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",Long.valueOf(unionId));
JdbcTemplate 最佳实践
JdbcTemplate Best Practices
JdbcTemplate 类的实例一旦配置好,就是 线程安全 的。这很重要,因为这意味着你可以配置 JdbcTemplate 的一个实例,然后安全地将这个共享引用注入到多个 DAO(或存储库)。JdbcTemplate 是有状态的,因为它维护着对 DataSource 的引用,但这个状态不是对话状态。
在使用 JdbcTemplate 类(以及相关的 NamedParameterJdbcTemplate 类)时,一个常见的做法是在 Spring 配置文件中配置一个DataSource,然后将该共享的 DataSource Bean 依赖注入到 DAO 类中。JdbcTemplate 是在 DataSource 的 setter 中创建的。这导致了类似于以下的 DAO:
public class JdbcCorporateEventDao implements CorporateEventDao {private JdbcTemplate jdbcTemplate;public void setDataSource(DataSource dataSource) {this.jdbcTemplate = new JdbcTemplate(dataSource);}// 对 CorporateEventDao上的方法的 JDBC 支持的实现遵循...}
下面的例子显示了相应的 XML 配置:
<?xml version="1.0" encoding="UTF-8"?><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"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd"><bean id="corporateEventDao" class="com.example.JdbcCorporateEventDao"><property name="dataSource" ref="dataSource"/></bean><bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"><property name="driverClassName" value="${jdbc.driverClassName}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean><!-- 处理 dataSource 中的占位符属性 --><context:property-placeholder location="jdbc.properties"/></beans>
显式配置的另一个选择是使用组件扫描和注解支持依赖注入。在这种情况下,你可以用 @Repository来注解这个类(这使它成为组件扫描的候选者),并用 @Autowired来注解 DataSource的 setter方法。下面的例子展示了如何做到这一点:
@Repositorypublic class JdbcCorporateEventDao implements CorporateEventDao {private JdbcTemplate jdbcTemplate;@Autowiredpublic void setDataSource(DataSource dataSource) {this.jdbcTemplate = new JdbcTemplate(dataSource);}// JDBC-backed implementations of the methods on the CorporateEventDao follow...}
下面的例子显示了相应的 XML 配置:
<?xml version="1.0" encoding="UTF-8"?><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"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd"><!-- 在应用程序的基础包中扫描 @Component 类,将其配置为 Bean。 --><context:component-scan base-package="org.springframework.docs.test" /><bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"><property name="driverClassName" value="${jdbc.driverClassName}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean><context:property-placeholder location="jdbc.properties"/></beans>
如果你使用 Spring 的 JdbcDaoSupport 类,并且你的各种支持 JDBC 的 DAO 类都是从该类扩展而来,那么你的子类就会从 JdbcDaoSupport 类继承一个 setDataSource(..)方法。你可以选择是否继承自这个类。JdbcDaoSupport 类只是作为一种方便提供(该类暴露了一些常用的方法,比如从数据源创建一个 JdbcTemplate 类、获取链接、获取数据源等快捷方法)。
无论你选择使用(或不使用)上述哪种模板初始化方式,每次要运行 SQL 时,很少有必要创建一个新的 JdbcTemplate 类实例。一旦配置好, JdbcTemplate 实例就是线程安全的。如果你的应用程序访问多个数据库,你可能需要多个 JdbcTemplate 实例,这就需要多个 DataSources,然后需要多个不同配置的 JdbcTemplate 实例。
