Spring Acceso a datos con Hibernate JPA

En los tutoriales previos de acceso a datos veíamos como la clase JdbcTemplate de Spring realiza gran parte del trabajo por nosotros, aun así, ya sea si utilizamos la API JDBC directamente o la clase JdbcTemplate siempre tenemos que convertir los datos obtenidos de la BD en objetos Java para manipularlos en nuestra aplicación y hacer el proceso inverso para almacenarlos nuevamente el la BD, para facilitarnos el trabajo existen frameworks que se encargan de realizar esta tarea, ello son los ORM.

spring orm hibernate jpa

Un ORM (Mapeo Objeto/Relacional) se refiere a la técnica de mapear una representación de datos desde un modelo de objeto a un modelo de datos relacionales con un esquema basado en SQL, existen muchos ORMs en el mercado actual, entre ellos Hibernate, ORM con el cual Spring se integra fácilmente.

La API de Persistencia de Java (JPA) es una especificación, es decir, es el estándar que define como se deben realizar las tareas de persistencia de datos en Java, por ello requerimos de un proveedor que implemente JPA, existen varios, como: Hibernate, EclipseLink, etc, para este tutoriales usaremos JPA con el ORM Hibernate, nos referimos a él como: Hibernate JPA.

Dependencias Spring Hibernate JPA

Como siempre creamos el proyecto Maven / Java Application y abrimos el archivo pom.xml para agregar las dependencias necesarias.

Las dependencias requeridas por Spring Framework son las siguientes:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>4.3.6.RELEASE</version>
</dependency>

Las dependencias necesarias para usar el ORM Hibernate JPA son:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>4.3.11.Final</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-entitymanager</artifactId>
    <version>4.3.11.Final</version>
</dependency>

Nuestros datos de prueba estarán en el motor HSQLDB, pera ello agregamos:

<dependency>
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    <version>2.3.3</version>
    <scope>runtime</scope>
</dependency>

En los tutoriales de acceso a datos con JDBC anteriores usamos HSQLDB en modo servidor, teníamos que iniciar el servidor para probar la aplicación, esta vez veremos como Spring nos permite configurar una base de datos integrada que será iniciada al momento de ejecutar la aplicación.

Usar una base de datos integrada

Spring soporta tres motores de bases de datos integradas, (HSQLDB, H2, DERBY), usaremos el primero, para configurarlo primero debemos crear el DataSource y lo haremos por medio de la clase EmbeddedDatabaseBuilder, de la siguiente manera:

@Bean
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:schema.sql")
            .addScript("classpath:data.sql")
            .build();
}

Usamos setType(EmbeddedDatabaseType.HSQL) para indicar la base de datos a usar, otras opciones son H2 y DERBY, si deseas usar alguna de estas recuerda agregar las respectivas dependencias.

El método addScript() nos permite indicar los script SQL schema.sql y data.sql, el primero es usado para crear la estructura de la base de datos, las tablas y columnas, etc., el segundo añade datos a las tablas recién creadas, ambos archivos se encuentran en la raíz del classpath.

Archivo schema.sql para crear dos tablas, Customer y Product, ambos tomados de los datos de prueba de HSQLDB.

CREATE TABLE Customer(
    ID INTEGER PRIMARY KEY, 
    FirstName VARCHAR(20),
    LastName VARCHAR(30), 
    Street VARCHAR(50), 
    City VARCHAR(25));

CREATE TABLE Product(
    ID INTEGER PRIMARY KEY, 
    Name VARCHAR(30), 
    Price DECIMAL);

Archivo data.sql que agrega datos a las tablas Customer y Product.

INSERT INTO Customer VALUES(0,'Laura','Steel','429 Seventh Av.','Dallas');
INSERT INTO Product  VALUES(0,'Iron Iron',54);
INSERT INTO Customer VALUES(1,'Susanne','King','366 - 20th Ave.','Olten');
INSERT INTO Product  VALUES(1,'Chair Shoe',248);
INSERT INTO Customer VALUES(2,'Anne','Miller','20 Upland Pl.','Lyon');
INSERT INTO Product  VALUES(2,'Telephone Clock',248);

Al iniciar la aplicación se ejecutan estos scripts, sobre una base de datos en memoria que será creada por Spring.

Configurar Hibernate JPA

Para acceder a la base de datos JPA utiliza la interface EntityManager, esta puede ser creada de varias maneras, en este tutorial usaremos la clase LocalContainerEntityManagerFactoryBean otra opción, por ejemplo, sería crearla a partir de JDNI para ello usaríamos JndiObjectFactoryBean.

Para finalizar debemos crear un bean de tipo JpaVendorAdapter que nos sirve para definir cual es el proveedor que utilizaremos, nosotros estamos usando Hibernate por lo que creamos este bean con la clase HibernateJpaVendorAdapter para otros proveedores tenemos las correspondientes clases: EclipseLinkJpaVendorAdapter y OpenJpaVendorAdapter.

@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
    LocalContainerEntityManagerFactoryBean factoryBean
            = new LocalContainerEntityManagerFactoryBean();
    factoryBean.setDataSource(dataSource());
    factoryBean.setJpaVendorAdapter(jpaVendorAdapter());
    factoryBean.setPackagesToScan("carmelo.spring.jpa.model");
    factoryBean.setPersistenceUnitName("pun-hibernate-jpa");
    return factoryBean;
}

@Bean
public JpaVendorAdapter jpaVendorAdapter() {
    HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
    jpaVendorAdapter.setGenerateDdl(false);
    jpaVendorAdapter.setDatabase(Database.HSQL);
    return jpaVendorAdapter;
}

Le prestamos atención al código: setPackagesToScan("carmelo.spring.jpa.model") con él indicamos en que paquete se encuentran las clases anotadas para JPA, con las correspondientes anotaciones indicamos como se mapea la clase contra la base de datos, para la tabla PRODUCT tenemos la clase:

package carmelo.spring.jpa.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "PRODUCT")
public class Product {

    @Id
    @Column(name = "ID")
    private Integer id;
    
    @Column(name = "NAME")
    private String name;
    
    @Column(name = "PRICE")
    private Double price;

}

Podemos ver las siguientes anotaciones:

  • @Entity establece que la clase Product representa una entidad.
  • @Table nos permite definir el nombre de la tabla representada por esta entidad.
  • @Column indica que el campo representa una columna de la tabla.
  • @Id esta columna es la clave primaria.

name = "..." indica el nombre de la tabla o columna, según la anotación.

Lo siguiente que vamos a hacer es implementar la interface ProductDao esta vez la implementación será con JPA, debemos tener presente que las consultas se hacen utilizando JPQL lenguaje de consultas usado por JPA, no utilizamos SQL como en los cursos anteriores. 

@Repository("productDaoJpa")
public class ProductDaoJpaImpl implements ProductDao {

    private EntityManager entityManager;

    @PersistenceContext
    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    @Override
    public List<Product> todos() {
        String query = "from Product";
        return entityManager.createQuery(query).getResultList();
    }

   //...//
}

Usamos la anotación @PersistenceContext para inyectar el EntityManager que requerimos para comunicarnos con la base de datos, lo siguiente que vemos es la implementación del método todos() que devuelve la lista de todos los productos en la BD, esta vez implementado con JPA. 

En la siguiente imagen podemos apreciar el código que teníamos anteriormente y la mejora utilizando un ORM con JPA, no debemos mapear datos de la tabla a objetos java ni viceversa, de esto se encarga el ORM Hibernate. 

spring hibernate jpa

Vemos ahora la implementación de los demás métodos del la interface.

Para buscar un producto por su ID usaremos el método find() indicamos primero el tipo y luego el ID que deseamos localizar, si no lo encuentra devolverá un valor nulo.

public Product buscar(Integer id) {
    return entityManager.find(Product.class, id);
}

Antes de continuar con los demás métodos de la interface ProductDao vamos a habilitar el administrador de transacciones, con él Spring administrará las transacciones por nosotros, nos olvidamos de los commit, rollback, etc.

@Configuration
@ComponentScan(basePackages = "carmelo.spring.jpa.dao")
@EnableTransactionManagement
public class SpringConfiguration {

    @Bean
    public DataSource dataSource() { ... }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() { ... }

    @Bean
    public JpaVendorAdapter jpaVendorAdapter() { ... }

    @Bean
    @Autowired
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory);
        return transactionManager;
    }
}

Usamos la clase JpaTransactionManajer para crear el PlatformTransactionManager a este debemos asociarle el EntityManagerFactory, para ello usamos @Autowired para inyectarlo, también es necesario agregar la anotación @EnableTransactionManager para activar esta característica.

Anotamos la clase ProductDaoJpaImpl con @Transactional para indicar que la misma utilizara el administrador de transacciones que acabamos de crear.

Hecho esto ya podemos ver los siguiente métodos que nos quedan por implementar, entre ellos, agregar un producto a la base de datos.

public void insertar(Product product) {
    entityManager.persist(product);
}

Aprovecharemos el método eliminar para ver como introducir parámetros a nuestra consultas JPQL, para indicar un parámetro dentro de una consulta usamos “:” seguido del nombre que le daremos. 

public void eliminar(Integer id) {
    String jpql = "select p from Product p where p.id = :id";
    Query query = entityManager.createQuery(jpql);
    query.setParameter("id", id);
    Product p = (Product) query.getSingleResult();
    
    entityManager.remove(p);
}

Primero buscamos el producto con el ID indicado, si vemos la consulta al final tenemos el parámetro :id para indicar el valor de este parámetro usamos setParameter() donde indicamos el nombre luego el valor, en este caso el ID.

Para actualizar un producto existente, por ejemplo, cambiar su precio.

public void actualizar(Product product) {
    entityManager.merge(product);
}

Finalizamos con método que cuenta la cantidad de productos presentes en la base de datos.

public Integer cantidad() {
     return entityManager
             .createQuery("select count(p) from Product p", Long.class)
             .getSingleResult().intValue();
 }

Quedamos aquí, por ahora, hemos podido ver como podemos aprovechar los beneficios de utilizar un ORM como Hibernate con la implementación de JPA, si comparamos el código de los tutoriales anteriores veremos como hemos logrado reducir el código repetitivo.

Comentarios

Temas relacionados

Entradas populares de este blog

tkinter Grid

Histogramas OpenCV Python

tkinter Canvas

Conectar SQL Server con Java