Spring JDBC参数处理与嵌入式数据库的实战指南

 更新时间:2025年10月29日 11:42:41   作者:lang20150928  
这篇文章介绍了Spring Framework中JDBC的支持,包括如何处理参数和数据值、处理BLOB和CLOB、为IN子句传递列表以及存储过程调用中的复杂类型,本文用通俗易懂的方式讲解Spring JDBC参数处理与嵌入式数据库的相关操作,感兴趣的朋友跟随小编一起看看吧

以下内容是 Spring Framework 官方文档中关于 JDBC 支持的两个重要章节(3.8 和 3.9),主要讲解了在使用 Spring 的 JdbcTemplate 和相关工具时,如何处理一些常见的、复杂的数据库操作问题,以及如何使用嵌入式数据库(Embedded Database)进行开发和测试。

下面我将用通俗易懂的方式,帮你系统地理解这段内容的核心思想和关键知识点,并说明它们在实际开发中的意义。

🧩 一、整体结构概览

章节主题用途
3.8参数与数据值处理常见问题解决 JDBC 操作中的特殊场景
3.9嵌入式数据库支持快速搭建轻量级数据库用于开发/测试

🔍 二、深入理解 3.8 节:参数与数据值处理的常见问题

✅ 3.8.1 提供 SQL 类型信息(SQL Type for Parameters)

❓ 问题背景:

Java 中的 null 值传给数据库时,JDBC 不知道它对应的是 VARCHAR 还是 INTEGER,所以无法正确设置类型。这会导致插入 NULL 失败。

✅ 解决方案:

Spring 允许你在设置参数时显式指定 SQL 类型(来自 java.sql.Types 的常量)。

三种方式:

int 数组方式(适用于位置参数)

jdbcTemplate.update(
    "INSERT INTO users(name, age) VALUES(?, ?)",
    "Tom", 25,
    new int[]{Types.VARCHAR, Types.INTEGER}  // 显式指定类型
);

使用 SqlParameterValue 包装参数

new SqlParameterValue(Types.VARCHAR, "Tom")

可以更精细控制,比如设置 scale(小数位数)。

命名参数 + SqlParameterSource
使用 MapSqlParameterSourceBeanPropertySqlParameterSource 并注册类型:

MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("name", "Tom", Types.VARCHAR);
params.addValue("age", 25, Types.INTEGER);

💡 关键点:主要用于处理 NULL 插入或类型模糊的情况。

✅ 3.8.2 处理 BLOB 和 CLOB(大对象)

❓ 什么是 BLOB / CLOB?

  • BLOB:Binary Large Object → 图片、音频、PDF 等二进制文件。
  • CLOB:Character Large Object → 大段文本(如文章、日志)。

❓ 为什么需要特殊处理?

普通 Stringbyte[] 在读写大文件时会占用大量内存。理想做法是流式处理

✅ Spring 的解决方案:LobHandler+LobCreator

功能接口方法
写入 LOBLobCreatorsetBlobAsBinaryStream, setClobAsCharacterStream
读取 LOBLobHandlergetBlobAsBytes, getClobAsString

示例:插入图片和文本文件

jdbcTemplate.execute(
    "INSERT INTO docs(id, content, image) VALUES (?, ?, ?)",
    new AbstractLobCreatingPreparedStatementCallback(lobHandler) {
        protected void setValues(PreparedStatement ps, LobCreator lc) throws SQLException {
            ps.setLong(1, 1L);
            lc.setClobAsCharacterStream(ps, 2, reader, (int)file.length()); // CLOB
            lc.setBlobAsBinaryStream(ps, 3, inputStream, (int)image.length()); // BLOB
        }
    }
);

⚠️ 注意:lobHandler 通常是 DefaultLobHandler,但注意它不支持流式读取超过 Integer.MAX_VALUE 的数据。

优势:避免一次性加载整个大文件到内存。

✅ 3.8.3 为IN子句传递列表(List in IN Clause)

❓ 问题:

SQL 不允许预编译语句动态占位符数量,比如:

SELECT * FROM users WHERE id IN (?)

但如果要传 (1,2,3),就需要三个 ?

✅ Spring 的解决方案:

自动拼接 SQL,根据 List 长度生成对应数量的 ?

List<Long> ids = Arrays.asList(1L, 2L, 3L);
List<User> users = jdbcTemplate.query(
    "SELECT * FROM users WHERE id IN (:ids)",
    new MapSqlParameterSource("ids", ids),
    userRowMapper
);

✅ 使用 NamedParameterJdbcTemplate 自动处理。

⚠️ 注意限制

  • 大多数数据库对 IN 列表有上限(如 Oracle 是 1000)。
  • 如果超过,应分批查询。

✅ 3.8.4 存储过程调用中的复杂类型(Complex Types)

❓ 问题:

某些数据库(如 Oracle)支持自定义对象类型(如 STRUCT, ARRAY),Java 如何传递和接收?

✅ 解决方案:

输出参数(返回复杂类型)→ SqlReturnType

declareParameter(new SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE",
    (cs, idx, sqlType, typeName) -> {
        STRUCT struct = (STRUCT) cs.getObject(idx);
        // 转换为 Java 对象
        return new TestItem(...);
    }));

输入参数(传入复杂类型)→ SqlTypeValue

SqlTypeValue value = new AbstractSqlTypeValue() {
    protected Object createTypeValue(Connection conn, ...) {
        StructDescriptor desc = new StructDescriptor("ITEM_TYPE", conn);
        return new STRUCT(desc, conn, new Object[]{id, name, date});
    }
};

然后作为参数传入:

Map<String, Object> in = new HashMap<>();
in.put("item", value);
storedProc.execute(in);

✅ 适用于 Oracle、PostgreSQL 等支持复杂类型的数据库。

🛠️ 三、深入理解 3.9 节:嵌入式数据库(Embedded Database)

✅ 3.9.1 为什么要用嵌入式数据库?

  • 轻量快速:无需安装 MySQL/PostgreSQL。
  • 启动快:内存中运行,适合单元测试。
  • 隔离性好:每个测试独立数据库,不污染真实数据。
  • 便于自动化测试:CI/CD 中无需外部依赖。

常见用途:单元测试、集成测试、原型开发

✅ 3.9.2 通过 XML 创建嵌入式数据库

<jdbc:embedded-database id="dataSource" generate-name="true">
    <jdbc:script location="classpath:schema.sql"/>
    <jdbc:script location="classpath:test-data.sql"/>
</jdbc:embedded-database>
  • 自动创建 HSQL 内存数据库。
  • 执行建表脚本和测试数据脚本。
  • 生成一个 DataSource Bean,可注入 DAO。

✅ 3.9.3 编程方式创建(推荐用于测试)

EmbeddedDatabase db = new EmbeddedDatabaseBuilder()
    .generateUniqueName(true)
    .setType(H2)
    .addScript("schema.sql")
    .addScripts("data.sql")
    .build();

EmbeddedDatabase 实现了 DataSource,可以直接传给 JdbcTemplate

✅ 测试结束后记得 db.shutdown()

✅ 3.9.4 支持的嵌入式数据库类型

类型说明
HSQL默认,老牌嵌入式数据库
H2功能强大,支持 MySQL 模式、Web 控制台
DerbyApache 开源,Java 编写

推荐使用 H2,功能最全,调试方便。

✅ 3.9.5 测试数据访问逻辑(最佳实践模板)

class DataAccessTest {
    private EmbeddedDatabase db;
    @BeforeEach
    void setUp() {
        db = new EmbeddedDatabaseBuilder()
            .generateUniqueName(true)
            .addDefaultScripts() // 自动加载 schema.sql + data.sql
            .build();
    }
    @Test
    void shouldFindUserById() {
        JdbcTemplate template = new JdbcTemplate(db);
        List<User> users = template.query("SELECT * FROM users WHERE id = ?", 
            new Object[]{1}, userRowMapper);
        assertThat(users).hasSize(1);
    }
    @AfterEach
    void tearDown() {
        db.shutdown();
    }
}

✅ 这是典型的 集成测试模板

✅ 3.9.6 为什么需要“唯一数据库名”?

❓ 问题:

多个测试类都用相同的配置创建嵌入式数据库,Spring 会复用同一个实例(因为默认名字是 testdb),导致数据污染。

✅ 解决方案:

启用唯一名称生成:

.generateUniqueName(true)

或 XML 中:

<jdbc:embedded-database generate-name="true">

✅ 每次创建都是独立数据库,避免冲突。

✅ 3.9.7 扩展支持(高级)

你可以自定义:

  • 新的嵌入式数据库类型(实现 EmbeddedDatabaseConfigurer
  • 新的 DataSource 工厂(如连接池)

一般开发者不需要做,但框架开发者可以贡献扩展。

🎯 四、总结:这些知识能用来做什么?

技术点实际应用场景
SqlParameterValue插入 NULL 字段,确保类型正确
LobHandler存储图片、文档、大文本(如博客内容)
IN 子句传 List批量查询用户、订单等
SqlTypeValue调用 Oracle 存储过程传对象
嵌入式数据库单元测试、快速原型、CI/CD 自动化

📌 五、学习建议

  1. 先掌握 JdbcTemplate 基础用法(查询、更新)
  2. 再学命名参数和 IN 子句(日常开发高频)
  3. 测试时使用嵌入式数据库(提升测试效率)
  4. 只有对接 Oracle 存储过程时才深入 SqlTypeValue

📘 补充:现代 Spring 推荐替代方案

虽然这些技术仍然有效,但在现代 Spring Boot 项目中:

传统技术现代替代
JdbcTemplate + LobHandler使用文件存储服务(如 MinIO、S3)+ 数据库存路径
手动创建嵌入式数据库使用 @DataJpaTest + H2(Spring Boot 自动配置)
SqlTypeValue使用 JPA + Hibernate 映射复杂类型,或 MyBatis

如果你告诉我你目前在做什么项目(比如:Web 后端?单元测试?还是对接 Oracle?),我可以给出更具体的建议 😊

到此这篇关于Spring JDBC实战:参数处理与嵌入式数据库的文章就介绍到这了,更多相关Spring JDBC参数处理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java静态泛型使用方法实例解析

    Java静态泛型使用方法实例解析

    这篇文章主要介绍了Java静态泛型使用方法实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • java语言求解兔子问题代码分析

    java语言求解兔子问题代码分析

    这篇文章主要介绍了Java语言求解兔子问题代码分析,具有一定借鉴价值,需要的朋友可以了解下。
    2017-12-12
  • Java日期毫秒值和常见日期时间格式相互转换方法

    Java日期毫秒值和常见日期时间格式相互转换方法

    这篇文章主要给大家介绍了关于Java日期毫秒值和常见日期时间格式相互转换的相关资料,在Java的日常开发中,会随时遇到需要对时间处理的情况,文中给出了详细的示例代码,需要的朋友可以参考下
    2023-07-07
  • SpringBoot实现接口参数加密解密的示例代码

    SpringBoot实现接口参数加密解密的示例代码

    加密解密本身并不是难事,问题是在何时去处理?SpringMVC 中给我们提供了 ResponseBodyAdvice 和 RequestBodyAdvice,利用这两个工具可以对请求和响应进行预处理,非常方便。废话不多说,我们一起来学习一下
    2022-09-09
  • Java swing框架实现的贪吃蛇游戏完整示例

    Java swing框架实现的贪吃蛇游戏完整示例

    这篇文章主要介绍了Java swing框架实现的贪吃蛇游戏,结合完整实例形式分析了java使用swing框架结合awt图形绘制实现贪吃蛇游戏的具体步骤与相关实现技巧,需要的朋友可以参考下
    2017-12-12
  • Spring Boot 中集成 Lombok 和 MapStruct最佳实践指南

    Spring Boot 中集成 Lombok 和 MapStruct最

    文章详解SpringBoot项目中Lombok与MapStruct整合实践,涵盖版本兼容、IDE配置、代码分层、映射配置、测试验证及性能优化,重点解决注解冲突、依赖注入等常见问题,强调分层管理和组件扫描配置,提升开发效率与代码简洁性,本文给大家介绍的非常详细,感兴趣的朋友一起看看吧
    2025-08-08
  • 读取Java文件到byte数组的三种方法(总结)

    读取Java文件到byte数组的三种方法(总结)

    下面小编就为大家带来一篇读取Java文件到byte数组的三种方法(总结)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-08-08
  • Java语言实现最大堆代码示例

    Java语言实现最大堆代码示例

    这篇文章主要介绍了Java语言实现最大堆代码示例,具有一定参考价值,需要的朋友可以了解下。
    2017-12-12
  • Java中实现Redis管道技术的代码详解

    Java中实现Redis管道技术的代码详解

    在高并发的应用中,数据访问性能往往是系统性能的关键瓶颈之一,Redis作为一款高性能的内存数据库,广泛应用于缓存、会话存储等场景,然而,在某些需要执行大量Redis命令的场景下,网络往返延迟,Redis提供了管道技术解决这一问题,下面小编给大家详细说说
    2025-04-04
  • 一文带你彻底理解Java序列化和反序列化

    一文带你彻底理解Java序列化和反序列化

    这篇文章主要介绍了Java序列化和反序列化的相关资料,帮助大家更好的理解和学习Java,感兴趣的朋友可以了解下
    2020-09-09

最新评论