【Spring6笔记】 - 11 - JDBCTemplate

【Spring6笔记】 - 11 - JDBCTemplate

1、JdbcTemplate 概述与开发环境搭建

1.1 什么是 JdbcTemplate?

JdbcTemplate 是 Spring 框架对 JDBC(Java Database Connectivity)的二次封装。它的核心设计目标是:

  • 简化代码:消除冗长的 JDBC 模板代码(如:注册驱动、创建连接、释放资源、异常处理等)。
  • 统一风格:提供了一套一致的 API 来执行 SQL 语句(增删改查)。
  • 灵活性 :虽然它简化了操作,但并没有隐藏底层的 JDBC 特性。如果需要,开发者依然可以通过回调函数(如 execute 方法)直接操作 PreparedStatement
  • 可集成性:它是 Spring 生态的一部分,能够完美享受 Spring 的声明式事务管理。

注意 :JdbcTemplate 并不是唯一的选择。在大型项目中,我们通常会让 Spring 集成更强大的 ORM 框架,如 MyBatisHibernate


1.2 Maven 项目依赖配置 (pom.xml)

要使用 JdbcTemplate,除了 Spring 核心依赖外,还需要引入 spring-jdbc 模块、数据库驱动以及第三方连接池。

以下是核心依赖清单(基于 Spring 6.x):

xml 复制代码
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>6.0.2</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.0.2</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.8</version>
</dependency>

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
</dependency>

1.3 数据源(DataSource)的概念与实现

在 JDBC 编程中,数据源(DataSource)是获取数据库连接(Connection)的标准工厂。

① 数据源的作用
  • 资源池化:管理连接的创建与销毁,避免频繁建立 TCP 连接带来的开销。
  • 接口规范 :只要实现了 javax.sql.DataSource 接口的类,都可以作为 Spring 的数据源(如 Druid, C3P0, DBCP, HikariCP)。
② 自定义数据源示例

虽然生产环境使用 Druid,但通过手动实现 DataSource 接口(如你代码中的 MyDataSource),可以清晰看到数据源的核心职责是利用 DriverManager 获取连接并注入配置参数(driverClass, url, username, password)。

java 复制代码
@Component("myDataSource")
public class MyDataSource implements DataSource {

    /*
    * 数据源存在的目的是为了提供Connection对象
    * 只要实现了DataSource接口的都是数据源
    * druid 连接池  c3p0 连接池 dbcp 连接池   都是实现了DataSource接口
    * */

    @Value("com.mysql.cj.jdbc.Driver")
    private String driverClass;
    @Value("jdbc:mysql://localhost:3306/spring6")
    private String url;
    @Value("root")
    private String username;
    @Value("123456")
    private String password;

    public String getDriverClass() {
        return driverClass;
    }

    public void setDriverClass(String driverClass) {
        this.driverClass = driverClass;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public Connection getConnection() throws SQLException {
        try {
            //注册驱动
            Class.forName(driverClass);
            //获取数据库连接对象
            return DriverManager.getConnection(url, username, password);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return null;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }
}

1.4 Spring 核心配置文件 (spring.xml)

在 Spring 中,JdbcTemplate 的初始化遵循 "依赖注入" 原则。

配置步骤解析:
  1. 配置数据源 (DataSource):管理连接池。
  2. 配置 JdbcTemplate:将数据源注入到 JdbcTemplate 实例中。
  3. 注解扫描 :为了方便后续使用 @Repository 等注解。
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/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">

    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="myDataSource"/>
    </bean>

    <context:component-scan base-package="com.zzz"/>
</beans>

1.5 验证环境是否成功

通过单元测试加载配置文件并获取 JdbcTemplate 实例,若不报错且能打印出对象地址,说明环境搭建成功。

java 复制代码
@Test
public void testJdbcTemplateInit() {
    // 1. 创建 Spring 容器
    ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
    // 2. 从容器中获取 JdbcTemplate 对象
    JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
    // 3. 打印对象,验证是否成功注入 DataSource
    System.out.println("JdbcTemplate 实例:" + jdbcTemplate);
}

2、使用 JdbcTemplate 完成基础 CURD 操作

JdbcTemplate 中,所有的写操作(增、删、改)统一使用 update() 方法,而读操作(查)则根据返回结果的不同(单条、多条、单值)使用不同的方法。

2.1 数据的写操作:新增、修改、删除

JdbcTemplate.update() 方法是执行所有 非查询 SQL 语句的核心。

① 新增数据 (Insert)
  • 核心方法int update(String sql, Object... args)

  • 代码示例

    java 复制代码
    String sql = "insert into t_user(real_name, age) values(?, ?)";
    // 第一个参数是SQL,后续是可变参数,对应SQL中的占位符 ?
    int count = jdbcTemplate.update(sql, "zzz", 23); 
    System.out.println("成功插入记录数:" + count);
② 修改数据 (Update)
  • 逻辑说明 :与新增一致,只需改变 SQL 语句。通常需要带 where 条件防止全表更新。

  • 代码示例

    java 复制代码
    String sql = "update t_user set real_name = ?, age = ? where id = ?";
    int count = jdbcTemplate.update(sql, "张三", 25, 1);
③ 删除数据 (Delete)
  • 逻辑说明:通常通过主键 ID 进行精准删除。

  • 代码示例

    java 复制代码
    String sql = "delete from t_user where id = ?";
    int count = jdbcTemplate.update(sql, 1);

2.2 数据的查询操作:单条、多条、聚合

查询是 JdbcTemplate 最灵活的部分。为了将数据库的 ResultSet 结果集自动转换为 Java 对象,我们需要用到 RowMapper 接口。Spring 提供了默认实现类 BeanPropertyRowMapper

① 查询单条记录 (Query One Object)
  • 核心方法T queryForObject(String sql, RowMapper<T> rowMapper, Object... args)

  • 重点分析BeanPropertyRowMapper 会自动根据数据库列名 (如 real_name)与实体类属性名 (如 realName)进行驼峰映射。

  • 代码示例

    java 复制代码
    String sql = "select * from t_user where id = ?";
    // 注意:如果查询不到结果或查到多条结果,此方法会抛出异常
    User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), 2);
② 查询多条记录 (Query List)
  • 核心方法List<T> query(String sql, RowMapper<T> rowMapper, Object... args)

  • 代码示例

    java 复制代码
    String sql = "select * from t_user";
    List<User> userList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
    // 结果为一个 List 集合,若无数据则返回空集合(长度为0)
③ 查询单列单值 (Query Single Value)
  • 核心场景 :常用于聚合函数,如 count(*)max()sum() 等。

  • 核心方法T queryForObject(String sql, Class<T> requiredType)

  • 代码示例

    java 复制代码
    String sql = "select count(1) from t_user";
    // 第二个参数指定返回值的类型(如 Integer.class 或 Long.class)
    Integer count = jdbcTemplate.queryForObject(sql, Integer.class);

2.3 实体类 (POJO) 的编写规范

在使用 BeanPropertyRowMapper 时,实体类(如 User.java)必须满足以下条件:

  1. 必须有无参构造方法:Spring 反射创建对象时需要。
  2. 属性名与数据库字段对应 :支持下划线转驼峰(例如:数据库 real_name 对应 Java realName)。
  3. 提供 Getter/Setter 方法:用于属性注入。

总结:

  • 增/删/改 :统统找 update()
  • 查对象/查列表 :利用 BeanPropertyRowMapper 自动封装。
  • 查数量 :指定返回类型的 Class 对象。

3、JdbcTemplate 高阶应用(批量操作与回调函数)

在处理海量数据或需要高度自定义 JDBC 过程时,简单的 updatequery 可能不够用。Spring 提供了批量处理机制和底层回调接口。

3.1 批量操作(Batch Operations)

当需要同时插入或更新成千上万条记录时,单条执行 SQL 会频繁与数据库建立连接,导致性能低下。批量操作可以显著减少网络开销。

① 批量新增/修改/删除
  • 核心方法int[] batchUpdate(String sql, List<Object[]> batchArgs)
  • 参数解析
    • sql:带占位符的 SQL 模板。
    • batchArgs:一个 List 集合,集合中的每个 Object[] 数组代表一行数据,数组内的元素对应 SQL 中的 ?
  • 返回值 :返回一个 int[] 数组,数组的每个元素代表对应行受影响的行数。
② 代码实现(以批量新增为例)
java 复制代码
@Test
public void testBatchInsert() {
    String sql = "insert into t_user(real_name, age) values(?,?)";
    
    // 1. 准备多组数据,每组数据是一个 Object 数组
    Object[] obj1 = {"zzz1", 23};
    Object[] obj2 = {"zzz2", 24};
    Object[] obj3 = {"zzz3", 25};

    // 2. 将数组添加到 List 集合中
    List<Object[]> list = new ArrayList<>();
    list.add(obj1);
    list.add(obj2);
    list.add(obj3);

    // 3. 执行批量操作
    int[] counts = jdbcTemplate.batchUpdate(sql, list);
    System.out.println("批量执行结果:" + Arrays.toString(counts));
}

注意 :批量修改和批量删除的逻辑完全一致,只需更换 SQL 语句并调整 Object[] 数组中的参数顺序(例如把 ID 放在最后)。


3.2 使用回调函数实现底层操作(Callback)

JdbcTemplate 屏蔽了太多底层细节,如果你偶尔想重温原始的 JDBC 编程(比如手动操作 ResultSetPreparedStatement),可以使用 execute 方法配合回调接口。

① 核心接口:PreparedStatementCallback
  • 作用 :允许你直接操作 PreparedStatement 对象。
  • 优势:在不破坏 Spring 事务管理和连接管理的前提下,获得对 JDBC 底层对象的完全控制权。
② 代码实现
java 复制代码
@Test
public void testCallback() {
    String sql = "select * from t_user where id = ?";
    
    // 使用 execute 方法并传入匿名内部类
    User user = jdbcTemplate.execute(sql, new PreparedStatementCallback<User>() {
        @Override
        public User doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
            // 1. 手动设置参数
            ps.setInt(1, 2);
            
            // 2. 手动执行查询并处理结果集
            ResultSet rs = ps.executeQuery();
            User user = null;
            if (rs.next()) {
                user = new User(
                    rs.getInt("id"),
                    rs.getString("real_name"),
                    rs.getInt("age")
                );
            }
            return user; // 此返回值将作为 jdbcTemplate.execute 的返回值
        }
    });
    System.out.println("通过回调获取的对象:" + user);
}

3.3 数据库字段名与实体类属性名的映射

在你的代码中,数据库字段是 real_name(下划线),而 Java 属性是 realName(驼峰)。

  • BeanPropertyRowMapper 的神奇之处

    它默认支持下划线转驼峰 。只要你的数据库字段遵循规范(如 user_id),Java 类遵循规范(如 userId),它就能自动匹配。

  • 手动映射方案

    如果字段名完全不一致(例如数据库叫 name,Java 叫 username),则不能使用 BeanPropertyRowMapper,而需要实现 RowMapper<User> 接口并重写 mapRow 方法,手动调用 set 方法。


总结与对比

操作类型 推荐方法 关键点
单条增删改 update() 直接传参即可
批量增删改 batchUpdate() 使用 List<Object[]> 封装数据
查单条对象 queryForObject() 配合 BeanPropertyRowMapper,需处理查不到报错
查 List 集合 query() 配合 BeanPropertyRowMapper,查不到返回空集合
聚合查询 queryForObject() 第二个参数直接传 Integer.classLong.class
底层复杂操作 execute() 使用 PreparedStatementCallback 接口
相关推荐
2201_756847332 小时前
Golang如何处理JSON空值null_Golang JSON空值处理教程【精通】
jvm·数据库·python
hef2882 小时前
怎么诊断MongoDB Config Server响应极慢的问题_高频Auto-split导致的元库写入压力
jvm·数据库·python
也许明天y2 小时前
Spring AI 核心原理解析:基于 1.1.4 版本拆解底层架构
java·后端·spring
qq_380619162 小时前
html怎么用deno运行_Deno如何作为本地服务器运行HTML文件
jvm·数据库·python
ruan1145142 小时前
Redis--个人学习记录
数据库·redis·学习
小红的布丁2 小时前
BIO、NIO、AIO 与 IO 多路复用:select、poll、epoll 详解
java·数据库·nio
袋鼠云数栈2 小时前
AI 时代,企业为何必须重新思考数据底座?
数据库·数据治理·数据中台·数栈·袋鼠云
Elastic 中国社区官方博客2 小时前
在 Elastic 中使用 OpenTelemetry 内容包可视化 OpenTelemetry 数据
大数据·开发语言·数据库·elasticsearch·搜索引擎
Ahern_2 小时前
PolarDB 8.4.19 单节点安装
mysql·centos