Использование JDBC

Хотя в Jmix для доступа к данным в основном используется JPA, существуют некоторые сценарии, в которых может потребоваться прямой доступ через JDBC, например, выполнение сложных SQL-запросов, пакетных операций или вызов хранимых процедур.

Мы рекомендуем использовать DataManager и прибегать к JDBC только при необходимости.

Для выполнения запросов на уровне JDBC можно использовать классы JdbcTemplate или JdbcClient, предоставляемые Spring.

И JdbcTemplate, и JdbcClient автоматически участвуют в управляемых Spring транзакциях, поэтому вы можете использовать их вместе с операциями JPA в рамках одной транзакции.

Использование JdbcTemplate

JdbcTemplate — это классический класс Spring Framework, который упрощает операции с JDBC, обеспечивая управление ресурсами и преобразование исключений.

Чтобы использовать JdbcTemplate для доступа к основному хранилищу данных, просто инжектируйте его в ваш бин:

@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. В следующем примере JdbcTemplate создается для хранилища данных db1:

@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 Framework 6.1, предоставляет более современный и удобный API для операций JDBC.

Чтобы использовать JdbcClient для доступа к основному хранилищу данных, просто инжектируйте его в ваш бин:

@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. В следующем примере JdbcClient создается для хранилища данных db1:

@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();
}

Вызов хранимых процедур

Для выполнения хранимых процедур можно использовать класс SimpleJdbcCall из Spring. Он обеспечивает лучшую обработку параметров и поддержку метаданных базы данных по сравнению с 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);
}

Если нужно вызвать процедуру из дополнительного хранилища данных, создайте экземпляр JdbcTemplate для соответствующего javax.sql.DataSource, как описано выше.