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.
Gli esempi appena visti fanno riferimento ad un progetto demo che ho realizzato a dimostrazione di quanto letto in questo e in altri post. Lo trovi su GitHub a questo indirizzo.
Link di riferimento:
- GitHub del progetto demo: demo-api-service
- Post creare un progetto Spring Boot
- Post creare servizi RESTful Spring Boot
- Link esterni:
- Spring Boot docs data access: howto-data-access
- Spring Boot docs configurare le proprietà Jpa: howto-configure-jpa-properties
No comment yet, add your voice below!