Обработка курсора из PostgreSQL с помощью Spring JDBC

#java #spring #postgresql #spring-jdbc

Вопрос:

Я пытаюсь выполнить функцию sql с помощью классов SimpleJdbcCall и BeanPropertyRowMapper, но не могу получить правильный список объектов. Похоже, в списке есть пара ключ-значение. Также у меня есть ошибка:

 Inconvertible types; cannot cast 'com.example.demo.model.MyCalendarDto' to 'org.springframework.util.LinkedCaseInsensitiveMap'
 

Нужно отметить, что я обязан использовать именно эту функцию и не могу ее изменить. Я использую PostgreSQL 13. Может кто-нибудь показать мне ошибку?

  1. Стол:
 -- cntr_m2.calendar definition

-- Drop table

-- DROP TABLE cntr_m2.calendar;

CREATE TABLE cntr_m2.calendar (
    id_calendar int4 NOT NULL,
    period_name varchar(255) NOT NULL,
    calendar_date date NULL,
    calendar_level int4 NOT NULL,
    calendar_level_name varchar NULL,
    year_number int4 NULL,
    month_number int4 NULL
);


-- cntr_m2.calendar foreign keys
 
  1. Функция:
 CREATE OR REPLACE FUNCTION cntr_m2.f_get_year(p_id_year_in integer DEFAULT NULL::integer)
 RETURNS refcursor
 LANGUAGE plpgsql
AS $function$
declare
    ref refcursor;
begin
    open ref for
    select c.id_calendar as id_year,
           c.year_number
      from cntr_m2.calendar c
     where c.id_calendar = coalesce(p_id_year_in, c.id_calendar)
       and c.calendar_level_name = 'year';

    return ref;
end;

$function$
;

 
  1. Класс сущностей:
 package com.example.demo.model;

import java.util.Objects;

public class MyCalendarDto {

    private Integer idYear;
    private Integer yearNumber;

    public MyCalendarDto() {
    }

    public Integer getIdYear() {
        return idYear;
    }

    public void setIdYear(Integer idYear) {
        this.idYear = idYear;
    }

    public Integer getYearNumber() {
        return yearNumber;
    }

    public void setYearNumber(Integer yearNumber) {
        this.yearNumber = yearNumber;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        MyCalendarDto that = (MyCalendarDto) o;
        return Objects.equals(idYear, that.idYear) amp;amp; Objects.equals(yearNumber, that.yearNumber);
    }

    @Override
    public int hashCode() {
        return Objects.hash(idYear, yearNumber);
    }

}
 
  1. Хранилище
 package com.example.demo.repository;

import com.example.demo.model.MyCalendarDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcCall;
import org.springframework.stereotype.Repository;


import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.util.*;

@Repository
public class JdbcMyCalendarRepository implements MyCalendarRepository{

    @Autowired
    private DataSource dataSource;
    private JdbcTemplate jdbcTemplate;
    private SimpleJdbcCall simpleJdbcCall;

    @PostConstruct
    private void postConstruct(){
        this.jdbcTemplate = new JdbcTemplate(dataSource);
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        jdbcTemplate.setResultsMapCaseInsensitive(true);
        this.simpleJdbcCall = new SimpleJdbcCall(jdbcTemplate)
                .withSchemaName("cntr_m2")
                .withProcedureName("f_get_year")
                .returningResultSet("#result-set-1", 
                        BeanPropertyRowMapper.newInstance(MyCalendarDto.class));
    }

    @Override
    public List<MyCalendarDto> findMyCalendars(Integer id) {
        SqlParameterSource parameters = new MapSqlParameterSource()
                .addValue("p_id_year_in", id);

        Map out = simpleJdbcCall.execute(parameters);

        if (out == null){
            return Collections.emptyList();
        }
        return  (List) out.get("#result-set-1");

    }
}
 

Комментарии:

1. Вы возвращаете результат с фиксированным количеством столбцов. Почему бы не сделать это функцией возврата набора и не использовать select * from f_get_year() вместо этого? Тогда вы «просто» запускаете SELECT в Spring JDBC

2. Я должен следовать правилам и использовать функции, которые уже существуют.

Ответ №1:

К сожалению, у Spring нет возможности угадать сопоставление между столбцами вашей процедуры и атрибутами POJO, за исключением случаев, когда их имена на 100% идентичны, поэтому вам нужно явно указать, как выполнить сопоставление между ними с помощью RowMapper<Your_POJO>

Здесь вы можете найти пример того, как это сделать. (см. 1.1 Пользовательский формат строк)

https://mkyong.com/spring/spring-jdbctemplate-querying-examples/

Ответ №2:

Это работает так по какой-то причине:

 @Repository
public class JdbcMyCalendarRepository implements MyCalendarRepository{

    @Autowired
    private DataSource dataSource;
    private JdbcTemplate jdbcTemplate;
    private SimpleJdbcCall simpleJdbcCall;

    @PostConstruct
    private void postConstruct(){
        jdbcTemplate = new JdbcTemplate(dataSource);
        jdbcTemplate.setResultsMapCaseInsensitive(true);
        simpleJdbcCall = new SimpleJdbcCall(jdbcTemplate)
                .withSchemaName("cntr_m2")
                .withProcedureName("f_get_year")
                .declareParameters(
                        new SqlParameter("p_id_year_in", Types.INTEGER))
                .withoutProcedureColumnMetaDataAccess()
                .returningResultSet("calendars",
                        BeanPropertyRowMapper.newInstance(MyCalendarDto.class));
    }

    @Override
    public List<MyCalendarDto> findMyCalendars(Integer id) {


        Map out = simpleJdbcCall.execute(new MapSqlParameterSource().addValue("p_id_year_in", id));
        if (out == null){
            return Collections.emptyList();
        }
        return  (List) out.get("calendars");
    }
}