使用 JDBC

尽管 Jmix 主要使用 JPA 进行数据访问,但在某些场景下可能仍需直接使用 JDBC,例如执行复杂的 SQL 查询、执行批量操作或调用存储过程。

我们建议优先使用 DataManager,仅在必要时才使用到 JDBC。

可以使用 Spring 提供的 JdbcTemplateJdbcClient 类在 JDBC 级别执行操作。

JdbcTemplateJdbcClient 都会自动加入 Spring 管理的 事务,因此可以在同一事务中与 JPA 操作一起使用。

使用 JdbcTemplate

JdbcTemplate 是 Spring 框架的一个经典类,通过资源管理和异常事务简化了 JDBC 操作。

如需使用 JdbcTemplate 访问 主数据存储,只需在 bean 中注入:

@Autowired
private JdbcTemplate jdbcTemplate;

public Map<String, BigDecimal> getCustomerAmounts(CustomerGrade grade) {
    return jdbcTemplate.query(
            """
            select c.NAME, sum(o.AMOUNT)
            from CUSTOMER c join ORDER_ o on c.ID = o.CUSTOMER_ID
            where c.GRADE = ?
            group by c.NAME
            """,
            (ResultSet rs) -> {
                Map<String, BigDecimal> result = new HashMap<>();
                while (rs.next()) {
                    result.put(rs.getString(1), rs.getBigDecimal(2));
                }
                return result;
            },
            grade.getId()
    );
}

如果需要访问 附加数据存储,首先注入存储对应的 javax.sql.DataSource,然后创建一个 JdbcTemplate 的新实例。在下面的示例中,创建了 db1 数据存储的 JdbcTemplate

@Autowired
@Qualifier("db1DataSource")
private DataSource db1DataSource;

public List<String> loadFooNames() {
    JdbcTemplate jdbcTemplate = new JdbcTemplate(db1DataSource);
    return jdbcTemplate.queryForList("select NAME from SAMPLE_FOO", String.class);
}

使用 JdbcClient

JdbcClient 是在 Spring 6.1 引入的,为 JDBC 提供了更现代的流式操作 API。

如需使用 JdbcClient 访问 主数据存储,只需在 bean 中注入:

@Autowired
private JdbcClient jdbcClient;

public Map<String, BigDecimal> getCustomerAmountsByJdbcClient(CustomerGrade grade) {
    return jdbcClient.sql("""
                select c.NAME, sum(o.AMOUNT)
                from CUSTOMER c join ORDER_ o on c.ID = o.CUSTOMER_ID
                where c.GRADE = :grade
                group by c.NAME
                """)
            .param("grade", grade.getId())
            .query((ResultSet rs) -> {
                Map<String, BigDecimal> result = new HashMap<>();
                while (rs.next()) {
                    result.put(rs.getString(1), rs.getBigDecimal(2));
                }
                return result;
            });
}

如果需要访问 附加数据存储,首先注入存储对应的 javax.sql.DataSource,然后创建一个 JdbcClient 的新实例。在下面的示例中,创建了 db1 数据存储的 JdbcClient

@Autowired
@Qualifier("db1DataSource")
private DataSource db1DataSource;

public List<String> loadFooNamesByJdbcClient() {
    JdbcClient jdbcClient = JdbcClient.create(db1DataSource);
    return jdbcClient.sql("select NAME from SAMPLE_FOO").query(String.class).list();
}

调用存储过程

可以使用 Spring 的 SimpleJdbcCall 类执行存储过程。相对于 JdbcTemplate,该类提供了更好的参数处理和数据库元数据支持。

下面的示例演示如何在主数据存储调用 PostgreSQL 的存储过程。

CREATE OR REPLACE FUNCTION get_customer_stats(
    p_customer_id UUID,
    OUT total_orders INTEGER,
    OUT total_amount DECIMAL(19,2)
)
LANGUAGE plpgsql
AS $$
BEGIN
    SELECT
        COUNT(*),
        COALESCE(SUM(AMOUNT), 0)
    INTO
        total_orders,
        total_amount
    FROM ORDER_
    WHERE CUSTOMER_ID = p_customer_id;
END;
$$;
@Autowired
private JdbcTemplate jdbcTemplate;

public record CustomerStats(Integer totalOrders, BigDecimal totalAmount) {
}

public CustomerStats callStoredProcedure(UUID customerId) {
    // Using SimpleJdbcCall for stored procedure
    SimpleJdbcCall jdbcCall = new SimpleJdbcCall(jdbcTemplate)
            .withFunctionName("get_customer_stats")
            .withoutProcedureColumnMetaDataAccess()
            .declareParameters(
                    new SqlParameter("p_customer_id", Types.OTHER), // UUID type
                    new SqlOutParameter("total_orders", Types.INTEGER),
                    new SqlOutParameter("total_amount", Types.DECIMAL)
            );

    // Execute the stored procedure
    Map<String, Object> result = jdbcCall.execute(customerId);

    // Extract results
    Integer totalOrders = (Integer) result.get("total_orders");
    BigDecimal totalAmount = (BigDecimal) result.get("total_amount");

    return new CustomerStats(totalOrders, totalAmount);
}

如果需要调用附加数据存储中的存储过程,首先按 上面 的说明创建对应 javax.sql.DataSourceJdbcTemplate