[]

  1. Differenze con .NET
  2. Java Fundamental
  3. Maven
  4. Apache Tomcat
  5. Spring Fundamental
  6. Spring Ioc Container
  7. Spring Librerie
  8. Spring REST API
  9. Spring Data
  10. ORM Hibernate
  11. Hibernate – Problematiche di produzione
  12. Risorse

Differenze con .NET

  • .dll (IL) = .jar (byte code) (contiene file .class che sono il risultato della compilazione di file .java)
  • ..JAR (Java Archive)
    • Puo’ essere usato come un’alternativa a .zip
    • Spesso serve per distribuire versioni binarie delle librerie (file .class compressi) ed eventuali file “resources” aggiuntivi da esse utilizzati.
    • E’ possibile creare un file .jar tramite il comando jar o tool come Maven
      • //tipica struttura di une file JAR
        META-INF/ (cartella privata)
           MANIFEST.MF  //puo' contenere metadata aggiuntivi sui file presenti nel file .jar
        com/
          baeldung/
            MyApplication.class
  • .WAR (Web application Archive)
    • Sono usati per creare un package contenente applicazioni web che possono essere deploiare su qualsiasi container Servlet/JSP
      • //tipica struttura di une file WAR
        META-INF/ (cartella privata)
           MANIFEST.MF //puo' contenere metadata aggiuntivi sui file presenti nel file .war
        WEB-INF/  (cartella publica con risorse statiche, classi servlet e libraries)
           web.xml
           jsp/
              helloWorld.jsp  //Java server page
        classes/
              static/ 
              templates/
              application.properties
        lib/
           // *.jar files as libs
  • Classpath (no assembly-cache) 
    • Spesso bisogna aiutare Java a trovare tutti i file JAR che si stanno utilizzando costruendo un classpath ad ogni build o run. 

 

Java Fundamental

  • Java Environment
    • JDK (JRE + Java Compiler + Debugger + Core classes)
      • Java Development Kit.
      • Fornisce tutti i tools, gli eseguibili e le librerie richieste per compilare, debuggare e eseguire un programma Java.
      • JShell
      • E’ specifico alla piattaforma in cui si installa (Windows,Mac, Linux)
      • Da installare su un computer di sviluppo.
      • In Windows settare variabili d’ambiente
        • Crea JAVA_HOME =  C:\Program Files\Java\jdk1.8.0_202
        • Aggiungi a PATH = C:\Program Files\Java\jdk1.8.0_202\bin
      • C:>javac -version   //javac 1.8.9_291
    • JVM (equivalente della .NET CLR)
      • Java Virtual Machine.
      • JIT (Just-in-time Compiler)
      • E’ personalizzabile.
      • Virtuale perché fornisce un supporto per eseguire ogni programma Java indipendentemente dalla piattaforma.
      • Fornisce funzionalità chiave come: memory management, garbage collection, sicurezza.
      • L’installazione é specifica alla piattaforma in cui si installa (Windows,Mac, Linux).
    • JRE (JVM + Java binaries + other classes)
      • E’ l’implementazione della JVM.
      • Permette di eseguire ogni programma Java.
      • Da installare sul server di produzione.
  • Access modifier
    • Final
      • Equivalente di Final in C# 
        • - Final class  => Sealed
          - Final method => Sealed
          - Final field  => Readonly
          //Definendo una variabile locale (es: in un if-else) significa che prima di essere letta le deve essere assegnato un valore
          - local variable or method parameter => nessun equivalente in C#
  • Enum
    • //metodo classico
      public enum Color {
          green, red, yellow
      }
      
      public class Car {
         
          @Enumerated(EnumType.String)
          private Color color;
      }
      
      
      //metodo con converter
      public enum Color {
         green(1, "the car is green"),
         red(2, "the car is red"),
         yellow(3, "the car is yellow");
      
         private Integer key;
         private String description;
      
         Color(Integer key, String description) {
             this.key = key;
             this.description = description;
         }
         
      }
      
      
      public class Car {
      
         private Color color;
      }
      
      @Converter(autoApply = true)
      public class ColorAttributeConverter implements AttributeConverter<Color, Integer> {
         @Override
         public Integer convertToDatabaseColumn(Color attribute) {
            //from enum to integer before storing into DB
            return attribute != null ? attribute.getKey(): null;
         }
        
         @Override
         public Certification convertToEntityAttribute(Integer dbData) {
            return Stream.of(Color.values())
             .filter(item -> item.getKey().equals(dbData))
             .findFirst().orElse(null);
         }  
      }

 

Maven

  • Permette di fare il build di progetti JAVA.
  • Goals => E’ possibile eseguire differenti “goals” durante il build
    • Compilare il progetto
    • Creare un package (file JAR o WAR)
    • Installare delle librerie nel repository locale delle dependency.
  • In Windows settare variabili d’ambiente
    • Aggiungi a PATH = C:\apache-maven-3.9.1\bin
    • In Windows settare variabili d’ambiente
    • Crea MAVEN_HOME = C:\apache-maven-3.9.1
  • mvn -v  //version di Maven installata
    
    //Da eseguire nella stessa cartella dove é presente il file Pom.xml
    //Lancia i differenti golas definiti nel Pom.xml
    //lancia il build del nostro progetto e crea i file .class nella cartella => ./target/classes/.class 
    //[-e, visualizza dettaglio in caso di errori]
    //[-X, per abilitare il logging]
    mvn compile [-e] [-X] 
    
    // compila
    // lancia test
    // creare il package JAR nella directory ./target
    oppure
    //creare il package WAR nella directory ./
    //il nome del package é basato su <artifactId> e <version>
    mvn package
    
    //eseguo il file appena creato
    java -jar target/my-projet-1.0.0.jar
    
    //Maven gestisce un repository delle dependency in locale (.m2/repository directory) affinché il progetto possa usarle rapidamente
    //compile + test + package + copia il package nella cartella locale delle dependency.
    //cosi facendo un altro progetto puo' referenziare il nostro progetto.
    mvn install
  • Pom.xml  => File di definizione di un progetto Maven
    • <modelVersion>. POM model version (always 4.0.0).
    • <groupId>. Group or organization that the project belongs to. Often expressed as an inverted domain name.
    • <artifactId>. Name to be given to the project’s library artifact (for example, the name of its JAR or WAR file).
    • <version>. Version of the project that is being built.
    • <packaging> – How the project should be packaged. Defaults to “jar” for JAR file packaging. Use “war” for WAR file packaging.
    • <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
         <modelVersion>4.0.0</modelVersion>   //sempre 4.0.0
         <groupId>it.richlab</groupId>        //la mia organizzazione all'inverso
         <artifactId>my-project</artifactId>  //farà parte del nome del package livrabile
         <packaging>jar</packaging>           //definisce il tipo di package creato dalla compilazione 
         <version>0.1.0</version>             //farà parte del nome del package livrabile
      
         <properties>
            <maven.compiler.source>1.8</maven.compiler.source>
            <maven.compiler.target>1.8</maven.compiler.target>
          </properties>
      
         <modules>
            <module>nonrest</module>
            <module>rest</module>
         <modules>
      
         <build>
           <plugins>
             <plugin>
               <groupId>org.apache.maven.plugins</groupId>
               <artifactId>maven-shade-plugin</artifactId>
               <version>3.2.4</version>
               <executions>
                 <execution>
                   <phase>package</phase>
                   <goals>
                     <goal>shade</goal>
                   </goals>
                   <configuration>
                     <transformers>
                       <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                          <mainClass>hello.HelloWorld</mainClass>
                       </transformer>
                     </transformers>
                   </configuration>
                 </execution>
               </executions>
             </plugin>
           </plugins>
         </build>
      </project>
  • Aggiungere delle dipendenze esterne
    • Se nelle nostre classi Java si usano delle librerie esterne, queste devono essere aggiunte nel file Pom.xml nel nodo <dependencies>
    • Il nodo <scope> é opzionale se il suo valore é compile [default]
      • provided = dipendenze necessarie solo in compilazione (a runtime sono fornite dal container che esegue il codice).
      • test = dipendenze necessarie solo in compilazione e esecuzione dei test ma non in produzione.
      • compile: valore di default, la dipendenza con questo scope sarà disponibile sia  insrc/main che insrc/test
      • test: la dipendenza é disponibile solo in src/test
      • provided:  simile a compile ma la dipendenza é fornita da JDK o un container a runtime
      • runtime: la dipendenza non é richiesta in compilazione ma solo a runtime
      • system: simile a “provided” ma é necessario fornire il JAR che contiene la dipendenza specifica
        • <systemPath>path/some.jar</systemPath>
      • import: disponibile per <type>pom</type> e dovrebbe essere rimpiazzato dalla dipendenza effettiva presente in<dependencyManagement/>
    • <dependencies>
        <dependency>
          <groupId>joda-time</groupId>
          <artifactId>joda-time</artifactId>
          <version>2.9.2</version>
        </dependency>
       <dependency> 
         <groupId>joda-time</groupId>   
         <artifactId>joda-time</artifactId>   
         <version>2.9.2</version>
         <scope>test</scope>
      </dependencies>
  • Eseguire i test
    • Maven usa un plugin chiamato “surefire” per eseguire i test.
    • La configurazione di default compila e eseguie tutte le classi in src/test/java con un nome *Test.
    • Il metodo di test deve essere decorato con @Test
    • //src/test/java/hello/GreeterTest.java
      package hello; 
      
      import static org.assertj.coe.api.Assertions.assertThat;
      import static org.hamcrest.CoreMatchers.containsString; 
      import static org.junit.Assert.*; 
      import org.junit.Test; 
      
      public class GreeterTest 
      { 
        private Greeter greeter = new Greeter(); 
        
        @Test 
        public void greeterSaysHello() { 
          assertThat(greeter.sayHello(), containsString("Hello")); 
          assertThat(movie.getName()).as(description: "got bad movie").isEqualto(expected: "Rocky IV");
        } 
      }
      
      //per lanciare i test
      mvn test
  • IntelliJ IDEA 
    • Dopo una modifica a pom.xml
      • Aprire la finestra di gestione di “Maven”
        • View -> Tool Windows -> aprire la finestra di gestione di “Maven”.
      • Lanciare il reload del progetto cliccando sull’icona “refresh”.
      • Dopodiché eseguire “install”.

 

Gradle

  • Ricordarsi di aggiungere il PATH alla cartella /bin di dove è installato Gradle nella system enviroment variable PATH.
  • gradle init

 

Apache Tomcat

  • Installare Apache Tomcat 9.0.73 in locale (fare il download dell’ultima versione)
  • Configurare IntelliJ per utilizzare l’istanza appena installata
    • Run -> Edit Configurations -> + -> Aggiungere nuova configurazione -> Selezionare TomCat server
    • Posizionarsi sulla nuova voce ‘Tomcat Seerver’ -> Selezionzionare la propria applicazione
      • Tab ‘Server’ ->
        • Configure -> Indicare il path del server nel computer locale.
        • Url -> Scegliere a quale indirizzo la nostra applicazione risponde.
      • Tab ‘Deployment’ -> Selezionare ‘Artifact’ -> indicarne il path (normalmente presente nella cartella /target)
        • Se non c’é bisogna configurare il pom.xml
          • Nel file pom.xml aggiungere vicino a <artifactId> il nuovo tag <packaging>war</paclaging> se non già presente.
          • <project> 
              ...
              <artifacId>..</artifactId> 
              <packaging>war</packaging> 
              ...
            </project>
      • ServletInitializer 
        • Per packaging war é necessario aggiungere il Servlet Initializer alla nostra applicazione 
        • import org.springframework.boot.builder.SpringApplicationBuilder;
          import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
          
          public class ServletInitializer extends SpringBootServletInitializer {
          
            @Override
            protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
               return application.sources(DemoApplication.class);
            }
          }
  • Configurare il server manager
    • tomcat-users.xml
      • Aprire il file C:\apache-tomcat-9.0.73\conf\tomcat-users.xml
        
        <tomcat-users>
           <role rolename="manager-gui"/>
           <role rolename="manager-script"/>
           <user username="tomcatgui" password="tomcatgui" roles="manager-gui"/>
           <user username="tomcattext" password="tomcattext" roles="manager-script"/>
        </tomcat-users>
  • Aprire la console di management
    • http://localhost:8080/manager/html

 

Spring  Fundamental

  • Spring é un framework per Java open source con il quale é possibile creare:
    • Microservice
    • Reactive (programmazione asincrona)
    • Cloud
    • Applicazioni web
      • Organizzazione progetto web
        • Controller
          • I controller raggruppano gli end-point
          • Espongono i dto
          • Utilizzano i manager per richiamare la business logic
          • import org.springframework.beans.factory.annotation.Autowired;
            import org.springframework.web.bind.annotation.GetMapping;
            import org.springframework.web.bind.annotation.PostMapping;
            import org.springframework.web.bind.annotation.RequestParam;
            import org.springframework.web.bind.annotation.RestController;
            
            @RestController
            public class MyController {
            
              @GetMapping("/myapi")
              public myapi mypapi(){}
            
            }
        • Dto
          • Entità esposte all’esterno.
          • Spesso un sottoinsieme delle “Entity”.
        • Manager
          • I manager contengono la business logic dell’applicazione.
          • Nella creazion della business logic richiamano i diversi “service”.
          • import org.springframework.beans.factory.annotation.Autowired;
            import org.springframework.stereotype.Component;
            
            @Component
            public class MyManager {
              ..
            }
        • Service
          • I service utilizzano i repository per esporre le operazioni CRUD e di interazione con il data layer.
          • import org.springframework.stereotype.Service;
            
            @Service
            public class MyService {
              ..
            }
        • Repository (interagisce con il Dao)
          • I repository ragruppano le operazioni Dao
        • Dao (interagiste con il data layer per la persistenza dei dati, es: hiberate)
        • Entity (raggruppa il business model dell’applicazione)
      • //file system
        /src
          /test/java/<my-package-name>
          /main
             /resources
             /java/<my-package-name>
                            /controller
                            /dto
                            /manager 
                            /service
                            /repository
                            /dao
                            /entity
    • Serverless (Scale-up on demand)
    • Event Driven
    • Batch (esecuzione automatica di task)

  • Configurazione in Spring (application.properties vs application.xml)

 

      • application.properties
        • //L'uso di ‘#---' indica dove vogliamo dividere il documento
          //Nell'esempio dotto Spring interpreta 2 sezioni con profili differenti
          
          logging.file.name=myapplication.log
          bael.property=defaultValue
          
          application.servers[0].ip=127.0.0.1
          application.servers[0].path=/path1
          application.servers[1].ip=127.0.0.2
          application.servers[1].path=/path2
          
          
          #---
          spring.config.activate.on-profile=dev
          spring.datasource.password=password
          spring.datasource.url=jdbc:h2:dev
          spring.datasource.username=SA
          bael.property=devValue
          #---
          spring.config.activate.on-profile=prod
          spring.datasource.password=password
          spring.datasource.url=jdbc:h2:prod
          spring.datasource.username=prodUser
          bael.property=prodValue
      • application.xml
        • //In questo caso, l'uso di ‘---' indica dove vogliamo dividere il documento
          
          application:
            servers:
            -  ip: '127.0.0.1'
               path: '/path1'
            -  ip: '127.0.0.2'
               path: '/path2'
          
          logging:
            file:
              name: myapplication.log
          ---
          spring:
            config:
              activate:
                on-profile: dev
            datasource:
                password: 'password'
                url: jdbc:h2:dev
                username: SA
          bael:
             property: devValue
          
          ---
          spring:
            config:
              activate:
                on-profile: prod
            datasource:
              password: 'password'
              url: jdbc:h2:prod
              username: prodUser
          bael:
            property: prodValue
      • Leggere la configurazione
        • @Value
          • Questa annotazione é usata per assegnare valori di default a variabili o argomenti di metodi.
          • E’ possibile leggere variabili d’ambiente Spring (es: “application.properties”) cosi come variabili di sistema usando questa annotazione
          • //Spring Expression Language (SpEL)
            @Value({spring.datasource.url})
            privat String url;
            
            @Value("${APP_NAME_NOT_FOUND}")
            private String defaultAppName;
            
            @Value("${java.home}")
            private String javaHome;
            
            @Value("classpath:<my-folder>/<my-file>")
            private Resource my-resource;
        • Environment abstraction
          • @Autowired
            private Environment env;
            
            public String getSomeKey(){
               return env.getProperty("spring.datasource.url");
            }
        • @ConfigurationProprerties
          • @ConfigurationProperties(prefix = "mail")
            public class ConfigProperties {
               String name;
               String description;

 

Spring IoC Container 

  • @Bean
    • Un bean é un oggetto  aggiunto al IoC container di Spring. 
    • Il comando @Bean é usato per dichiarare esplicitamente un singolo “bean”.
    • Permette di “componentizzare” una classe della quale non si ha il codice sorgente.
    • Permette disaccoppaire la dichiarazione di un “bean” dalla classe che lo definisce.
    • Permette di rendere disponibile all’application context un classe di terze-parti.
  • Generazione dei bean
    • (metodo automatico) Creando una classe con l’annotazione @Component
      • Una volta che l’applicazione Spring viene eseguita, ogni classe con l’annotazione @ComponentScan fa lo scan di ognuna dele proprie classi con @Component
      • Ne fa poi il restore delle istanze nel container IoC
    • (metodo manuale) Creondo un metodo con l’annotazione @Bean
      • Le classi che contengono dei metodi che usano @Bean dovrebbero essere annotate  @Configuration.
      • L’annotazione @ComponentScanesegue anche tutti i metodi con @Bean.
      • Ne fa poi il restore delle istanze nel context dell’applicazione.
  • Classe config
    • @Configuration
      @ComponentScan(basePackageClasses = Company.class)
      public class Config {
         @Bean
         public Address getAddress() {
            return new Address("High Street", 1000);
         }
      }
  • Classe context
    • ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
  • @SpringBootApplication  = @EnableAutoConfiguration + @Configuration + @ComponentScan
    • @EnableAutoConfiguration
      • Permette di attiva/disattivare la funzionalità di auto-configurazione in un’applicazione “Spring”.
        • Dice a Spring Boot di iniziare ad aggiungere i beans sulla base dei “classpath” configurati nel file pom.xml
      • Tenta infatti di configurare automaticamente la nostra applicazione in base alle dipendenze che sono state definite.
        • Per esempio se esiste una dipendenza aHSQLDB e non si é ancora definito manualemente la connessione (tramite @bean) allora Spring Boot auto configura un in-memory database.
    • @ComponentScan
      • Attiviva lo scan degli attributi @Component nel package dell’applicazione.
      • @Component
        • @Component
          class User {
          }
          
          // to get Bean
          @Autowired
          User user;
        • Puo’ essere usato per auto-detect e auto-configurigurare “beans” a partire della dipendenze definite nel progetto.
        • @Component ha diverse specializzazione come:
          • @Controller
          • @Repository
          • @Service
        • Posso definire dei “bean” senza l’uso di @Configuration.
    • @Configuration
      • Permette di registrare altri @Beans nel context o in classi di configurazione addizionali.
      • @Configuration 
        class MyConfiguration{ 
          @Bean 
          public User getUser() { 
            return new User(); 
          } 
        } 
        
        class User{ } 
        
        // Getting Bean 
        User user = applicationContext.getBean("getUser")
    • package com.example.myapplication; 
      
      import org.springframework.boot.SpringApplication; 
      import org.springframework.boot.autoconfigure.SpringBootApplication; 
      
      //@EnableAutoConfiguration 
      //@Configuration 
      //@ComponentScan 
      @SpringBootApplication 
      public class Application { 
         public static void main(String[] args) { 
           SpringApplication.run(Application.class, args); 
         } 
      }
  • Come usare la dependency injection
    • import org.springframework.beans.facotry.annotation.Autowired;
      
      public class MyClass {
         @Autowired
         MyInjectedClass injectedClass; 
      
         public MyClass(MyInjectedClass pInjectedClass) {
            this.injectedClass = pInjectedClass
         }
      }

 

Spring Librerie

  • spring-boot-starter-web 
    • contiene le seguenti dipendenze spring web:
      
      - spring-boot-starter
      - jackson
      - spring-core
      - spring-mvc
      - spring-boot-starter-tomcat
  • spring-boot-starter-tomcat
    • //contiene ogni cosa correlata ad un tomcat server embedded:
      core 
      el 
      logging (logback)
      websocket
    • Come configurare logback
      • application.properties
        • logging.level.org.springframework.web=DEBUG
          logging.level.org.hibernate=ERROR
      • Oppure aggiungere il file di configurazione di logback.xml nella cartella “resources” del nostro progetto:
        • //logback.xml
          <?xml version="1.0" encoding="UTF-8"?>
          <configuration>
            <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
              <encoder>
                <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>   
              </encoder>
            </appender>
          
            <logger name="org.hibernate.SQL" level="DEBUG" />
            <logger name="org.hibernate.type.descriptor.sql.BasicBinder level="DEBUG" />       //log params value in autogenerated Hibernate SQL statement
            <logger name="com.<my-projet>.<my-package>" level="TRACE" />                       //log <my-package>
            <logger name="org.springframework.orm.jpa.JpaTransactionManager" level="DEBUG" />  //log Hibernate transaction manager
            <logger name="org.hibernate.internal.SessionImpl" level="DEBUG" />                 //log Hibernate session manager
          
            <root level="WARN">
              <appender-ref ref="STDOUT" />
            </root>
          </configuration>
        • Loggare le query generate da Hibernate + i parametri valorizzati
          • <logger name="org.hibernate.SQL" level="DEBUG" />
            <logger name="org.hibernate.type.descriptor.sql.BasicBinder level="DEBUG" />
  • spring-boot-starter-security 
    • Permette di securizzare tutti gli endpoint by default usando
      
      httpBasic 
      oppure 
      formLogin 
  • Database
    • com.h2database.h2
      • //da usare con spring-boot-starter-data-jpa
        
        //application.yaml
        spring:
          datasource:
            url: jdbc:h2:mem:mydb
            username: sa
            password: password
            driverClassName: org.h2.Driver
          jpa:
            spring.jpa.database-platform: org.hibernate.dialect.H2Dialect
      • Precaricare dei dati in H2
        • Recuperare dalla classe di configurazione del livello di persistenza i metodi datasource() e il transactionManager()
        • Creare uno script sql con le query che si vogliono eseguire per riempiere il nostro db H2 (es: /data/fill.sql).
        • Aggiungere alla nostra classe di test le 2 seguenti annotazioni:
          • @SqlConfig(dataSource = "dataSourceH2", transactionManager = "transactionManager")
            @Sql({ "/data/fill.sql" })
            public class MyTest {
            
            }
    • spring-boot-starter-data-jdbc (JDBC)
      • Un applicazione usa le API offerte da questa libreria per comunicare con il JDBC manager
      • Permette di scrivere direttamente nell’applicazione gli statement SQL necessari per leggere o modificare i dati del DB.
      • E’ dipendente dal DB.
      • La transazione é gestita esplicitamente con l’uso di commit e rollback.
      • //Questa annotazione indica a Spring quale costruttore utilizzare per materializzare la data tabella del DB in un oggetto
        //nel caso tale oggetto abbia più di un costruttore
        @PersistenceCreator
    • spring-boot-starter-data-jpa (JPA)
      • Permette di fare bind di oggetti Java con un database relazionale. E’ uno dei possibili approcci al ORM.
      • Permette di usare delle annotzioni per describere come la data classe Java si mappa sulla data tabella.
      • La transazione é implicita.
      • Le applicazoni basate su JPA usano in effetti JDBC negli strati inferiori:
        • In altri termini JPA agisce da livello di astrazione che nasconde allo sviluppatore le chiamate interne a JDBC rendendo l’iterazione con il DB molto più semplice.
      • E’ indipendente dal DB.
      • javax.persistence package.
      • Usa Java Persistence Query Language (JPQL) per interrogare il DB.
      • Per interagire con le entity usa EntityManagerFactory. Questa restituisce un EntityManager che realizza le oerazioni CRUD sulle istanze.
      • @Entity
        @Table(name = "employee")
        public class Employee implements Serializable {
           @Column(name = "employee_name")
           private String employeeName;
        }
      • //definire relazione 1-a-molti tra la tabella "employee" e "communication"
        @Entity
        @Table(name = "employee")
        public class Employee implements Serializable {
        
          @OneToMany(mappedBy = "employee", fetch = FetchType.EAGER)
          @OrderBy("firstName asc")
          private Set communications;
        }
    • org.hibernate (Hibernate)
      • La principale differenza conn JPA è che Hibernate è un framework mentre JPA è una specifica API.
      • Hibernate è l’implementazione di tutte le linee guida JPA.
      • E’ un Object-Relational Mapping (ORM) usato per salvare oggetti Java in DB relazionale.
      • Usa Hibernate Query Language (HQL) per interrogare il DB.
      • Per interagire con le entity usa la SessionFactory. Questa restituisce un’interfaccia Session che agisce come un’interfaccia a runtime tra l’applicatione Java e Hibernate.
      • <dependency>
           <groupId>org.hibernate</groupId>
           <artifactId>hibernate-core</artifactId>
        </dependency>
        <dependency> 
           <groupId>org.springframework</groupId> 
           <artifactId>spring-orm</artifactId> 
        </dependency>
  • org.springdoc vs io.springfox
    • Per poter aggiungere automaticamente Swagger alle nostre REST API in un progetto Spring é possibile usare la vecchia libreria “springfox” oppure sostituirla con la nuova “org.springdoc” che supporta OpenAPI 3.0. 
    • SpringDoc
      • //aggiungere la libreria SpringDoc
        <dependency>
          <groupId>org.springdoc</groupId>
          <artifactId>springdoc-openapi-ui</artifactId>
          <version>1.6.4</version>
        </dependency>
        
      • URL specifiche OpenAPI 3.0 degli endpoins della nostra applicazione Spring
        • //JSon
          http://<my-url>/v3/api-docs
          
          //Yaml
          http://<my-url>/v3/api-docs.yaml 
      • Customizzazione dell’ URL
        • //Settare il nuovo path nel file "application.properties"
          springdoc.api-docs.path=<my-new-path> (es: /api-docs)
      • URL interfaccia Swagger
        • http://localhost:8080/swagger-ui.html
      • Swagger-UI properties
        • //Sempre nel file "application.properties" é possibile aggiungere delle entry per customizzare l'interfaccia Swagger
          springdoc.swagger-ui.path=/swagger-ui-custom.html
          springdoc.swagger-ui.operationsSorter=method
  • spring-boot-starter-actuator
    • In sostanza, Actuator aggiunge delle funzionalità alla nostra applicazione per monitorare le prestazioni in un ambiente di produzione.
    • Monitorare la nostra app, raccogliere metriche, comprendere il traffico o lo stato del nostro database diventa banale con questa dipendenza.
  • spring-boot-starter-test
    • /src/test/java/<my-app>
    • Le classi devono contenere la parola test
    • //contiene le seguenti librerie
      
      - org.jUnit                      //de-facto standard per unit testing in applicazioni Java
      - Spring Test & Spring Boot Test //Utilities e test d'integrazione per applicazioni Spring Boot applications
      - AssertJ                        //Fluent assertion library
      - Hamcrest                       //Una libreria per oggetti matcher objects
      - Mockito                        //Mocking framework per Java
      - JSONassert                     //Assertion library per JSON
      - JsonPath                       //XPath per JSON
  • spring-boot-starter-hateoas
    • Permette di scrivere output hypermedia-driven (rendendo le nostri API RESTful)
  • org.slf4j
    • Libreria per il logging
    • public class MyClass {
         private static final Logger LOGGER = LoggerFactory.getLogger(MyClass.class);
      
         public void MyMethod() {
            LOGGER.trace("writing here my trace...");  
            LOGGER.info("writing here my info..."); 
         }
      }
  • org.apache.commons
    • Libreria per implementazione delle “tuple”
    • <dependency> 
         <groupId>org.apache.commons</groupId> 
         <artifactId>commons-lang3</artifactId> 
      </dependency>
  • com.zaxxer.HikariCP
    • Pool di connessioni
    • <dependency>
         <groupId>com.zaxxer</groupId>
         <artifactId>HikariCP</artifactId> 
      </dependency>
      
      //classe PersistenceConfig
      //attivo il pool id connessioni
      @Bean
      public DataSource dataSource() {
         DataSourceBuilder<?> dataSourceBuilder = DataSourceBuilder
            .create()
            .driverClassName(env.getProperty("jdbc.postgres.driver-class-name"))
            .username(env.getProperty("jdbc.postgres.username"))
            .password(env.getProperty("jdbc.postgres.password"))
            .url(env.getProperty("jdbc.postgres.connection-url"));
      
         return dataSourceBuilder.build();
      }
      

 

Spring REST API

  • Controller
    • @RestController
      • Indica che i dati restituiti da ciascun metodo (della classe a cui si applica tale annoazione) verranno scritti direttamente nel “body” della “HTTP Response” invece di eseguire il rendering dei dati. 
    • @RestController
      @RequestMapping("/api/book")
      public class BookController {
      
         @Autowired
         private BookRepository repository;
      
         //Caso senza QUERYSTRING
         //http://<my-site>/apoi/book/{id}
         @GetMapping("/{id}")
         public Book findById(@PathVariable long id) {
            return repository.findById(id)
              .orElseThrow(() -> new BookNotFoundException());
         }
      
         //Caso con QUERYSTRING
         //http://<my-site>/api/book/searchByName=?name=<valore>
         @GetMapping("/searchByName") 
         public Book findByName(@ModelAttribute("name") String name) {
         
         }
         @GetMapping("/")
         public Collection<Book> findBooks() {
            return repository.getBooks();
         }
      
         @PutMapping("/{id}")
         @ResponseStatus(HttpStatus.OK)
         public Book updateBook(@PathVariable("id") final String id, @RequestBody final Book book) {
            return book;
         }
      
         
      }

 
Spring Data

  • dd

 

ORM Hibernate

  • Glossario
    • JPA é un insieme di linee guida (la specificazione)/
    • Hibernate é un’implementazione delle specifiche JPA.
    • Spring Data é uno strato aggiuntivo cje integra hiberante in Spring. Non necessario per poter utilizzare Hibernate in un progetto.
  • 3 Regole d’oro
    1. Conoscere lo stato della sessione (@PersistenceContext).
      • Sapere se siamo o non siamo in una sessione.
      • Un oggetto é nella sessione di Hibernate se é nel map Hibernate la cui chiave é l’id dell’oggetto + il nome della classe dell’oggetto.
    2. Sapere se siamo o non siamo in una transazione.
    3. Verificare il SQL generato da Hibernate
  • Ciclo di vita di un’entità

             

    • Stato degli oggetti nel “persistence context”
      • Transient (== detached di Entity Framework)
        • Oggetto creato nell’applicazione (new) ma non ancora aggiunto al context
      • Managed (== added)
        • Dopo aver creato l’oggetto, tramite il metodo “.persist()” lo aggiungo al context.
      • Removed (== deleted)
        • Tramite il metodo “.remove()” é possibile rimuove un’oggetto che era “Managed”
      • Session = managed + removed
        • Gli oggetti negli stati “managed” e “Removed” sono gestiti da Hibernate, appartengono cioé al “persistence context” che é possibile chiamare anche “sessione”.
        • Map = Classe + ID entità
      • Detached (== unchanged)
        • Il metodo “.detach()” permette di ritirare l’oggetto dal context. Tale oggetto si trova in stato “Detached”.
        • Il metodo “.merge()” permette di riaggiungere l’oggetto al context.recuperandolo dal DB.
          • Tale oggetto si troverà allora nello stato “Managed”.
          • Se ci sono delle differenze tra l’oggetto e quello recuperato nel DB, viene fatto l’insert/update.
    • Sessione
      • La sessione in Hibernate é detta anche cache di primo livello.
      • Se domandiamo al contesto di persistenza degli oggetti che conosce già, li cerca nella cache senza andare a prenderli nel DB.
      • In questo caso la cache dura quanto dura la sessione. 
  • PersistenceContext (==DbContext)
    • entityManager
      • @PersistContext
        EntityManager entityManager;
    • contains
      • var isPersisted = entityManager.contains(movie);  //true o false
    • persist
      • //movie.status = transient 
        entityManager.persist(movie); 
        //movie.status = managed
    • find (recupera i dati tramite una query nel DB)
      • Movie movie = entityManager.find(Movie.class, id);
    • createQuery
      • List<Movie> movies = entityManager.createQuery("from Movie", Movie.class).getResultList();
    • merge
      • Evitare di utilizzare il metodo merge() su un entità che é in stato “managed”. Infatti Hibernate lancia un aggiornamento degli snapshots associti all’entintà consumando delle risorse inutilmente.
      • Movie movie = entityManager.merge(movie); //update or insert
    • remove
      • entityManager.remove(movie); //delete
    • getReference (recuperare solo la classe proxy senza fare una query nel DB. Le proprietà dell’oggetto non sono inizializzate)
      • getReference ritorna un oggetto di tipo proxy il quale implementa intrinsecamente lo ‘automatic dirty checking’
      • é utile quando non é necressario fare il fetch di tutti i dati dell’oggetto nel DB ma é sufficiente il proxy con la possibilità di fare set
      • I dati saranno caricati (da Hibernate) alla domanda solo qualora necessari questo se l’oggetto é in stato managed
      • I proxy hibernate non sono serializzabili.
      • Movie movie = entityManager.getReference(Movie.class, id);
    • flush (== SaveCanches())
      • Il metodo flush forza l’entityManager a inviare tutte le eventuali modifiche (efettuate sugli oggetti) al DB.
  • Flush automatico
    • Alla chiusura di ogni sessione, il contesto di persistenza esegue un flush automatico delle modifiche presenti verso il DB.
    • Normalmente se si fa una routine che aggiunge un oggetto nel DB e poi richiede di leggere tutti gli oggetti presenti allora Hibernate automaticamente farà 2 flush.
      • Un primo per inserire la nuova entità.
      • Il secondo per rileggere il contenuto aggiornato della relativa tabella.
    • Invece di fare aggiungi e leggi ripetutamente, sarebbe meglio centralizzare gli insert e leggere solo alla fine.
    • //nella classe PersistenceConfig
      private Properties additionalProperties() {
         -- nessun azione sul DB all'avvio dell'applicazione
         properties.setProperty("hibernate.hbm2ddl.auto", "none");
      
         -- formatto le query nelle tracce
         properties.setProperty("hibernate.format_sql", "true");
      
         -- settare la modalità di flush desiderata
         -- di default = AUTO
         //il flush viene fatto solo al commit della transazione 
         properties.setProperty("org.hibernate.flushMode", "COMMIT");  
      }
  • Lazy Initialization Exception
    • Con Hibernate é possibile lavorare con le classi proxy che vengono recuperate direttamente dalla sessione/cache senza interrogare il DB
      • Le proprietà delle classi prowy non sono inizializzate ma caricate “on demand” (solo quando veramente richieste).
    • Quando si lavora con un’etità proxy e si cerca di accedere a uno dei sui attributi fuori sessione per cui Hibernate deve fare una chiamata al DB si avrà un’errore del tipo “Lazy Initialization Exception”
    • @Transaction => Se la mia query é racchiusa in un metodo con questa annotazione allora non avro’ la “Lazy Initialization Exception” in quanto la sessione é aperta d’ufficio e i dati sono disponibili quando richiesti. 
    • @Test
      @Transactional
      public void Test_getReference(Integer movieId) {
         //Hibernate recupera il proxy per l'oggetto "movie" senza interrogare il DB
         Movie movie = repository.getReference(movieId); 
         //a questo punto hibernate fa la SELECT nel DB per recuperare la proprietà "name"
         //se non ci fosse l'attributo @Transactional avrei una 
         // "Lazy Initialization Exception" perché Hibernate non avrebbe
         // una sessione aperta pronta al recupero della proprietà richiesta "name"
         String name = movie.getName();  
      }
  • Dirty checking
    • Il contesto di persistenza quando viene a conoscenza di un’entità (raccolta dal DB o attacca manualmente) ne prende una “foto” cosi ad ogni flush automatico é capace di sapere quali query fare sul DB.
    • Stoccare troppe referenze nel context puo’ portare ad aumentare la memoria utilizzata ma il problema principale é piuttosto legato alla CPU. Troppi calcoli per decidere quali modifiche riportare nel DB.
  • Classe di configurazione
    • //esempio di file di configurazione di Hibernate
      @Configuration
      @EnableTransactionManagement
      @ComponentScan(basePackages = { "com.<my-project>.<my-package>" })
      public class PersistenceConfig {
      
        @Bean
        public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
          LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
          //em.setDataSource(dataSourceH2());
          em.setDataSource(dataSourcePostgres());
          //package where looking for model entities
          em.setPackagesToScan(new String[] { "com.hibernate4all.tutorial.model" });
      
          JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
          em.setJpaVendorAdapter(vendorAdapter);
          em.setJpaProperties(additionalProperties());
      
          return em;
        }
      
        @Bean
        public DataSource dataSourceH2() {
          DriverManagerDataSource dataSource = new DriverManagerDataSource();
          dataSource.setDriverClassName("org.h2.Driver");
          dataSource.setUrl("jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
          dataSource.setUsername("sa");
          dataSource.setPassword("");
      
          return dataSource;
        }
      
       @Bean public DataSource dataSourcePostgres() { 
          DriverManagerDataSource dataSource = new DriverManagerDataSource(); 
          dataSource.setDriverClassName("org.postgresql.Driver"); 
          dataSource.setUrl("jdbc:postgresql://localhost:5432/dbname"); 
          dataSource.setUsername("postgres"); 
          dataSource.setPassword("password"); 
      
          return dataSource; 
        }
        @Bean
        public PlatformTransactionManager transactionManager() {
          JpaTransactionManager transactionManager = new JpaTransactionManager();
          transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
      
          return transactionManager;
        }
      
        private Properties additionalProperties() {
          Properties properties = new Properties();
          //hibernate create and drop the persistence layer (DB) at each application run
          //based on declared entities into persistence context
          properties.setProperty("hibernate.hbm2ddl.auto", "create-drop");
          properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
          return properties;
        }
      }
  • target/generated-sources
    • JPA Static Metamodel Generator =>  JPA definisce una “typesafe Criteria” API che permette alle query che usano i “Criteria” queries di essere costruite in modo fortemente-tipizzato, utilizzando le classi cosidette static metamodel 
    • //Questo plugin permette di aggiungere automaticamente nella cartella "/target/generated-sources"
      //le varie entità definite nel nostro progetto in modo da poterle referenziare nei vari moduli
      <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
         <configuration>
            <annotationProcessorPaths>
               <annotationProcessorPath>
                  <groupId>org.hibernate</groupId>
                  <artifactId>hibernate-jpamodelgen</artifactId>
                  <version>5.4.3.Final</version>
               </annotationProcessorPath>
            </annotationProcessorPaths>
          </configuration>
      </plugin>
      
      /target
        /generated-sources
           /annotations
             /com
                /<my-project>
                   /model
                      /Entity1_
                      /Entity2_
                      ..
                      /EntityN_
  • Notazioni
    • @PersistenceContext  (== DbContext di Entity Framework)
      • L’insieme delle entità che l’aplicazione ha delegato alla gestione di Hiberante.
    • @Entity 
      • HasCode e Equals é opportuno sovrascriverle per un entità se:
        • Si una un SET<entità>
        • Si pensa di fare il “retach” di un’istanza detached
      • Se si ha una chiave funzionale tra le proprietà dell”entità é utile usare direttamente solo questo campo (senza usare gli altri)
      • Equals(Object o)
        • Evitare di usare le associazioni esterne. Usare solo i campi di proprietà dell’entità.
      • Set Vs List
        • Version Hibernate < 5.8 => usare SET.
        • Per le altre versioni
          • List se si vuole avere liste ordinate.
          • List é più performante.
          • Set se si vuole una lista di valori univoci (bisogna definire correttamente HasCode e Equals)
      • import javax.persistence.Entity;
        import javax.persistence.GeneratedValue;
        import javax.persistence.GenerationType;
        import javax.persistence.Id;
        
        @Entity
        @Table(name = "Movies")
        public class Movie {
        
          public enum Color {
              red,green,yellow
          }
          @Id  //Field access
          @GeneratedValue(strategy = GenerationType.SEQUENCE | GenerationType.IDENTITY | 
                                     GenerationType.AUTO)
          private Long id;
        
          @Column(unique = true, name = "nameNew", nullable = false, length = 4000)
          @Min(value = 0 , message = "Value should be greater then equal to 0")
          @Max(value = 10 , message = "Value should be less then equal to 10")
          private String name;
        
          @Enumerated  //indico a Hibernate che il campo color é un enum
          private Color color;
        
          @Transient  //permette di non mappare nel DB tale attributo della classe 
          private String nonPresenteInBase
        
          @Override
          public boolean equals(Object o) {
            if (this == o) return true;
        
            if ((!(o instanceof Movie))) return false;
        
            Movie movie = (Movie) o;
        
            //CASO 1
            //Se l'entità ha almeno un campo che é un identità funzionale (es: codice fiscale, targa..) 
            //allora possiamo appoggiarci su questo campo per implementare 
            // equals() e hasCode() (usate da Hibernate) 
            return Objects.equals(name, movie.name);
        
            //CASO 2A
            //(caso transient) Se l'entità non ha un campo (diverso da ID) 
            //  che possa essere legittimamente considerato univoco 
            //allora uso solmo il campo ID se non nullo 
            if(id == null && ((Movie) o).getId() == null) {
              return Objects.equals(name, ((Movie) o).getName()) &&
                     Objects.equals(description, ((Movie) o).getDescription()) &&
                     Objects.equals(certification, ((Movie) o).getCertification());
            }
        
            //CASO 2B
            //(caso managed) oppure una combinazione con un senso 
            // funzionale degli altri campi se ID é nullo
            //uso l'id se non nullo
            return id != null && Objects.equals(id, ((Movie) o).getId())
          }
           
          @Override
          public int hasCode() {  
             //usare una costante
             return 31;
          }
        
          //lasciare la generazione di default
          //anche qui non aggiungere nessun campo di entità esterne
          @Override
          public String toString() {
             return "Movie{" +
                       "id=" + id +
                       ", name='" + name + '\'' +
                       ", description='" + description + '\'' +
                       ", certification=" + certification +
                       ", color=" + color +
                          '}';
          }         
        }
      • Entità fluent
        • E’ possibile rendere la nostra entità fluent implementando i setter in modo che restituiscano l’entità stessa
        • public Movie setColor(Color color) {
             this.color = color;
             return this;
          }
          
          
          //utilizzo dell'entità che é diventata fluent
          Movie.setName("Fuga da Alcatraz").setDescription("Miglior film").setColor("red");
          
          
      • Field Access e mapping implicito
        • Mettendo @Id su le proprietà della nostra classe, indichiamo ad Hibernate di scorrere tutte le altre proprietà (mapping implicito) per decidere le colonne della tabelle nel DB.
        • Avremmo potuto mettere @Id su un getter allora Hibernate avrebbe scorso tutti i metodi con get… per cercare le colonne (questo secondo caso é meno utilzzato).
    • @Repository
      • import java.util.List;
        import org.springframework.stereotype.Repository;
        import <my-projet>.model.Movie;
        
        @PersistenceContext
        EntityManagr entityManager;
        
        @Repository
        public class MovieRepository {
          public void persist(Movie movie) {
            entityManager.persist(movie);
          }
        
          public List<Movie> getAll() {
            throw new UnsupportedOperationException();
          }
        }
  • Relazioni
    • 1-a-molti (bidirezionale)
      • //un movie puo' avere molte review
        //la tabella con la chiave esterna é review
        //quindi l'oggetto review contiene il campo movie
        
        @Entity
        class Movie {
        
           //la chiave esterna é detenuta da "Review.movie"
           @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "movie")
           private List<Review> reviews = new ArrayList<>();
           public Movie addReview(Review r){
              if(r != null) {
                 this.reviews.add(r);
                 r.setMovie(this);
              }
              return this;
           }
        
           public Movie removeReview(Review r){
              if(r != null) {
                 this.reviews.remove(r);
                 r.setMovie(null);
              }
              return this;
           }
        }
        
        @Entity
        class Review {
           
           @ManyToOne(fetch = FetchType.LAZY)  //evita di caricare automaticamente l'entità correlata
           @JoinColumn(name = "movie_id")
           private Movie movie;
           
        }
    • Molti-a-molti
      • @ManyToMany é utilizzabile solo se la tabella d’associazione non ha delle colonne in più oltre alle chiavi esterne. 
      • Prediligere SET su LIST se si pensa di fare parecchie sopressioni nella tabella d’associazione. Infatti LIST si comporta in una maniera meno performante nelle remove().
      • //un genere puo' appartenere a molti movie
        //un movie puo' avere molti generi
        
        @Entity
        class Genre {
        
           //il proprietario della relazione é l'altro lato
              //non ho quindi bisogno di configurare il comportamento da 
              //tenere sull'altra entità tramite l'attributo cascade
           //Set é meglio di List nel molti a molti
           @ManyToMany(mappedBy = "genres")
           private Set<Movie> movies = new HastSet<>();
        
           //il setMovies() non é da implementare 
           public Set<Movie> getMovies() {
             return movies;
           }
        
        }
        
        @Entity
        //entità principale della relazione molti-a-molti
        class Movie {
        
           //é il lato principale della relazione, bisogna definire le cascade
           //Quando faccio un "movie.persist" voglio fare lo stesso sui genre associati 
           //Quando faccio un "movie.merge" voglio fare lo stesso sui genre associati 
           //NON metto CascadeType.DELETE perché non voglio che venga eliminato il 
           // genere quando elimino un movie associato
           //per conseguenza non posso usare neanche CascadeType.ALL
           @ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
           //definisco la tabella di gestione della relazione molti-a-molti
           @JoinTable(name = "movie_genre", joinColumns = @JoinColumn(name="movie_id"), 
                      inverseJoinColumns = @JoinColumn(name="genre_id"))
           private Set<Genre> genres = new HastSet<>();
        
           //il setGenres() non é da implementare 
           public Set<Genre> getGenres() {
              return Collections.unmodifiableSet(genres);
           }
        
           public Movie addGenre(Genre g) {
              if(g != null) {
                 this.genres.add(g);
                 g.getMovies().add(this);
              }
              return this;
           }
        
           public Movie removeGenre(Genre g) {
              if(g != null) {
                 this.genres.remove(g);
                 g.getMovies().remove(this);
              }
              return this;
           }
        }
    • Molti-a-Molti con proprietà aggiuntive
      • E’ necessario modellizzare la tabella ternaria (che ospita le proprietà aggiuntive di cui ha bisogno la relazione molti-a-molti)
      • //un "actor" puo' essere legato da più "movie"
        //un "movie" puo' essere legato a più "actor"
        //la relazione molti-a-molti tra "actor" e "movie" ha la proprietà aggiuntiva "character"
        
        @Entity
        class Actor {
           @Id
           @GeneratedValue(strategy = GenerationType.IDENTITY)
           private Long id;
        
           //qui ci sono le proprietà di Actor
           ...
        
           //la chiave esterna é detenuta da MovieActor.actor
           @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "actor")
           private List<MovieActor> moviesActors = new ArrayList<>();
        
        }
        
        @Entity
        class Movie {
           @Id 
           @GeneratedValue(strategy = GenerationType.IDENTITY) 
           private Long id; 
        
           //qui ci sono le proprietà di Movie
           ...
        
           //la chiave esterna é detenuta da MovieActor.actor 
           @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "actor") 
           private List<MovieActor> moviesActors = new ArrayList<>();
        
           public List getMoviesActors() {
                return moviesActors;
            }
        
            public void addActor(Actor actor, String character) {
                 MovieActor movieActor = new MovieActor(this, actor).setCharacter(character);
                 moviesActors.add(movieActor);
                 actor.getMoviesActors().add(movieActor);
            } 
        
        }
        
        //modellizzazione della tabella d'associazione tra "Actor" e "Movie"
        @Entity
        @Table(name="movie_actor")
        class MovieActor {
        
           /////////////////////////////////////////////////////////////////////////
           //classe embedded che rappresenta la chiave esterna
           @Embeddable
           public static class MovieActorId implements Serializable {
                private static final long serialVersionUID = 6323699701758787853L;
                
                @Column(name = "movie_id")
                private Long movieId;
        
                @Column(name = "actor_id")
                private Long actorId;
        
                public MovieActorId() { }
        
                public MovieActorId(Long movieId, Long actorId) {
                    this.movieId = movieId;
                    this.actorId = actorId;
                }
        
                @Override
                public boolean equals(Object o) {
                    if (this == o) return true;
        
                    if ((!(o instanceof MovieActorId))) {
                        return false;
                    }
        
                    MovieActorId other = (MovieActorId) o;
                    return Objects.equals(movieId, other.movieId) &&
                           Objects.equals(actorId, other.movieId);
                }
        
                @Override
                public int hashCode() {
                    return Objects.hash(movieId, actorId);
                }
           }
           ////////////////////////////////////////////////////////////////
        
           @EmbeddedId
           private MovieActorId id;
        
           @ManyToOne(fetch = FetchType.LAZY)
           @MapsId("movieId")
           private Movie movie;
        
           @ManyToOne(fetch = FetchType.LAZY)
           @MapsId("actorId")
           private Actor actor;
        
           //proprietà aggiuntiva della relazione molti-a-molti
           private String character;
        
           public MovieActor() { }
        
           public MovieActor(Movie movie, Actor actor) {
              this.movie = movie;
              this.actor = actor;
              this.id = new MovieActorId(movie.getId(), actor.getId());
           }
        
        }
    • 1 a 1 (unidirezionale)
      • //un movie puo' avere un solo movie_detail e un movie_detail é associato a un solo movie
        
        //caso unidirezionale (nessuna implementazione nella classe movie)
        //si implementa solamente movie in movie_detail e non viceversa
        //se si implementasse anche l'altra direzione della relazione, Hibernate 
        // caricherebbe sempre i dati di movie_detail insieme a quelli di movie
        //invece noi, una volta recuperato l'oggetto movie, si vuole avere i dati 
        // di movie_detail solo alla domanda.
        
        //Nella base di voglio generare una tabelle
        movie_table(movie_id,plot)
        
        @Entity
        @Table(name='movie_detail')
        public class MovieDetail {
        
           @Id //indica che questo campo é l'identificativo dell'entità
           private Long id;
         
           private String plot;
        
           //EAGER (in fase di lettura dell'entità "MovieDetail" visto che carico 
                    tutti i "Movie" associati avro' il problema N+1)
           @OneToOne
           @MapsId //permette di mappare la chiave primaria (utilzzabile anche con @ManyToOne
                   //indica di utilizzare il valore di questo campo per l'identificativo (id) dell'entità
                   //in altre parole movie_detail.id = movie_id 
           private Movie movie;
        
        }
  • Ereditarietà
    • Hibernate può mappare una gerarchia di oggetti.
    • 4 possibili implementazioni. Quale scegliere ?
      • Se si ha una super classe con tanti campi e le classe figlie ne hanno pochi in più allora meglio orientarsi verso il caso 1 o caso 2 (se si ha bisogno di caricare delle relazioni esterne)
      • Se invece si ha una situazione più varia con classi figlie con molti campi meglio orientarsi verso il caso 3
    • 1 – Creare nel DB solo le tabelle per le entità figlie (@ID sulle entità figlie)
      • Svantaggi
        • Hibernate é costretta a fare un’interrogazione SQL per ognuna delle tabelle figlie.
        • Se si cambia un campo dell’oggetto padre, é necessario cambiarlo anche nelle tabelle dei figli presenti nel DB.
        • Lavorare con le associazioni presenti nelle classi figlie non é semplice.
      • @MappedSuperclass
        public abstract class Padre {
           protected String name;
           protected String description;
        
           --getter and setter (name + description)
        }
        
        @Entity
        public Figlio1 extends Padre {
        
           @Id
           @GeneratedValue(strategy = GenerationType.IDENTITY)
           private Long id;
        
        }
        
        @Entity
        public Figlio2 extends Padre {
        
           @Id
           @GeneratedValue(strategy = GenerationType.IDENTITY)
           private Long id;
        }
        
        
        --- RECUPERARE I FIGLI - ESEMPIO DI POLIMORFISMO
        //Hibernate fa 2 select, una per ognuna delle classi figlie
        List<Padre> padri = entityManager
          .createQuery(select w from <com.myproject.model>.padre w, Padre.class).getResultList();
        Long contaFiglio1 = padri.stream().filter(o -> o.getClass() == Figlio1.Class).count();
        Long contaFiglio2 = padri.stream().filter(o -> o.getClass() == Figlio2.Class).count();
    • 2 – Creare nel DB solo le tabelle per le entità figlie (@ID sull’entità padre)
      • Vantaggi
        • Viene fatta una sola select con tutti i figli (UNION con dei NULL nelle SELECT per poter gestire i campi che esistono in un figlio ma sono nulli negli altri e viceverda) e le LEFT JOIN con le relazioni esterne desiderate.
        • E’ più seplice lavorare con le associazioni presenti nelle classi figlie.
      • Svantaggi
        • Se si cambia un campo dell’oggetto padre, é necessario cambiarlo anche nelle tabelle dei figli presenti nel DB.
      • @Entity
        @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
        public abstract class Padre {
        
          @Id
          @GeneratedValue(strategy = GenerationType.SEQUENCE)
          protected Long id;
        
          protected String name;
          protected String description;
        
          --getter and setter (id + name + description)
        }
        
        @Entity
        public Figlio1 extends Padre {
          //senza id
        }
        
        @Entity
        public Figlio2 extends Padre {
         //senza id  
        }
        
        --- RECUPERARE I FIGLI - ESEMPIO DI POLIMORFISMO
        
        //Hibernate fa 2 select, una per ognuna delle classi figlie
        //non ho bisogno di usare <com.myproject.model>.padre ma solo padre 
        // (ora Hibernate conosce l'entità direttamente)
        List<Padre> padri = entityManager
           .createQuery(select w from padre w left join fetch w.tabella_esterna, Padre.class).getResultList();
        Long contaFiglio1 = padri.stream().filter(o -> o.getClass() == Figlio1.Class).count();
        Long contaFiglio2 = padri.stream().filter(o -> o.getClass() == Figlio2.Class).count();
        
        // esempio di select con 2 figli e una terza tabella (associazione esterna)
        select distinct watchable0_.id as id1_9_0_,
                          reviews1_.id as id1_7_1_,
                        watchable0_.description as descript2_9_0_,
                        watchable0_.clazz_ as clazz_0
                       
          from
             (
              select id,
                     description,
                     name,
                     certification,
                     null::int4 as seasons,
                     1 as clazz_
                from Figlio1
               union all
              select id,
                     description,
                     name,
                     null::int4 as certification,
                     seasons,
                     2 as clazz_
                from
             Figlio2 
            ) tab
         left outer join <Tabella-esterna> tab_ext on tab.id = tab_ext.movie_id
    • 3 – Creare nel DB solo una tabella per tutte le entità figlie (= coincide al caso Table-per-hierarchy pattern di Entity Framework)
      • Campi della tabella unica = campi del padre + campo unici del figlio1 + campo unici del figlioN + campo discriminante
      • Nel DB non ci sono più le tabelle figlie ma un’unica tabella con i campi somma dei campi padre + tutti i figli + colonna discriminante (per sapere a quale figlio corrispondono i dati presenti su quella riga)
      • @Entity 
        @Inheritance(strategy = InheritanceType.SINGLE_TABLE) 
        @DiscriminatorColumn(name="children_type")
        public abstract class Padre { 
          @Id 
          @GeneratedValue(strategy = GenerationType.IDENTITY) 
          protected Long id; 
          protected String name;  
          protected String description; 
        
          --getter and setter (id + name + description) 
        } 
        
        @Entity 
        @DiscriminatorValue("figlio1")
        public Figlio1 extends Padre { 
          //senza id 
          //campi specifici
        } 
        
        @Entity 
        @DiscriminatorValue("figlio2")
        public Figlio2 extends Padre { 
          //senza id 
          //campi specifici
        }
      • Vantaggi
        • Viene fatta una sola select con tutti i figli senza bisogno di una UNION e le LEFT JOIN con le relazioni esterne desiderate.
        • E’ più seplice lavorare con le associazioni presenti nelle classi figlie.
        • Non é necessario gestire le tabelle figlie nel DB (c’é solo il padre)
      • Svantaggi
        • Le colonne proprie ad un figlio non possono essere NOT NULL, in quanto il valore NULL é necessario perché tali colonne non esistono negli altri figli e vengono mappate in un unica colonna. Per ovviare a questo limite é opportuno validare tali campi direttamente nel codice (backend)
    • 4 – Creare nel DB padre + figli (= coincide al caso Table-per-type pattern di Entity Framework
      • Implementare l’ereditarietà direttamente nel DB tramite delle chiavi esterne 
        • Tabella padre con gli attributi comuni
        • Tabelle figlie con la chiave esterna che punta al padre + colonne specifiche.
      • @Entity 
        @Inheritance(strategy = InheritanceType.JOINED) 
        public abstract class Padre { 
           @Id 
           @GeneratedValue(strategy = GenerationType.IDENTITY) 
           protected Long id; 
        
           protected String campo_comune_1; 
           protected String campo_comune_2; 
           protected String campo_comune_N; 
        
          --getter and setter (id + tutti i campi_comuni) 
        } 
        
        @Entity 
        public Figlio1 extends Padre { 
          //senza id 
          //campi specifici
        } 
        
        @Entity 
        public Figlio2 extends Padre { 
          //senza id 
         //campi specifici
        }
  • Lettura dei dati
    • Problema N + 1 (caricamento EAGER)
      • Quando interrogo un’entità che é legata con una relazione 1 a 1 ad un’altra con la modalità EAGER alla fine avro’ N+1 query da fare
        • la query sulla tabella legata alla mia entità.
        • ma anche le N query (una per ogni riga appena recuperata) per andare a prendere i dati dell’entità legata dalla relazione 1 a 1 e caricata in modalità EAGER.
        • //Al caricamento di MovieDetail oltre alla query su MovieDetail
          // vengono generate altre N sulla tabella Movie (EAGER)
          public class Movie {}
          
          public class MovieDetail {
             @OneToOne
             @MapsId 
             private Movie movie;
          }
      • Soluzione
        • Caricare l’entità esterna in modalità LAZY
          •  @OneToOne(fetch = FetchType = LAZY)
        • Usare inner join
          • entityManager
             .createQuery("select DISTINCT m from MovieDetail m join fetch m.movie", MovieDetail.class).getResultList();
        • Spezzare la query in due passaggi (recuperare prima l’entità principale e poi la relazione esterna corrispondente)
          • Attenzione: potrebbe trasformare il problema in un problema di prodotto cartesiano
    • Lettura entità tramite ID
      • //Find
        //Hibernate esegue una query SQL e recupera tutti i dati dell'entità "movie"
        Movie movie = repository.Find(movieId);  
        
        //getReference
        //Hibernate non esegue alcuna query SQL e recupera un proxy dell'entità "movie"
        //Tale proxy non ha tutte le proprietà dell'entità che dovranno essere caricate da Hibernate "alla domanda".
        Movie movie = repository.getReference(movieId);
    • Lettura di associazioni => Fetch plan 
      • //Fetch plan
        Quando in un'entità abbiamo delle referenze esterne possiamo impostare la proprietà fetch
        E' possibile cioé decidere se caricare una referenza esterna (LIST o entità singola) direttamente 
        quando si carica l'entità (eager loading) o "alla domande" (lazy loading)
        
        @Entity
        public class Movie {
           @Id
           @GeneratedValue(strategy = GenerationType.IDENTITY)
           private Long id;
         
           //EAGER
           @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "movie", fetch = FetchType.EAGER)
           private List<Review> reviews = new ArrayList<>();
        
           //LAZY
           @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "movie")
           private List<Award> awards = new ArrayList<>();
        
        }
    • Hql
      • Linguaggio di Hiberbate.
    • Jpql
      • Linguaggio dello standard JPA. 
      • Vantaggio
        • Codice leggibile (la query é simile al SQL)
      • Svantaggio
        • Non é facile modificare le query una volta fatte (es: cambiare il nome di una tabella).
        • Le query possono diventare lunghe sequenze di caratteri.
      • List<Movie> movies = entityManager.createQuery("SELECT m FROM movie m WHERE m.name = :pName, Movie.class)
                 .setParameter("pName", name) 
                 .getResultList();
        Movie movie = entityManager.createQuery(sql, Movie.class).getSingleResult();
        
        
    • Criteria Jpql
      • var movies = repository.findWithCertification(Operator.lessThanOrEqualTo, 2);
        
        public enum Operator {
           lessThan, lessThanOrEqualTo, equal, greaterThanOrEqualTo,greaterThan
        }
        
        public List<Movie> findWithCertification(Util.Operator operator, Certification certification) {
           CriteriaBuilder builder = entityManager.getCriteriaBuilder();
           CriteriaQuery<Movie> query = builder.createQuery(Movie.class);
          
           //select * from Watchable
           Root<Movie> root = query.from(Movie.class);
        
           //where m.certification
           Predicate predicat = null;
           switch (operator) {
              case lessThan -> {
                 predicat = builder.lessThan(root.get(Movie_.CERTIFICATION), certification);
              }
              case lessThanOrEqualTo -> {
                  predicat = builder.lessThanOrEqualTo(root.get(Movie_.CERTIFICATION), certification);
              }
              case equal -> {
                 predicat = builder.equal(root.get(Movie_.CERTIFICATION), certification);
              }
              case greaterThanOrEqualTo -> {
                 predicat = builder.greaterThanOrEqualTo(root.get(Movie_.CERTIFICATION), certification);
              }
              case greaterThan -> {
                 predicat = builder.greaterThan(root.get(Movie_.CERTIFICATION), certification);
              }
           }
        
           query.where(predicat);
           return entityManager.createQuery(query).getResultList();
        }
        
    • Join
      • //Jpql
        //la parola chiave DISTINCT permette a Hibernate di ripulire il risultato in memoria
        //setHint => possiamo invece evitare che sia applicato alla query SQL effettivamente generata
        //la parola chiave FETCH significa che nella SELECT si caricano anche i 
        // dati della tabelle dell'associazione esterna (es: movie.reviews)
        List<Movie> movies = entityManager
              .createQuery("SELECT DISTINCT m FROM Movie m LEFT JOIN FETCH m.reviews", Movie.class).getResultList()
              .setHint(QueryHints.HINT_PASS_DISTINCT_THROUGH, false)
        
        //Join doppio
        //Movie.reviews = LIST
        //Movie.genres= SET
        List<Movie> movies = entityManager
        .createQuery("SELECT DISTINCT m FROM Movie m 
                                        LEFT JOIN FETCH m.reviews 
                                        LEFT OUTER FETCH m.genres", Movie.class).getResultList()
        .setHint(QueryHints.HINT_PASS_DISTINCT_THROUGH, false) 
        
        //Criteria
        CriteriaBuilder cb = entityManager.getCriteriaBuilder(); 
        CriteriaQuery<Order> cq = cb.createQuery(Order.class); 
        SetJoin<Order, Item> itemNode = cq.from(Order.class).join(Order_.items); 
        cq.where( cb.equal(itemNode.get(Item_.id), 5 ) ).distinct(true);
    • Join multipli con almeno 2 LIST
      • MultipleBagFetchException =>
        • Hibernate ha problemi a fare un multiple join su 2 o più entità esterne di tipo LIST (che potrebbero quindi legate tra loro)
      • Prodotto cartesiano =>
        • Visto che fa delle left outer join allora se ho delle join multiple allora il risultato che ottengo é TUTTE le righe del prodotto cartesiano tra le entità di cui sto facendo le join che é un comportamento che non voglio. 
      • Soluzione veloce => Sostituire tutte le LIST con delle SET. Soluzione non efficace.
      • Soluzione efficace =>
        • Spezzo la multiple join in più join doppie e sfrutto la cache di primo livello nativa di Hibernate
        • ATTENZIONE: Le verie query DEVONO condividere la sessione.
        • //Join doppio con 2 list
            //Movie.reviews = LIST
            //Movie.awards = LIST
               //spezzo la query in 2 passaggi
          
          //Le varie query devono condividere la sessione
          @Transaction
          public List<Movie> getMoviesJoinReviewsAndAwards() {
            //prima query dove recupero le "reviews" associate ai "movie" 
            List<Movie> movies = entityManager
               .createQuery("select DISTINCT m from Movie m left join fetch m.reviews", Movie.class)
               .setHint(QueryHints.HINT_PASS_DISTINCT_THROUGH, false)
               .getResultList();
          
          
            //seconda query dove recupero gli awards dei movie che avevo recuperato nella query precedente
            return entityManager
               .createQuery("select DISTINCT m FROM Movie m 
                                               LEFT JOIN FETCH m.awards where m in (:movies)", Movie.class)
               .setParameter("movies", movies)
               .setHint(QueryHints.HINT_PASS_DISTINCT_THROUGH, false)
               .getResultList();
          }
        • //Esempio 2
          
          @Transactional
          public MovieDetail getDetail(Long movieId)
          {
             MovieDetail detail = entityManager
                .createQuery("select DISTINCT d from MovieDetail d " +
                             " inner join fetch d.movie m " +
                              " left join fetch m.reviews " +
                              " left join fetch m.genres " +
                             " where d.id in (:movieId)", MovieDetail.class)
                .setParameter("movieId", movieId)
                .setHint(QueryHints.HINT_PASS_DISTINCT_THROUGH, false)
                .getSingleResult();
          
             //Hibernate (stando operando nella stessa sessione) é capace 
             // di raggruppare i risultati di una seconda query.
             // In questo caso, visto che Movie.awards é una LIST come movie.reviews 
             // non possono essere caricate con la stessa query
             // percio' creo una seconda query che andrà a sommarsi alla prima 
             entityManager
                .createQuery("select DISTINCT m FROM Movie m 
                                                LEFT join fetch m.awards 
                                               WHERE m in (:movies)", Movie.class)
                .setParameter("movies", detail.getMovie())
                .setHint(QueryHints.HINT_PASS_DISTINCT_THROUGH, false)
                .getResultList();
          
             return detail;
          }
    • SQL native
      • Utile se si vogliono usare delle funzioni si SQL che non possono essere utilizzate in Hibernate (es: select reverse(name) from movie)
      • Query complesse 
      • //esempio 1
        Query query = em.createNativeQuery( "select id from users where username = ?"); 
        query.setParameter(1, "lt"); 
        BigDecimal val = (BigDecimal) query.getSingleResult();
        
        //esempio 2
        public List<Object[]> getTopReviewers()
        {
           List<Object[]> authors = 
              entityManager
                 .createNativeQuery("select author, count(*) conta from review r group by author")
                 .getResultList();
        
           //authors.stream().forEach(t -> LOGGER.trace("Author: " + t[0] + " (" + t[1] + ")"));
           return authors;
        }
        
        //esempio 3 con Tuple
        public List<Pair<String, BigInteger>> getTopReviewersWithTuple()
        {
           List<Pair<String, BigInteger>> result = new ArrayList<>();
           List tuples = entityManager
                  .createNativeQuery("select count(r), author FROM review r GROUP BY author", Tuple.class)
                  .getResultList();
        
           tuples.stream().forEach(t -> result.add(Pair.of((String) t.get(1), 
                                                           (BigInteger)t.get(0))));
        
           return result;
        }
        
        
    • getResultStream (non troppo usato)
      • //la sessione deve essere aperta per poter scorre il cursore
        return entityManager.createQuery("<my-query>")
                            .setHint(QueryHints.HINT_FETCH_SIZE, 50) //indico a PostgreSQL di recuperare 
                                                                       solo 50 righe e non tutte alla volta
                            .getResultStream()
                            .skip(start)
                            .limit(maxResult)
                            .filter(predicate) //filtro gestito in JAVA dopo aver recuperato i dati dal DB
                            .sorted(comparator)
                            .collect(Collector.toList())
  • Paginazione
    • JPA offre uno standard per la paginazione.
    • La paginazione é fatta in-memory e non direttamente nel DB, significa cioé che ogni volta bisogna comunque recuperare l’integralità dei dati prima di gestire la paginazione stessa.
    • public List<Movie> getAllPagination(int start, int maxResult) {
         return entityManager.createQuery("selct m from Movie m order by m.name", Movie.class)
             .setFirstResult(start)
             .setMaxResults(maxResult)
             .getResultList();
      }
  • Migrazione
    • Parlando in un ORM la migrazione é un punto di primaria importanza. Si tratta infatti di riportare nel DB le modifiche fatte nelle entità. Ci sono 2 approcci principali
    • Partendo dal codice
      • Hibernate puo’ agire sul livello di persistenza (DB) ad ogni restart dell’applicazione settando nel modo desiderato la proprietà “hibernate.hbm2ddl.auto” nel metodo AdditionalProperties() della classe di configurazione:
      • //Al restart dell'applicazione web Hibernate:
        
         //1 - non realizza nessuna azione sul "livello di persistenza"
         properties.setProperty("hibernate.hbm2ddl.auto", "none");
        
         //2 - crea il "livello di persistenza" in base alle @Entity definite nel "model"
         properties.setProperty("hibernate.hbm2ddl.auto", "create");
        
         //3 - riporta ogni cambiamento effettuato nel "model" al "livello di persistenza"
         properties.setProperty("hibernate.hbm2ddl.auto", "update");
        
         //4 - utile per i TEST - come caso "crea" ma alla chiusura dell'applicazione web Hibernate fa
         //    il drop del "livello di persistenza"
         properties.setProperty("hibernate.hbm2ddl.auto", "create-drop");
    • Direttamente nel DB
      • Flywaydb => Tramite Flywaydb é possibile gestire i cambiamenti di struttura del DB direttamente tramite script
        • Aggiungere dipendenza
          • //pom.xml
            <dependency>
            <groupId>org.flywaydb</groupId>
            <artifactId>flyway-core</artifactId>
            </dependency>
        • Aggiungere scripts di modifica del DB 
          • //percorso standare dove "flywaydb" cerca gli script da applicare alla base
            <my-project>/src/main/resources/db/migration/... (aggiungere qui gli scripts)
        • Tabella flyway_scheme_history
          • Flywaydb memorizza gli script applicati alla DB tramite la tabella “flyway_scheme_history”.
          • Ogni volta che viene aggiunto un file nella relativa cartella del progetto,, Flywaydb lo applicherà al DB e aggiornerà la tabella con lo storico delle modifiche cosi’ da poter sapere a che punto siamo e eventualmente ripetere certe modifiche.
        • Disabilitare Flywaydb
          • //application.properties
            spring.flyway.enabled=false
  • Meccanismi di cache
    • Problematiche generali
      • E’ un meccanismo per cui dei dati sono duplicati in modo da renderli di più facile accesso per la risorsa che li richiede
      • Questo meccanismo porta dei benefici ma deve essere poi gestito.
        • Come sincronizzare i dati presenti nella cache con quelli principali presenti nella nostra sorgente dati.
        • Come gestire gli accessi concorrenti
        • Cosa succede se i dati della cache diventano grossi
      • Implementare lq cache in Hibernate solo in estrema ratio se le performance sono insufficienti.
      • Ricordarsi che normalmente abbiamo già naturalmente delle cache
        • Il DBMS legge i dati da un sistema di file una prima volta se non ancora trattati.
        • Il DBMS li recupera invece direttamente dalla memoria se già letti.
        • Il DBMS usa anche degli INDICI che in soldoni duplica anch’esso una parte dei dati.
        • Hibernate ha già la gestione della sessione che é una sorta di cache di primo livello.
    • Cache di primo livello (sessione)
      • Quando le query sono parte di una sessione allora condividono i dati, cio’ simula una sorta di cache.
    • Cache di secondo livello (ehcache)
      • E’ una cache gesita a livello della Java virtual machine (JVM). 
      • Aggiungere al progetto la libreria e configurarla
        • //Passo 1: aggiungere la dipendenza al pom.xml
          <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-ehcache</artifactId>
          </dependency>
          
          //Passo 2: aggiungere delle proprietà nella classe PersistenceConfig
          private Properties additionalProperties(){
             //(gestione per entità) attivo la cache di 2° livello a livello di entità 
             properties.setProperty("hibernate.cache.use_second_level_cache", "true");
             
             //(gestione per query) attivo la cache di 2° livello a livello di query (comando <createQuery>)
             properties.setProperty("hibernate.cache.use_query_cache", "true");
             
             //indico a Hibernate dove memorizzare i dati in cache
             properties.setProperty("hibernate.cache.region.factory_class", "org.hibernate.cache.ehcache.EhCacheRegionFactory");
          }
    • (gestione per entità) Aggiungere le entità alla cache
      • //esempio cache entità singola
        @Entity
        //attivo la cache sull'entità "genre"
        @Cacheable
        //strategia di cache
        @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
        public class Genre {
          ...
        }
        
        //esempio cache collezione
        class Movie {
           @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
           private final Set<Genre> genres = new HashSet<>();  
        }
    • (gestione per query) Indicare quali query gestire in cache
      • @Override
        public List<Genre> getAll() {
           return entityManager.createQuery("from Genre g", Genre.class)
                               .setHint(QueryHints.HINT_CACHEABLE, "true")
                               .getResultList();
        }
  • Tips
    • Distinct
      • Uso DISTINCT se ho un FETCH con una campo che una collezione.
    • Prima di fare una decidere come implementare una query di lettura in hibernate:
      • verificare come le relazioni sono mappare: unilaterali o bilaterali.
      • verificare se le relazioni esterne sono é EAGER o LAZY
        • OneToOne di default é EAGER
    • StackOverFlow error
      • Quando non si usano dei DTO ma direttamente le entità nel nostro MODEL che sono correlate tra loro, fare attenzioni alle relazioni bidirezionali che potrebbero creare dei cicli infiniti.
      • @JsonIgnore
        • Spezzo la catena biridezionale che crea l’impossibilità di serializzare il mio oggetto
        • //un "movie" puo' avere molte "review"
          class Movie {
             @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "movie")
             private List<Review> reviews = new ArrayList<>();
          }
          
          una "review" é legato ad un "movie"
          class Review {
              @ManyToOne
              @JoinColumn(name = "movie_id")
              @JsonIgnore
              private Movie movie;
          }
  • Statistics
    • @Test
      void findAll_nominal_case_perftest() {
         //attivo le statistiche hibernate
         SessionFactory factory = entityManagerFactory.unwrap(SessionFactory.class);
         Statistics stats = factory.getStatistics();
         stats.setStatisticsEnabled(true);
      
         List<Movie> movies = repoMovie.getAll();
         //Controllo che la query che ritorna tutti i Movie generi solo 1 qurey
         //Se percaso attivo il fetch=EAGER in alcune delle referenze esterne della mia entità Movie allora le 
         // mi attendo che le query generate siano N + 1
         assertThat(stats.getPrepareStatementCount()).as("One SELECT to get all the movies").isEqualTo(1L);
      }

 

Hibernate – Problematiche di produzione

  • Pool di connessioni (alla base di dati)
    • Funzionamento di default (senza pool)
      • Per poter recuperare i dati nel Db, Hibernate apre una nuova connessione normalmente in modalità auto-commit (con l’impossibilità quindi di aprire una transazione)
    • Con un pool di connessioni attivo succede che Hibernate crea un certo numero di connessioni che costituiscono il nostro pool e che saranno a disposizione delle varie richieste di connessione di cui la procedura che stiamo eseguendo avrà bisogno.
    • Postgres
      • //visualizzo stato delle connessioni al DB
        select * from pg_stat_activity where datname = '<my-database>'
    • @Transactional =>
      • Permette di “prenotare” una connessione del pool.
      • Metta la modalità auto-commit = false, permette cioé di poter avviare una transazione.
      • Funziona solo su metodi di classi iniettate da Spring (@Bean) e public
      • Proprietà
        • Propagation
          • Metodi in lettura
            • //se esiste una transazione, la nostra query semplicemente si aggiunge
              //se invece non ne esiste una, la nostra query NON ne crea una nuova.
              @Transactional(Propagation = propagation.SUPPORTS)
              public MyMethod()
          • Metodi in scrittura
            • //se esiste una transazione, la nostra query semplicemente si aggiunge
              //se invece non ne esiste una, la nostra query ne crea una necessariamente.
              @Transactional(Propagation = propagation.REQUIRED) (valore di Default, attributo non necessario)
              public MyMethod()
        • ReadOnly
          • //Ogni modificazioni nella DB verrà impedita
            //Disattiva il sistema di dirty checking di Hibernate.
            @Transactional(Readonly = true)
            public MyMethod()
  • Look ottimistico
    • Le entità devono avere una proprietà “version”
      • class Movie {
           //lool ottimistico
           @Version  
          private short version;
        }
        
        //query con look ottimistico attivo
        update movie set name = '<new_name>', version=<old_version> + 1 where id=<my_id> and version = <old_version>
    • Quando prendo possessione dei dati su cui devo agire ne leggo la versione.
    • Subito prima di lanciare il commit delle mie modifiche leggo di nuovo la versione dei dati
      • se questa non é cambiata allora il commit é confermato.
      • se questa é cambiata allora lancio un’eccezione e il commit sarà annullato.
    • vs Look pessimistico
      • Ogni volta che prendo possesso di un set di dati, li loccko e nessun altro li potrà modificare/leggere finché non faccio il commit delle mie modifiche.

Risorse