JPA funziona con H2 ma non con MySQL

di il
20 risposte

JPA funziona con H2 ma non con MySQL

Sto studiando un'applicazione realizzata in Spring Data JPA. Vi mostro qui di seguito lo script:
https://github.com/Apress/beg-spring-boot-2/tree/master/chapter-08/springboot-jpa-demo
Se uso H2 fila tutto liscio come l'olio ma se uso MySQL 8.0.21 ottengo questo errore:
java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test
Pensando ad una dimenticanza dell'autore edito il codice in questo modo:
@SpringBootTest(classes=SpringbootJPADemoApplication.class)
ma ottengo ancora errori:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.apress.demo.SpringbootJPADemoApplicationTests': Unsatisfied dependency expressed through field 'userRepository'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.apress.demo.UserRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:587)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:91)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:373)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1344)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:400)
	at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:118)
	at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
	at org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener.prepareTestInstance(SpringBootDependencyInjectionTestExecutionListener.java:44)
	at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:242)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
	at org.junit.runners.Suite.runChild(Suite.java:128)
	at org.junit.runners.Suite.runChild(Suite.java:27)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.apress.demo.UserRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1509)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1104)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1065)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:584)
	... 36 more
Ovviamente per eseguire i test edito il pom.xml aggiungendo la versione del mio database:
<mysql.version>8.0.21</mysql.version>
Il mio file application-prod.properties va anche modificato per adattarlo alla mia versione/installazione del database MySQL:

logging.level.org.springframework=DEBUG
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC&createDatabaseIfNotExist=true
spring.datasource.username=root
spring.datasource.password=admin
spring.datasource.initialization-mode=always
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
Perché nonostante tutti questi accorgimenti l'applicazione non funziona?
Dove sbaglio?
La webapp contiene errori?
grazie anticipatamente

20 Risposte

  • Re: JPA funziona con H2 ma non con MySQL

    Innanzitutto non so se hai notato una cosa: c'è un test @Ignore, è il createUser(). C'è un motivo per cui l'ha messo ignored. Se togli @Ignore, anche solo con il H2 embedded questo test fallisce! Perché? Semplice, nel data.sql ha inserito dei dati con id "schiantati" 1, 2, 3. Ma quando fa il save di quel nuovo user Paul, va a prendere il id dalla sequence, che prende 1 e quindi va a cozzare contro il primo record nel data.sql.

    Se provi a togliere tutte le insert dal data.sql, succede che tutti i test potrebbero passare. C'è un motivo per il "potrebbero". Quei test sono interdipendenti tra di loro! Questa è una situazione che NON si deve realizzare. Ma probabilmente l'autore non voleva mettere troppa carne al fuoco.

    Negli unit test, l'ordine dei test NON è (facilmente) predicibile (e NON è l'ordine in cui sono disposti i metodi @Test !!).
    Nella prova che ho fatto, il createUser() viene fatto prima del findAllUsers(). Se il data.sql NON inserisce dati, il createUser() ne crea uno e quindi poi il findAllUsers() non fallisce. Se l'ordine fosse il contrario (prima findAllUsers() poi createUser() ), il findAllUsers() fallirebbe perché si aspetta "not empty".

    Insomma, ciascun unit-test dovrebbe sempre essere "isolato" dagli altri. Vale in generale. Nel caso però di unit-test che vanno su db (anche in-memory) .... c'è da stare molto attenti.


    P.S. per il MySQL vedo domani ...
  • Re: JPA funziona con H2 ma non con MySQL

    Grazie Andbin, non avevo notato che i test non fossero eseguiti in ordine, pure IntelliJ me lo suggeriva ma non ci facevo caso.
    Ho scoperto che esiste però:
    @FixMethodOrder(MethodSorters.NAME_ASCENDING)
    Aggiungendo un prefisso ai metodi risolvo (Esempio: "Test_03_createUser()").
    Tra il resto l'IDE ha anche due pulsanti che permette di specificare l'ordine di esecuzione dei Test ma cerco di starne il più possibile alla larga. Per me l'IDE non deve occuparsi anche di questo.

    In merito al discorso id hai anche ragione e qui invece me ne sarei dovuto accorgere. Ho anche notato che se scrivo:
    
    insert into users(name , email , disabled) values('John' , 'john@gmail.com', false);
    insert into users(name , email , disabled) values('Rod' , 'rod@gmail.com' , false);
    insert into users(name , email , disabled) values('Becky' , 'becky@gmail.com' , true);
    
    I test con H2 falliscono tutti quanti. Perché accade questo? Spring Boot non crea lo schema della tabella users leggendo l'oggetto User.java? JPA non dovrebbe creare un campo id che si auto incrementa in modo automatico?
    Ho anche notato che IntelliJ mi segna questo errore in rosso:
    @Table(name="USERS")
    Questo problemino però c'è sempre stato, non è una novità. IntelliJ mi scrive:
    Cannot resolve table 'USERS'
    Inspection info: This inspection controls whether the Persistence ORM annotations are checked against configured Datasources
    ma non dovrebbe essere un problema tale da compromettere il funzionamento dell'app. Sembra sia necessario aggiungere un datasources in IntelliJ per risolvere. Al momento non me ne preoccupo, vedo di risolvere in un secondo momento.
    Inoltre se sostituisco @SpringBootTest(classes=SpringbootJPADemoApplication.class) con @DataJpaTest ed uso data.sql originale, quello che assegna gli id, posso rimuovere @Ignore al metodo createUser(), perché accade questo?
  • Re: JPA funziona con H2 ma non con MySQL

    giannino1995 ha scritto:


    non avevo notato che i test non fossero eseguiti in ordine
    Sì in generale è così. Ed è anche ben scritto nella documentazione di JUnit. E normalmente va bene così, perché di norma i test dovrebbero essere isolati, indipendenti. Non dipendenti tra di loro.

    giannino1995 ha scritto:


    Ho scoperto che esiste però:
    @FixMethodOrder(MethodSorters.NAME_ASCENDING)
    Sì è una cosa solo da JUnit 4.11 in avanti, tra l'altro. E non andrebbe usata, salvo situazioni davvero particolari/importanti (e non è questo che stiamo discutendo).

    giannino1995 ha scritto:


    ma cerco di starne il più possibile alla larga. Per me l'IDE non deve occuparsi anche di questo.
    Infatti, no. Tra l'altro in contesti aziendali, se non si è sul PC di uno sviluppatore che ha l'IDE aperto, i test spesso/tipicamente vengono lanciati da prompt o da sistemi automatici (es. Jenkins). Insomma fuori da un IDE. Quindi l'IDE non deve c'entrare/influire ....

    giannino1995 ha scritto:


    Ho anche notato che se scrivo:
    
    insert into users(name , email , disabled) values('John' , 'john@gmail.com', false);
    insert into users(name , email , disabled) values('Rod' , 'rod@gmail.com' , false);
    insert into users(name , email , disabled) values('Becky' , 'becky@gmail.com' , true);
    
    I test con H2 falliscono tutti quanti. Perché accade questo? Spring Boot non crea lo schema della tabella users leggendo l'oggetto User.java? JPA non dovrebbe creare un campo id che si auto incrementa in modo automatico?
    Allora facciamo una premessa. Con altri database, ad esempio PostgreSQL, si può creare una cosa del genere:
    CREATE SEQUENCE users_id_seq;
    
    CREATE TABLE users (
        id INTEGER NOT NULL DEFAULT NEXTVAL('users_id_seq'),
        ......
        ......
    );
    Ossia la sintassi di PostgreSQL permette di creare la colonna id in modo che il default sia, concettualmente: "stacca un numero dalla sequence xyz".
    Quindi la colonna SA di dover prendere il nextval dalla sequence se id non viene menzionato/settato esplicitamente.

    Ora, se tu avessi guardato attentamente il log del SQL per H2 (che c'è per via del spring.jpa.show-sql=true), avresti visto:

    Hibernate: drop table users if exists
    Hibernate: drop sequence if exists hibernate_sequence
    Hibernate: create sequence hibernate_sequence start with 1 increment by 1
    Hibernate: create table users (id integer not null, disabled boolean not null, email varchar(255) not null, name varchar(255) not null, primary key (id))
    Hibernate: alter table users add constraint UK_6dotkott2kjsp8vw4d0m25fb7 unique (email)

    Dove sta scritto nella definizione di users che la colonna id dovrebbe staccare il numero da hibernate_sequence?? Infatti ... NON c'è!
    Lo "sa" solo Hibernate che prima di una insert deve staccare il numero da hibernate_sequence.

    Ma se tu fai una insert "secca" da script SQL così:

    insert into users(name , email , disabled) values('John' , 'john@gmail.com', false)

    già questa insert fallisce (lo VEDI dal log):

    Caused by: org.h2.jdbc.JdbcSQLException: NULL not allowed for column "ID"; SQL statement:
    insert into users(name , email , disabled) values('John' , 'john@gmail.com', false)

    E questo fa fallire tutto il setup del contesto di Spring: non crea il EntityManagerFactory, non crea il TransactionManager, ecc... E tutti i test falliscono di brutto.

    Ora la tua domanda potrebbe essere: si può specificare su H2 di creare la colonna id per avere come default il next dalla sequence?
    Allora: a livello di sintassi può darsi, non lo so per certo, non conosco H2 nei dettagli e bisognerebbe leggere la documentazione (te lo lascio come esercizio ...)

    Ma c'è una cosa: l'autore ha fatto l'esempio per H2 e MySQL. E in User è stato molto generico perché ha usato:

    @Id @GeneratedValue(strategy=GenerationType.AUTO)

    AUTO vuol dire in sostanza: bene ORM (Hibernate), scegli tu la strategia di generazione del ID che risulta meglio per il dialect selezionato. Stando a questo livello generico, non puoi controllare come viene definita la colonna id.

    Se vuoi c'è la annotation per specificare la sequence. Ma allora ti funzionerebbe con H2 ma NON con MySQL che non ha le sequence! Una alternativa sarebbe usare il descrittore XML (secondo specifiche JPA) per definire la colonna id, tenendo due xml da attivare uno per volta.
    Dovrei andarmi a documentare perché non mi ricordo bene dove/come si fa.

    Quindi, per cortesia: NON complicare le cose!!

    giannino1995 ha scritto:


    Ho anche notato che IntelliJ mi segna questo errore in rosso:
    @Table(name="USERS")
    Questo problemino però c'è sempre stato, non è una novità. IntelliJ mi scrive:
    Cannot resolve table 'USERS'
    Non vuol dire nulla di grave. L'annotation è giusta, serve a dire come si deve chiamare la tabella.
    @Table si usa sovente perché c'è sempre la differenza di naming tra tabelle e classi. Il nome di tabella dovrebbe essere generalmente al plurale (perché contiene più righe), es. USERS, ORDERS, ecc...
    La classe rappresenta 1 entità, quindi va messa al singolare, User, Order.

    Se non metti @Table, il ORM usa come nome di tabella esattamente il nome della classe: User .... che non è il massimo. Tutto qui.
  • Re: JPA funziona con H2 ma non con MySQL

    Bravissimo! Mille grazie!

    Da quello che scrivi sembra che esistano 2 tecniche per generare l'ID di un database, il primo con le sequenze, una specie di macchinetta che ti fornisce un id nuovo ogni volta che ti serve ed un secondo che calcola l'id più piccolo disponibile ogni volta. Anche questa volta hai individuato il problema, infatti se butto via il file data.sql ed aggiungo questo metodo:
    
    	@Test
    	public void Test_00_createRecordBase() {
    		User user = new User(null, "John", "john@gmail.com");
    		User savedUser = userRepository.save(user);
    		user = new User(null, "Rod", "rod@gmail.com");
    		savedUser = userRepository.save(user);
    		user = new User(null, "Becky", "becky@gmail.com");
    		savedUser = userRepository.save(user);
    		List<User> users = userRepository.findAll();
    		assertNotNull(users);
    		assertTrue(!users.isEmpty());
    	}
    
    tutti i test passano, sia con H2 che con MySQL.
    C'è però un nuovo problema che mi manda in tilt, se lancio i test più e più volte mi accorgo che il campo email è formato da valori non unici in difformità a quanto scritto nel modello User.java. Ho cancellato il DBMS e rilanciato i test ma non risolvo. Perché JPA non crea lo schema corretto? Perché JPA non setta ad unico il campo email neppure quando chiedo all'ORM di costruire il database per la prima volta?
  • Re: JPA funziona con H2 ma non con MySQL

    giannino1995 ha scritto:


    Da quello che scrivi sembra che esistano 2 tecniche per generare l'ID di un database, il primo con le sequenze, una specie di macchinetta che ti fornisce un id nuovo ogni volta che ti serve ed un secondo che calcola l'id più piccolo disponibile ogni volta.
    A dire il vero ... esistono diverse strategie.
    Basta che guardi la enum GenerationType: https://javaee.github.io/javaee-spec/javadocs/javax/persistence/GenerationType.html

    giannino1995 ha scritto:


    se lancio i test più e più volte mi accorgo che il campo email è formato da valori non unici in difformità a quanto scritto nel modello User.java.
    Se usi H2, è in-memory. Se non si applica il data.sql (o è vuoto), il db è inizialmente vuoto e durante questa sessione di test, puoi inserire solo email uniche, altrimenti schianta. Se rilanci i test, riparte da capo e non è un problema (finché non metti tu email duplicati).

    Con MySQL il db è persistente. Quindi se non si ricrea/pulisce il db, alla seconda volta ... boom.
  • Re: JPA funziona con H2 ma non con MySQL

    E invece no! E' questo che mi manda in tilt!
    Ti rispiego, ho un DBMS persistente su MySQL il cui campo email contiene dei duplicati (ovviamente dopo aver premuto due volte su 'Run All Tests'):
    Immagine.png
    Immagine.png

    Ma in User.java ho questo:
    
    @Column(nullable=false, unique=true)
    private String email;
    
    Quando rieseguo i test con MySQL per la seconda volta non dovrebbe accadere che Hibernate riscriva per la seconda volta gli stessi record ma dovrebbe bloccarsi!
    Come risolvo?
    Se vado a vedere lo schema del DBMS mi accorgo che manca UNIQUE (email). Perché Hibernate non aggiunge la specifica?
    Ecco il dump:
    DROP TABLE IF EXISTS `users`;
    
    CREATE TABLE `users` (
      `id` int NOT NULL,
      `disabled` bit(1) NOT NULL,
      `email` varchar(255) NOT NULL,
      `name` varchar(255) NOT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
    
    LOCK TABLES `users` WRITE;
    
    INSERT INTO `users` VALUES (1,_binary '\0','john@gmail.com','John'),(2,_binary '\0','rod@gmail.com','Rod'),(3,_binary '\0','becky@gmail.com','Becky'),(4,_binary '\0','paul@gmail.com','Paul'),(5,_binary '\0','john@gmail.com','John'),(6,_binary '\0','rod@gmail.com','Rod'),(7,_binary '\0','becky@gmail.com','Becky'),(8,_binary '\0','paul@gmail.com','Paul');
    
    `email` varchar(255) NOT NULL,

    Andbin è probabile che la causa sia ?
  • Re: JPA funziona con H2 ma non con MySQL

    giannino1995 ha scritto:


    Se vado a vedere lo schema del DBMS mi accorgo che manca UNIQUE (email). Perché Hibernate non aggiunge la specifica?
    Sì è quello. Se la definizione non ha lo unique ... vuol dire che le email sono duplicabili (ovviamente).

    Ora ti faccio una "domandona": la tabella users ce l'avevi già da prima??? Magari dal springboot-mybatis-demo dove faceva la create user SENZA lo unique su email (perché non serviva in quel capitolo) ???

    Su dai ....


    C'è però una cosa che devo verificare. Nel springboot-jpa-demo nel application-prod.properties c'è

    spring.jpa.hibernate.ddl-auto=update

    "update" vuol dire che Hibernate dovrebbe confrontare lo schema (se già esiste) con la/e entity e aggiornare lo schema dove/come necessario. Quindi perché non "vede" che in User la email ha unique=true e aggiunge (se necessario) il constraint??

    Verificherò ....
  • Re: JPA funziona con H2 ma non con MySQL

    No il DBMS non è più lo stesso. Ho fatto tasto destro su 'test' e premuto 'Drop Schema' poi ho rilanciato due volte l'applicazione ma il campo email non viene mai settato come unico.
    Immagine.png
    Immagine.png


    Si uso 'update' nel .properties:
    
    logging.level.org.springframework=DEBUG
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC&createDatabaseIfNotExist=true
    spring.datasource.username=root
    spring.datasource.password=admin
    spring.datasource.initialization-mode=always
    spring.jpa.hibernate.ddl-auto=update
    spring.jpa.show-sql=true
    
  • Re: JPA funziona con H2 ma non con MySQL

    Avevo la tabella users sul mio MySQL ancora dalla prova con springboot-mybatis-demo. Quindi la tabella users aveva 3 colonne (mancava disabled).

    Ho lanciato il test "prod" nel springboot-jpa-demo e la prima cosa che ho notato dal log:

    Hibernate: create table hibernate_sequence (next_val bigint) engine=MyISAM
    Hibernate: insert into hibernate_sequence values ( 1 )
    Hibernate: alter table test.users add column disabled bit not null

    Ha creato una tabella simil-sequence (MySQL NON ha le vere sequence) e poi ha aggiunto la colonna disabled. Ma NON ha cambiato altro, non ha aggiunto alcun constraint alla colonna email.
    Da quanto ho letto, è un limite della modalità "update". E tra l'altro "update" è assolutamente sconsigliato infatti in un ambiente di "produzione" reale.


    Ho droppato le tabelle in test (NON ho droppato il db test) e ho rilanciato il test. Dal log vedo:

    Hibernate: create table hibernate_sequence (next_val bigint) engine=MyISAM
    Hibernate: insert into hibernate_sequence values ( 1 )
    Hibernate: create table users (id integer not null, disabled bit not null, email varchar(255) not null, name varchar(255) not null, primary key (id)) engine=MyISAM
    Hibernate: alter table users drop index UK_6dotkott2kjsp8vw4d0m25fb7
    Hibernate: alter table users add constraint UK_6dotkott2kjsp8vw4d0m25fb7 unique (email)

    Quindi ha ricreato la tabella users e poi ha aggiunto il constraint di unique.

    Pertanto: a me "quadra" tutto.
  • Re: JPA funziona con H2 ma non con MySQL

    Sempre grazie, gentilissimo come sempre!

    Ho fatto come dici ma non ha funzionato. Ho provato ad impostare unique al campo email con Workbench e neppure lui lo ha fatto ma mi ha fornito un messaggio di errore che mi ha permesso di risolvere. Ho provato a lanciare questo ed ho finalmente visto lo script funzionare:
    
    DROP DATABASE IF EXISTS test;
    CREATE DATABASE test 
    CHARACTER SET = 'utf8'
    COLLATE = 'utf8_general_ci';
    
    Se non mi hai nascosto nulla credo che il tuo script funzioni con MySQL perché tu durante l'installazione del DBMS hai settato il charset appropriato oppure lo hai fatto in seguito. In realtà il codice è mal scritto, sottintende che MySQL usi UTF-8 ma non è assolutamente detto.
    Ho anche provato a modificare application-prod.properties in questo modo:
    
    logging.level.org.springframework=DEBUG
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC&createDatabaseIfNotExist=true&useUnicode=yes&characterEncoding=UTF-8
    spring.datasource.username=root
    spring.datasource.password=admin
    spring.datasource.initialization-mode=always
    spring.jpa.hibernate.ddl-auto=update
    spring.jpa.show-sql=true
    spring.jpa.properties.hibernate.connection.CharSet=UTF-8
    spring.jpa.properties.hibernate.connection.characterEncoding=UTF-8
    spring.jpa.properties.hibernate.connection.useUnicode=true
    
    ma non ho risolto. Non riesco a far impostare ad Hibernate il charset giusto. Tu sapresti come fare?
    Posso chiederti il codice che usi per application-prod.properties ed application-properties?
    Con H2 ho lo stesso problema ma credo che anche tu lo abbia e penso che la ragione sia la stessa (H2 non sta usando UTF-8). Ho aggiornato in questo modo:
    
    logging.level.org.springframework=INFO
    spring.jpa.show-sql=true
    spring.jpa.properties.hibernate.connection.CharSet=UTF-8
    spring.jpa.properties.hibernate.connection.characterEncoding=UTF-8
    spring.jpa.properties.hibernate.connection.useUnicode=true
    
    ma nulla da fare. I test su H2 funzionano all'infinito ed il campo email continua ad accettare email già esistenti.
  • Re: JPA funziona con H2 ma non con MySQL

    In realtà il discorso dei caratteri non è chiarissimo per me ma me lo spiego in questo modo (correggimi se sbaglio):
    Se per il campo 'email' si sceglie VARCHAR(255) e si sceglie ad esempio 'utf8mb4' come tipologia di carattere (carset) servono 1020 byte (255 caratteri x 4 byte/carattere =1020). 'utf8mb4' è molto completo ma occupa al contempo molto spazio (4 bytes per carattere). Di default il limite di MySQL per ogni VARCHAR(255) è 767 bytes. Se si vuole usare 'utf8mb4' si deve aumentare la dimensione massima di bytes da assegnare al VARCHAR(255) che può arrivare fino a 3072 bytes, usare un carset meno dispendioso come il comune 'utf8' oppure un VARCHAR più piccolo per il campo 'email' come ad esempio VARCHAR(191). Ogni carset ha una sua serie di COLLATION. La COLLATION, definita in fase di progettazione dello schema di un DBMS, stabilisce invece una regola che consente di confrontare le stringhe.
    DOMANDE
    1) Quello che non mi è chiaro sono le ragioni del perché la dichiarazione di unicità faccia eccedere il normale limite di 767 bytes e come settare nel database 3072 byte come impostazione predefinita.
    2) Altra curiosità è capire se impostando 3072 byte il mio database rischi di occupare troppa memoria e quindi sia di norma una pratica da evitare.
    3) Altra curiosità è se, in generale e quindi per non solo riferendosi al caso dell'email, usare 'utf8' sia un carset sufficiente (siti web in Europa ed America, non Asia o Russia).
  • Re: JPA funziona con H2 ma non con MySQL

    giannino1995 ha scritto:


    Se non mi hai nascosto nulla credo che il tuo script funzioni con MySQL perché tu durante l'installazione del DBMS hai settato il charset appropriato oppure lo hai fatto in seguito.
    In realtà non ho settato nulla esplicitamente, mi pare di aver lasciato il default indicato dall'installer (ma non ricordo di preciso al 100%).
    Comunque nel mio MySQL tra le sue variabili c'è:

    character_set_connection = utf8mb4
    collation_connection = utf8mb4_0900_ai_ci

    giannino1995 ha scritto:


    ma nulla da fare. I test su H2 funzionano all'infinito
    Cosa vuol dire all'infinito?? E' un database in-memory .... terminati tutti i test il db sparisce! Se non sei tu che nel unit-test A inserisci una email X e in un altro unit-test B inserisci di nuovo la email X .... non ti schianta.

    giannino1995 ha scritto:


    Se per il campo 'email' si sceglie VARCHAR(255) e si sceglie ad esempio 'utf8mb4' come tipologia di carattere (carset) servono 1020 byte (255 caratteri x 4 byte/carattere =1020). 'utf8mb4' è molto completo ma occupa al contempo molto spazio (4 bytes per carattere).
    Ma cosa dici?! Ma dove sta scritto?!!

    Allora UTF-8, per sua definizione è solo un formato di rappresentazione del Unicode e rappresenta i caratteri con un numero VARIABILE di byte, da 1 a N byte (N mi pare sia 6 come numero massimo teorico, se non sbaglio).

    Il carattere " a " (ASCII) occupa 1 byte in UTF-8
    Il carattere " è " (e accento grave) occupa 2 byte in UTF-8
    Il carattere " ? " ("gatto" in cinese) occupa 3 byte in UTF-8
    ecc...

    utf8mb4 vuol dire: UTF-8 ma con AL MASSIMO 4 byte.

    giannino1995 ha scritto:


    Di default il limite di MySQL per ogni VARCHAR(255) è 767 bytes. Se si vuole usare 'utf8mb4' si deve aumentare la dimensione massima di bytes da assegnare al VARCHAR(255) che può arrivare fino a 3072 bytes, usare un carset meno dispendioso come il comune 'utf8' oppure un VARCHAR più piccolo per il campo 'email' come ad esempio VARCHAR(191). Ogni carset ha una sua serie di COLLATION. La COLLATION, definita in fase di progettazione dello schema di un DBMS, stabilisce invece una regola che consente di confrontare le stringhe.
    DOMANDE
    1) Quello che non mi è chiaro sono le ragioni del perché la dichiarazione di unicità faccia eccedere il normale limite di 767 bytes e come settare nel database 3072 byte come impostazione predefinita.
    2) Altra curiosità è capire se impostando 3072 byte il mio database rischi di occupare troppa memoria e quindi sia di norma una pratica da evitare.
    3) Altra curiosità è se, in generale e quindi per non solo riferendosi al caso dell'email, usare 'utf8' sia un carset sufficiente (siti web in Europa ed America, non Asia o Russia).
  • Re: JPA funziona con H2 ma non con MySQL

    Allora perché se lascio CHARSET=utf8mb4 ottengo "#1071 - Specified key was too long; max key length is 767 bytes" se tento di impostare email come UNIQUE?

    Perché se imposto CHARSET=utf8 riesco a settare UNIQUE su email sia da Spring Boot che da Workbench?
  • Re: JPA funziona con H2 ma non con MySQL

    giannino1995 ha scritto:


    Allora perché se lascio CHARSET=utf8mb4 ottengo "#1071 - Specified key was too long; max key length is 767 bytes" se tento di impostare email come UNIQUE?

    Perché se imposto CHARSET=utf8 riesco a settare UNIQUE su email sia da Spring Boot che da Workbench?
    https://stackoverflow.com/questions/1814532/1071-specified-key-was-too-long-max-key-length-is-767-bytes
Devi accedere o registrarti per scrivere nel forum
20 risposte