开发者

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

开发者 https://www.devze.com 2025-10-30 10:27 出处:网络 作者: lang20150928
目录 一、整体结构概览 二、深入理解 3.8 节:参数与数据值处理的常见问题✅ 3.8.1 提供 SQL 类型信息(SQL Type for Parameters)❓ 问题背景:✅ 解决方案:✅ 3.8.2 处理 BLOB 和 CLOB(大对象)❓ 什么是 BLOB /
目录
  • 一、整体结构概览
  • 二、深入理解 3.8 节:参数与数据值处理的常见问题
    • ✅ 3.8.1 提供 SQL 类型信息(SQL Type for Parameters)
      • ❓ 问题背景:
      • ✅ 解决方案:
    • ✅ 3.8.2 处理 BLOB 和 CLOB(大对象)
      • ❓ 什么是 BLOB / CLOB?
      • ❓ 为什么需要特殊处理?
      • ✅ Spring 的解决方案:LobHandler+LobCreator
    • ✅ 3.8.3 为IN子句传递列表(List in IN Clause)
      • ❓ 问题:
      • ✅ Spring 的解决方案:
    • ✅ 3.8.4 存储过程调用中的复杂类型(Complex Types)
      • ❓ 问题:
      • ✅ 解决方案:
  • ️ 三、深入理解 3.9 节:嵌入式数据库(Embedded Database)
    • ✅ 3.9.1 为什么要用嵌入式数据库?
      • ✅ 3.9.2 通过 XML 创建嵌入式数据库
        • ✅ 3.9.3 编程方式创http://www.devze.com建(推荐用于测试)
          • ✅ 3.9.4 支持的嵌入式数据库类型
            • ✅ 3.9.5 测试数据访问逻辑(最佳实践模板)
              • ✅ 3.9.6 为什么需要“唯一数据库名”?
                • ❓ 问题:
                • ✅ 解决方案:
              • ✅ 3.9.7 扩展支持(高级)
              • 四、总结:这些知识能用来做什么?
                • 五、学习建议
                  • 补充:现代 Spring 推荐替代方案

                    以下内容是 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 Obje编程ct → 图片、音频、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 setV编程alues(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:te编程st-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 DataAccessT编程est {
                        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参数处理内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

                    0

                    精彩评论

                    暂无评论...
                    验证码 换一张
                    取 消

                    关注公众号