Spring Framework 005 - Como configurar Spring JPA
Para configurar Spring Framework con JPA necesitamos hacer los siguientes pasos
- Añadir la dependencia de nuestro driver de la base de datos
- Configurar nuestro DataSource
- Configurar nuestro PersistenceProvider
- Configurar nuestro EntityManagerFactory
- Configurar nuestro TransactionManager
Veamos con calma qué es cada una de estas cosas y cómo configurarla. Tienes un ejemplo conpleto en spring-jpa.
Introducción[editar]
Antes de nada, comentar que existen muchas formas de configurar todo esto y muchas opciones. La que vamos a poner aquí es adecuada para una aplicación java standalone, es decir, que no vaya dentro de un servidor de aplicaciones JEE o Tomcat. Con spring-data-jpa o con Spring Boot se simplifican estos procesos también. Pero aquí vamos con Spring Framework, sin Spring Boot y sin spring-data-jpa, para ver los conceptos e intentar aclararlos.
Dependencia del driver[editar]
En este ejemplo usamos maven. Así que una de las primeras cosas es añadir la dependencia del driver de la base de datos concreta que queramos usar. Para evitarte lios de instalación si quisieras probar este ejemplo, aquí usaremos H2 en memoria, que no requiere de ninguna instalación por tu parte.
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.220</version>
</dependency>
Configurar nuestro DataSource[editar]
Cuando manejamos bases de datos, es habitual configurar un DataSource. Esto no es más que una clase java que nos de conexiones con la base de datos cuando las necesitemos. Para configurarlo, solo tenemos que darle los parámetros de conexión con la base de datos.
Primero, elegimos un DataSource, como puede ser el que tiene la librería commons-dbcp2 de apache. Ponemos la dependencia en maven
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.9.0</version>
</dependency>
Y en nuestro fichero de configuración de Spring instanciamos este DataSource y lo configuramos.
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="org.h2.Driver" />
<property name="url" value="jdbc:h2:mem:users_database" />
<property name="username" value="" />
<property name="password" value="" />
</bean>
Se instancia la clase, se indica qué clase es el driver, se pone la url de conexión con la base de datos, el usuario y el password. users_database es el nombre que queremos dar a nuestra base de datos.
Configurar nuestro PersistenceProvider[editar]
JPA no es más que un conjunto de interfaces y normas para manejo de bases de datos. Pero no implementa estas interfaces. Así que necesitamos alguna librería que sí las implemente. En nuestro caso usaremos Hibernate, que es de las más conocidas. Así que primero ponemos la dependencia en maven
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.6.15.Final</version>
</dependency>
y lo configuramos en el fichero de configuración de Spring
<!-- Ojo, esto va dentro de una property de entityManagerFactory -->
<bean class="org.hibernate.jpa.HibernatePersistenceProvider"></bean>
Configurar nuestro EntityManagerFactory[editar]
En este contexto de Spring con base de datos, un EntityManager es la clase que nos ayuda a guardar, modificar y recuperar datos en una base de datos concreta. Todas las transacciones que queramos hacer desde código, las haremos usando métodos de un EntityManager. Con Spring Boot o spring-data-jpa no es necesario hacerlo a través de esta clase, podemos usar clases de nivel más alto. Pero para este ejemplo lo haremos con esta clase de más bajo nivel.
Para obtener un EntityManager, necesitamos configurar una EntityManagerFactory. Primero añadimos la dependencia maven del jar de spring que cotiene un EntityManagerFactory
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.29</version>
</dependency>
y ahora, en el fichero de configuración de Spring instanciamos y configuramos
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<!-- Si no ponemos esto, es necesario un fichero persistence.xml -->
<property name="packagesToScan" value="com.chuidiang.examples.spring_jpa" />
<property name="dataSource" ref="dataSource" />
<property name="jpaProperties">
<props>
<prop key="hibernate.show_sql">false</prop>
<prop key="hibernate.hbm2ddl.auto">create</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.H2Dialect</prop>
</props>
</property>
<property name="persistenceProvider">
<bean class="org.hibernate.jpa.HibernatePersistenceProvider"></bean>
</property>
</bean>
EntityManagerFactory no es más que una interface. Elegimos la clase LocalContainerEntityManagerFactoryBean como la implementación de esa interface. Esta clase es adecuada para aplicaciones Java y Spring standalone, es decir, que no van dentro de un contenedor web como Tomcat o JEE como Glassfish.
Con packagesToScan le indicamos el paquete de nuestro código donde hay clases anotadas con @Entity, que son las clases que querremos guardar o recuperar de base de datos.
Con dataSource le pasamos el DataSource que creamos en pasos anteriores.
Con jpaProperties podemos pasar properties que entienda nuestro proveedor de JPA, en nuestro ejemplo Hibernate. Le estamos diciendo que no nos enseñe las sentencias SQL que va a ejecutar, que queremos que cree la base de datos en el arranque y que use la variante SQL específica para la base de datos H2 H2Dialect
Con persistenceProvider le estamos indicando quién es la implementación de JPA. Es decir, la clase HibernatePersistenceProvider de Hibernate.
Configurar nuestro TransactionManager[editar]
El EntityManager que obtenemos no es thread-save, es decir, no podemos tener muchos hilos atacándolo a la vez. También es habitual en ocasiones que queramos hacer una transacción con base de datos en varios pasos y que no nos interese que se quede a medias, es decir, queremos todo o nada. Por ejemplo, imagina que tienes dos tablas, una con puntos x,y y otra con polígonos, donde se referencia a la tabla de puntos x,y para los vertices del polígono. Si insertas un polígono, necesitas insertar todos los vértices y luego el polígono. Si hay un fallo tras insertar el quinto punto, me interesa que se borren los anteriores y de fallo la inserción completa del polígono.
Todo esto se consigue con un TransactionManager que se encargue de comenzar nuestras transacciones y esté pendiente de cuándo acaban para dejar a otros hilos y validar/descartar todas las transacciones parciales de una transacción más grande.
La clase TransactionManager está en la dependencia spring-orm que ya hemos añadido, así que sólo nos quedan instanciarla y configurar en nuestro fichero Spring
<bean class="org.springframework.orm.jpa.JpaTransactionManager"
id="transactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
Instanciamos la clase JpaTransactionManager y la configuramos pasándole el dataSource con las conexiones a nuestra base de datos.
Código Java[editar]
No vamos a poner aquí todo el detalle de cómo hacer transacciones en base de datos desde java, pero sí lo que tenemos que usar de todo esto que se ha ido comentando.
Antes de liarnos con el código, en el fichero Spring debemos poner un par de cosas
<context:component-scan base-package="com.chuidiang.examples.spring_jpa"/>
<tx:annotation-driven/>
Son necesarias para que Spring tenga en cuenta las anoticaciones en nuestro código. tx:annotacion-driven para tener en cuenta las anotaciones @Transactional que usamos más adelante.
Clases Entity[editar]
Tenemos que tener clases con la anotación @Entity que son las que se guardarán en base de datos. Estas clases deben estar en el paquete que indicamos al configurar el EntityManagerFactory en packagesToScan. También pueden estar en subpaquetes de este paquete.
También es posible no tener anotaciones @Entity en las clases y en su lugar tener ficheros XML para indicar qué clases persistir y cómo hacerlo. Pero eso es para otro tutorial.
Un ejemplo de clase con anotación @Entity
package com.chuidiang.examples.spring_jpa;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
/**
* @author Chuidiang
* @date 15/07/2023
*/
@Getter @Setter
@Entity
@Table(name="user_table")
public class User {
@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private int id;
private String name;
private String email;
}
Con @Table indicamos el nombre que queremos para la tabla en base de datos. @Getter y @Setter genera los métodos get() y set() de la clase, usando la librería lombok, pero eso es otra guerra. Puedes no usarla y poner estos métodos a mano o decirle a tu IDE que los genere (eclipse, IntelliJ, etc). @Id es para indicar que este campo es la clave primaria en la base de datos. @GeneratedValue para indicar cómo generar esta clave en cada inserción. AUTO indica que se use el mecanismo que tenga la base de datos por debajo (auto increment, secuencias, etc).
Un DAO para acceso[editar]
Para las operaciones con la base de datos, es habitual hacerse clases DAO o Data Access Object. Veamos un ejemplo
package com.chuidiang.examples.spring_jpa;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import java.util.List;
/**
* @author Chuidiang
* @date 15/07/2023
*/
@Transactional
public class UserDao {
@PersistenceContext
EntityManager entityManager;
public void insert(){
User pedro = new User();
pedro.setName("Pedro");
pedro.setEmail("pedro@email.com");
entityManager.persist(pedro);
User juan = new User();
juan.setName("Juan");
juan.setEmail("juan@email.com");
entityManager.persist(juan);
}
public void query(){
TypedQuery<User> query = entityManager.createQuery("select u from User u", User.class);
List<User> resultList = query.getResultList();
resultList.forEach(user -> {
System.out.println(user.getId() + ", "+ user.getName() + ", "+ user.getEmail());
});
}
}
Hemos anotado la clase con @Transactional. Esto indica que todos los métodos de esta clase hacen transacciones con la base de datos y por tanto, queremos que TransactionManager los tenga en cuenta. Si el método salta una excepción, TransactionManger la capturará y deshará todos los cambios que se hayan hecho en base de datos en ese método antes de saltar la excepción.
Recibimos un EntityManager que hemos anotado con @PersistenceContext. Spring se encargará de inyectar aquí el EntityManager de forma que podamos usarlo.
El método insert() simplemente crea dos instancias de la clase User, les mete datos y llama a entityManager.persist(). Esto hará que se guarden en base de datos.
El método query() hace la consulta. Para ello, usa el método entityManager.createQuery(). La sintaxis del parámetro se parece a SQL pero no es SQL, es específica de JPA. Con query.getResultList() obtendríamos el listado de User almacenados en base de datos.