Spring Boot: comunicare con il database

In questo articolo voglio mostrarti come connettere e comunicare con il database in un’applicazione Spring Boot.

 

Connettere Spring Boot al database

Il primo passo consiste nell’aggiungere le dipendenze al progetto. Queste possono essere inserite in qualsiasi momento dello sviluppo o durante la creazione del progetto con Spring initializr.

 

Aggiungere le dipendenze

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
	<groupId>com.h2database</groupId>
	<artifactId>h2</artifactId>
</dependency>

La prima riguarda l’utilizzo di JPA ed è fornita dal set di dipendenze Spring Boot starters.

Ho inserito poi la dipendenza H2 che si riferisce ad un database in-memory, togliendo la necessità di dover scegliere un particolare gestore di dati in questo momento. Più avanti ti mostrerò come utilizzare un database diverso da H2.

 

Aggiungere una configurazione

Il prossimo passo consiste nel definire i parametri di connessione (spring.datasource) all’interno del file application.yml (o application.properties se non si utilizza YAML).

# DATASOURCE 
## H2 ## 
spring: 
    datasource: 
        url: jdbc:h2:mem:testdb 
        username: test 
        password: 
    # JPA - Hibernate 
    jpa: 
        hibernate: 
            ddl-auto: update

Con un datasource così definito facciamo riferimento ad un database chiamato testdb, in-memory h2, con username e password le rispettive credenziali di accesso.

La proprietà spring.datasource.jpa.hibernate.ddl-auto è un’opzione per cui Hibernate convalida o esporta automaticamente lo schema DDL nel database. Se non viene specificato nessun valore, Spring Boot assegna in automatico valori predefiniti diversi a seconda delle condizioni di runtime (docs). I valori possibili per questa proprietà sono: validate, update, create e create-drop.

 

Database diverso da H2

Se il database H2 va benissimo per testare la nostra applicazione, lo stesso non vale se usato come sistema di gestione dei dati di un’applicazione “reale”. Per questo motivo, la scelta di un DBMS deve ricadere nella fase iniziale dello sviluppo.

Se ad esempio vogliamo utilizzare un’istanza postgreSQL, dobbiamo aggiungere la dipendenza nel file pom.xml affinché vengano caricate le giuste configurazioni, e cambiare i valori delle proprietà in application.yml.

Sostituiamo come segue:

<dependency>
	<groupId>org.postgresql</groupId>
	<artifactId>postgresql</artifactId>
</dependency>
# DATASOURCE 
## POSTGRESQL ## 
spring: 
    datasource: 
        url: jdbc:postgresql://localhost:5432/testdb 
        username: test 
        password: 
    # JPA - Hibernate 
    jpa: 
        hibernate: 
            ddl-auto: update
 

Creare una configurazione non di default

È possibile definire i parametri della connessione all’interno di una classe di configurazione (necessaria nel caso in cui si voglia definire più connessioni).

All’interno del file application.yml spostiamo le proprietà spring.datasource sotto una voce diversa, ad esempio application.postgres, e creiamo un file di configurazione che definisca la connessione al database leggendo da queste.

In questo caso Spring non eseguirà l’autoconfigurazione non essendo specificate le proprietà spring.datasource.

application: 
    postgres: 
        datasource: 
            url: jdbc:postgresql://localhost:5432/demodb 
            username: demo 
            password: 
            driver-class-name: org.postgresql.Driver 
        jpa: 
            hibernate: 
                ddl-auto: update 
                jdbc: 
                    lob: 
                        non_contextual_creation: true
@Configuration
@EnableJpaRepositories(
        basePackages = "it.laterale.cloud.repositories",
        entityManagerFactoryRef = "postgresEntityManager",
        transactionManagerRef = "postgresTransactionManager")
public class PostgresDatasourceConfig {
    
    @Autowired
    private Environment env;
    
    @Bean
    @Primary
    public DataSource postgresDataSource() {
        DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
        driverManagerDataSource.setUrl(env.getProperty("application.postgres.datasource.url"));
        driverManagerDataSource.setUsername(env.getProperty("application.postgres.datasource.username"));
        driverManagerDataSource.setPassword(env.getProperty("application.postgres.datasource.password"));
        driverManagerDataSource.setDriverClassName(env.getProperty("application.postgres.datasource.driver-class-name"));
        return driverManagerDataSource;
    }
    
    @Bean
    @Primary
    public LocalContainerEntityManagerFactoryBean postgresEntityManager() {
        LocalContainerEntityManagerFactoryBean postgresEntityManager = new LocalContainerEntityManagerFactoryBean();
        postgresEntityManager.setDataSource(postgresDataSource());
        postgresEntityManager.setPackagesToScan(new String[] {"it.laterale.cloud.entities"});
        HashMap hibernateProperties = new HashMap<>();
        hibernateProperties.put("hibernate.hbm2ddl.auto", env.getProperty("application.postgres.jpa.hibernate.ddl-auto"));
        hibernateProperties.put("hibernate.jdbc.lob.non_contextual_creation", env.getProperty("application.postgres.jpa.hibernate.jdbc.lob.non_contextual_creation"));
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        postgresEntityManager.setJpaVendorAdapter(vendorAdapter);
        postgresEntityManager.setJpaPropertyMap(hibernateProperties);
        return postgresEntityManager;
    }
    
    @Bean
    @Primary
    public PlatformTransactionManager postgresTransactionManager() {
        JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
        jpaTransactionManager.setEntityManagerFactory(postgresEntityManager().getObject());
        return jpaTransactionManager;
    }
}

Fin qui, abbiamo visto come definire una connessione ad un database in Spring Boot. Nel prossimo capitolo voglio farti vedere alcuni modi per comunicare con il database.

 

Come comunicare con il database

Una volta connesso all’applicazione Spring Boot, vogliamo un modo per comunicare con il database. Di seguito, ti mostrerò alcuni strumenti da utilizzare per raggiungere lo scopo.

 

#1 EntityManager

Un modo semplice per eseguire query ad un database è quello di utilizzare le API dell’oggetto EntityManager, in grado di interagire con tutte le entità gestite all’interno di un persistence context.

Possiamo pensare di creare una classe DAO nella quale implementare le operazioni.

@Component
@Transactional(readOnly = true)
public class UserDao {
    private static final String NO_RESULT = "No Result";
    
    @PersistenceContext
    private EntityManager em;
    
    public ApplicationUser findById(Long id) {
        ApplicationUser result = em.find(ApplicationUser.class, id);
        if (result != null) {
            return result;
        } else {
            throw new EntityNotFoundException(NO_RESULT);
        }
    }
    
    public ApplicationUser findByEmail(String email) {
        String sql = "from ApplicationUser u where u.email = :email";
        TypedQuery query = em.createQuery(sql, ApplicationUser.class);
        query.setParameter("email", email);
        List result = query.getResultList();
        if (result != null && result.size() == 1) {
            return result.get(0);
        } else {
            throw new EntityNotFoundException(result == null || result.size() == 0 ? NO_RESULT : "Non unique result");
        }
    }
    
    @Transactional
    public void create(ApplicationUser entity) {
        em.persist(entity);
    }
}

Nel primo metodo findById viene utilizzato il metodo find di EntityManager, dove occorre specificare la classe dell’entità e l’Id da cercare. Si ottiene lo stesso risultato creando una query come per il secondo metodo findByEmail, ad esempio:

public ApplicationUser findByEmail(String email) { 
    String sql = "from ApplicationUser u where u.id = :id"; 
    TypedQuery query = em.createQuery(sql, ApplicationUser.class); 
    query.setParameter("id", id); 
    List result = query.getResultList(); 
    
    if (result != null && result.size() == 1) { 
        return result.get(0); 
    } else { 
        throw new EntityNotFoundException(result == null || result.size() == 0 ? NO_RESULT : "Non unique result"); 
    } 
}
 

#1.1 NamedQuery

Una variante consiste nell’utilizzo di una namedQuery. In questo caso bisogna aggiungere l’annotazione sull’entità:

@NamedQuery(name = "user.findByEmail", query = "from ApplicationUser u where u.email = :email") 
public class ApplicationUser { ... }

e sostituire la query creata utilizzando il metodo createNamedQuery passando come parametro il nome della namedQuery:

TypedQuery query = em.createNamedQuery("user.findByEmail", ApplicationUser.class);

Entrambi i metodi findById e findByEmail fanno uso dell’annotazione @Transactional(readOnly = true) di springframework posta a livello di classe, dove con “readOnly = true” si indica una transazione di sola lettura.

Il metodo create, invece, utilizza il metodo persist di EntityManager e necessita di una transazione non-readOnly, cosa che viene risolta aggiungendo l’annotazione @Transactional sul metodo.

 

#2 JpaRepository

Utilizzare l’interfaccia JpaRepository messa a disposizione dal framework, in particolare da Spring Boot starter data Jpa, è un altro modo e la soluzione più rapida per comunicare con il database da un’applicazione Spring Boot.

Bisogna creare una nuova interfaccia java che estende proprio JpaRepository.

@Repository 
public interface ApplicationUserRepository extends JpaRepository<ApplicationUser, Long> { }

Ho utilizzato l’annotazione di classe @Repository che ben rappresenta l’intento del componente.

In questo modo possiamo eseguire le operazioni CRUD sull’entità ApplicationUser, che ha un Id di tipo Long come specificato, sfruttando i metodi forniti dall’interfaccia JpaRepository, tra i quali: save, findById, delete, … .

È possibile anche creare nuovi metodi per eseguire query a proprio piacimento utilizzando la sintassi richiesta, ad esempio:

public Optional findByEmail(String email); 

public Collection findByOrderByAgeAsc(); 

public Collection findByAgeGreaterThan(Integer age); 

@Query("from ApplicationUser u where u.name = :name ") 
public Collection findByName(@Param("name") String name);

e addirittura, come nelle ultime righe dell’esempio, scrivendo esplicitamente la query.

Per vedere tutti i modi per utilizzare questa interfaccia, puoi consultare la documentazione che trovi qui.

 

#2.1 Utilizzare i metodi dell'interfaccia JpaRepository

In questo post precedente, ho già fatto vedere come creare servizi REST utilizzando l’interfaccia JpaRepository. Se vogliamo ottenere una risorsa dal repository, possiamo scrivere:

Optional entityOpt = this.applicationUserRepository.findById(id);
Optional entityOpt = this.applicationUserRepository.findByEmail(email);
..

con il componente ApplicationUserRepository iniettato all’interno della classe.

 

Arrivato fin qui hai visto alcuni modi per comunicare con il database e come configurare la connessione in un progetto Spring Boot.

Qui sotto ho messo alcuni link ai quali puoi fare riferimento per approfondire l’argomento.

 

Link di riferimento:

Recommended Posts

No comment yet, add your voice below!


Add a Comment

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *