Non riesco a scrivere una semplice query in jdbcTemplate

di il
8 risposte

Non riesco a scrivere una semplice query in jdbcTemplate

Ho questa tabella:

CREATE TABLE IF NOT EXISTS client (
    id BIGSERIAL NOT NULL,
    ip VARCHAR(39) NOT NULL,
    data TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT client_pk PRIMARY KEY(id),
    CONSTRAINT client_uk UNIQUE (ip)
);
Ho aggiunto al database questi record:

    @Test
    public void questotestnonfunziona(){
        System.out.println("");
        System.out.println("4) trovaIpTraDateEdOre()");
        System.out.println("");
        GestioneDataOra gestioneDataOra = new GestioneDataOra();
        clientRepository.cancellaTutti();
        clientRepository.inserisciClientConData(
                new Client(
                        0L,
                        "ip-da-non-conteggiare-1",
                        gestioneDataOra.dataOraDaStringa(
                                "2000-12-01 05 +02:00",
                                "yyyy-MM-dd HH ZZZZZ"
                        )
                )
        );
        clientRepository.inserisciClientConData(
                new Client(
                        0L,
                        "ip-da-non-conteggiare-2",
                        gestioneDataOra.dataOraDaStringa(
                                "2000-12-01 06 +02:00",
                                "yyyy-MM-dd HH ZZZZZ"
                        )
                )
        );
        clientRepository.inserisciClientConData(
                new Client(
                        0L,
                        "ip-da-non-conteggiare-3",
                        gestioneDataOra.dataOraDaStringa(
                                "2000-12-01 07 +02:00",
                                "yyyy-MM-dd HH ZZZZZ"
                        )
                )
        );
        for(int i=1; i<=5; i++){clientRepository.inserisciClient(new Client(0L,"ip-da-trovare"+i,null));}
        OffsetDateTime dataOraInizialeInclusa = gestioneDataOra.dataOraDaStringa(
                "2021-10-05 01:00:00 +02:00",
                "yyyy-MM-dd HH:mm:ss ZZZZZ");
        System.out.println(dataOraInizialeInclusa);
        OffsetDateTime dataOraFinaleEsclusa = gestioneDataOra.dataOraDaStringa(
                "3000-10-05 01:00:00 +02:00",
                "yyyy-MM-dd HH:mm:ss ZZZZZ");
        System.out.println(dataOraFinaleEsclusa);
        assertTrue(clientRepository.trovaIpTraDateEdOre(dataOraInizialeInclusa,dataOraFinaleEsclusa).equals(5L));
        clientRepository.cancellaTutti();
        assertTrue(clientRepository.numeroClient()!=null && clientRepository.numeroClient().equals(0L));
    }
Il database che ottengo è questo:

1,ip-da-non-conteggiare-1,2000-12-01 03:00:00.000000
2,ip-da-non-conteggiare-2,2000-12-01 04:00:00.000000
3,ip-da-non-conteggiare-3,2000-12-01 05:00:00.000000
4,ip-da-trovare1,2021-10-11 13:07:20.873068
5,ip-da-trovare2,2021-10-11 13:07:20.873849
6,ip-da-trovare3,2021-10-11 13:07:20.874633
7,ip-da-trovare4,2021-10-11 13:07:20.875814
8,ip-da-trovare5,2021-10-11 13:07:20.876532
Se lancio questa query con l'IDE:

SELECT COUNT(*) FROM client WHERE data >= TIMESTAMP WITH TIME ZONE '2021-10-05 01:00:00 +02:00' AND data < TIMESTAMP WITH TIME ZONE '3000-10-05 01:00:00 +02:00';
ottengo 5.
Ma se provo a fare la stessa cosa con jdbcTemplate ottengo un errore (ovviamente togliendo il try/catch).

    @Transactional(readOnly=true)
    public Long trovaIpTraDateEdOre(OffsetDateTime dataOraInizialeInclusa, OffsetDateTime dataOraFinaleEsclusa) {
        try{
            String sql = "SELECT COUNT(*) FROM client WHERE " +
                    "data >= TIMESTAMP WITH TIME ZONE ? AND data < TIMESTAMP WITH TIME ZONE ?;";
            int[] tipi = {Types.TIMESTAMP_WITH_TIMEZONE, Types.TIMESTAMP_WITH_TIMEZONE};
            return jdbcTemplate.queryForObject(sql, Long.class, tipi);
        }catch (Exception e){
            return 0L;
        }
    }

org.springframework.dao.DataIntegrityViolationException: PreparedStatementCallback; SQL [SELECT COUNT(*) FROM client WHERE data >= TIMESTAMP WITH TIME ZONE ? AND data < TIMESTAMP WITH TIME ZONE ?;]; Nessun valore specificato come parametro 2.; nested exception is org.postgresql.util.PSQLException: Nessun valore specificato come parametro 2.
Perché il metodo trovaIpTraDateEdOre() fallisce?
Dove sbaglio?

Io ho capito questo (correggetemi se sbaglio):
'TIMESTAMP WITH TIME ZONE' corrisponde a 'OffsetDateTime' ma usa come fuso orario '+00:00' ovvero l'ora UTC. In pratica se l'oggetto OffsetDateTime trasmette al DBMS come orario 15:07 ed ha un fuso orario impostato su "Europe/Rome" ovvero +02:00, in PostgreSQL l'ora diventa 13:07 ovvero 15:07 +02:00. In altri periodi dell'anno, in Italia, quando il fuso orario cambia +02:00 diventa +01:00.
'CURRENT_TIMESTAMP' prende l'ora dal server su cui è installato PostgreSQL. Quando sono in locale e quindi in sviluppo PostgreSQL preleva dal mio OS l'ora 15:07, toglie 2 ore di fuso orario sulla base della data odierna e salva nel DBMS l'ora UTC ovvero quella priva di fuso orario o con fuso orario nullo.
L'oggetto in arrivo da PostgreSQL con jdbcTemplate è Timestamp e va convertito in OffsetDateTime mentre quello fornito a jdbcTemplate deve essere OffsetDateTime.
Ho anche provato a fornire per logica a jdbcTemplate un Timestamp ma non ho risolto (ottengo lo stesso errore).

public class Client {

    private Long id;
    private String ip;
    private OffsetDateTime data;

    public Client() {
    }

    public Client(Long id, String ip, OffsetDateTime data) {
        this.id = id;
        this.ip = ip;
        this.data = data;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public OffsetDateTime getData() {
        return data;
    }

    public void setData(OffsetDateTime data) {
        this.data = data;
    }

}

public class ClientRowMapper implements RowMapper<Client> {

    GestioneDataOra gestioneDataOra = new GestioneDataOra();

    @Override
    public Client mapRow(ResultSet rs, int rowNum) throws SQLException {
        Client client = new Client();
        client.setId(rs.getLong("id"));
        client.setIp(rs.getString("ip"));
        client.setData(gestioneDataOra.daTimestampAdOffsetDateTime(rs.getTimestamp("data")));
        return client;
    }

}

    public OffsetDateTime daTimestampAdOffsetDateTime(Timestamp timestamp){
        try{
            OffsetDateTime offsetDateTime = OffsetDateTime.ofInstant(timestamp.toInstant(), ZoneId.of(zona));
            return offsetDateTime;
        }catch (Exception e){
            return null;
        }
    }

8 Risposte

  • Re: Non riesco a scrivere una semplice query in jdbcTemplate

    iBaffiPro ha scritto:


    Ma se provo a fare la stessa cosa con jdbcTemplate ottengo un errore (ovviamente togliendo il try/catch).
    
        @Transactional(readOnly=true)
        public Long trovaIpTraDateEdOre(OffsetDateTime dataOraInizialeInclusa, OffsetDateTime dataOraFinaleEsclusa) {
            try{
                String sql = "SELECT COUNT(*) FROM client WHERE " +
                        "data >= TIMESTAMP WITH TIME ZONE ? AND data < TIMESTAMP WITH TIME ZONE ?;";
                int[] tipi = {Types.TIMESTAMP_WITH_TIMEZONE, Types.TIMESTAMP_WITH_TIMEZONE};
                return jdbcTemplate.queryForObject(sql, Long.class, tipi);
            }catch (Exception e){
                return 0L;
            }
        }
    
    Ma a me pare abbastanza ovvio ..... nel sql ci sono 2 "?" e il metodo riceve 2 parametri. Dove li usi??
  • Re: Non riesco a scrivere una semplice query in jdbcTemplate

    Penso anche io che sia quello il problema ma non so come risolvere.
    Quello che non vorrei fare è convertire quei OffsetDateTime in String.
    Ho provato questo:
    
        @Transactional(readOnly=true)
        public Long trovaIpTraDateEdOre(OffsetDateTime dataOraInizialeInclusa, OffsetDateTime dataOraFinaleEsclusa) {
            try{
                String sql = "SELECT COUNT(*) FROM client WHERE " +
                        "data >= TIMESTAMP WITH TIME ZONE ? AND data < TIMESTAMP WITH TIME ZONE ?;";
                return jdbcTemplate.queryForObject(sql, Long.class, dataOraInizialeInclusa, dataOraFinaleEsclusa);
            }catch (Exception e){
                return 0L;
            }
        }
    
    e altri ma non so come va scritto il metodo in jdbcTemplate.
  • Re: Non riesco a scrivere una semplice query in jdbcTemplate

    iBaffiPro ha scritto:


    Penso anche io che sia quello il problema ma non so come risolvere.
    
                return jdbcTemplate.queryForObject(sql, Long.class, dataOraInizialeInclusa, dataOraFinaleEsclusa);
    
    Ora hai usato il

    queryForObject(String sql, Class<T> requiredType, Object... args)

    che va bene.

    iBaffiPro ha scritto:


    Quello che non vorrei fare è convertire quei OffsetDateTime in String.
    No, non devi fare conversioni in string. In JDBC il OffsetDateTime è già un TIMESTAMP WITH TIMEZONE.

    https://jdbc.postgresql.org/documentation/head/8-date-time.html

    Non dovrebbe nemmeno essere necessario nel SQL mettere "TIMESTAMP WITH TIME ZONE" (ammesso comunque che sia corretto come sintassi ... non ricordo ora).

    P.S. comunque scusa ma: "trovaIpTraDateEdOre" come nome e poi fai un COUNT(*), quindi cosa ti aspetti? un Ip o un conteggio??
  • Re: Non riesco a scrivere una semplice query in jdbcTemplate

    Mi aspetto un numero ovvero il numero di record che hanno una data compresa tra dataOraInizialeInclusa e dataOraFinaleEsclusa.
    Si sarebbe più corretto trovaNumeroDiIpTraDateEdOre(), hai ragione.
  • Re: Non riesco a scrivere una semplice query in jdbcTemplate

    Comunque anche il secondo codice fallisce:
    
        @Transactional(readOnly=true)
        public Long trovaIpTraDateEdOre(OffsetDateTime dataOraInizialeInclusa, OffsetDateTime dataOraFinaleEsclusa) {
            String sql = "SELECT COUNT(*) FROM client WHERE " +
                    "data >= TIMESTAMP WITH TIME ZONE ? AND data < TIMESTAMP WITH TIME ZONE ?;";
            return jdbcTemplate.queryForObject(sql, Long.class, dataOraInizialeInclusa, dataOraFinaleEsclusa);
        }
    
    
    org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar [SELECT COUNT(*) FROM client WHERE data >= TIMESTAMP WITH TIME ZONE ? AND data < TIMESTAMP WITH TIME ZONE ?;]; nested exception is org.postgresql.util.PSQLException: ERRORE: errore di sintassi a o presso "$1"
    
    sembra che la mia grammatica sql sia sbagliata ma questa:
    
    SELECT COUNT(*) FROM client WHERE data >= TIMESTAMP WITH TIME ZONE '2021-10-05 01:00:00 +02:00' AND data < TIMESTAMP WITH TIME ZONE '3000-10-05 01:00:00 +02:00';
    
    restituisce 5!
  • Re: Non riesco a scrivere una semplice query in jdbcTemplate

    iBaffiPro ha scritto:


    
            String sql = "SELECT COUNT(*) FROM client WHERE " +
                    "data >= TIMESTAMP WITH TIME ZONE ? AND data < TIMESTAMP WITH TIME ZONE ?;";
    
    org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar [SELECT COUNT(*) FROM client WHERE data >= TIMESTAMP WITH TIME ZONE ? AND data < TIMESTAMP WITH TIME ZONE ?;]; nested exception is org.postgresql.util.PSQLException: ERRORE: errore di sintassi a o presso "$1"
    String sql = "SELECT COUNT(*) FROM client WHERE data >= ? AND data < ?";
    E il ";" finale nel sql non serve in JDBC.

    (l'ho già detto prima: OffsetDateTime -> Types.TIMESTAMP_WITH_TIMEZONE)
  • Re: Non riesco a scrivere una semplice query in jdbcTemplate

    Sapevo che avresti risolto! Bravissimo!
    Ma... sono sconcertato...
    Sono assolutamente certo di aver provato questa configurazione ieri sera:
    
        @Transactional(readOnly=true)
        public Long trovaIpTraDateEdOre(OffsetDateTime dataOraInizialeInclusa, OffsetDateTime dataOraFinaleEsclusa) {
            String sql = "SELECT COUNT(*) FROM client WHERE data >= ? AND data < ?;";
            return jdbcTemplate.queryForObject(sql, Long.class, dataOraInizialeInclusa, dataOraFinaleEsclusa);
        }
    
    e non andava, oggi invece si.
    Forse sto diventando matto...
    Comunque funziona anche con il punto è virgola finale (;). Il problema è questo "TIMESTAMP WITH TIME ZONE" di troppo che ho aggiunto perché il codice sopra non andava. Guarda non so cosa dire sono sconcertato...
    Grazie infinite per il supporto!
    Mi consigli di togliere tutti i punti e virgola da tutte le query in tutte le classi Repository?
  • Re: Non riesco a scrivere una semplice query in jdbcTemplate

    iBaffiPro ha scritto:


    Sono assolutamente certo di aver provato questa configurazione ieri sera:
    Non so .... può essere che avevi usato il queryForObject non appropriato, come si è visto prima all'inizio (?)

    iBaffiPro ha scritto:


    Mi consigli di togliere tutti i punti e virgola da tutte le query in tutte le classi Repository?
    Sì assolutamente, puoi togliere tutti i ";" finali. In JDBC non servono.
Devi accedere o registrarti per scrivere nel forum
8 risposte