Implementando Hibernate con Spring Boot y PostgreSQL

A medida que el uso de software se vuelve más común y se construyen más y más sistemas para manejar diversas tareas, los datos juegan un papel más importante en el presente y el futuro...

Introducción

A medida que el uso de software se vuelve más común y se construyen más y más sistemas para manejar varias tareas, los datos juegan un papel más importante en la escena tecnológica actual y futura. La información es cada vez más valiosa a medida que avanza la tecnología y abre más oportunidades para su uso.

Es por esta razón, y muchas más, que el almacenamiento y la manipulación seguros de datos se han convertido en un aspecto importante de cualquier sistema o aplicación que se construya.

¿Qué es el mapeo relacional de objetos?

En muchos sistemas, los objetos de la vida real se modelan como objetos en sistemas para facilitar la representación y manipulación de sus atributos. Por ejemplo, un teléfono se puede modelar como un objeto con atributos como su nombre, sistema operativo, fabricante y mucho más, y esto se puede manipular y almacenar fácilmente en una base de datos.

El mapeo relacional de objetos (ORM) es una técnica de mapeo de dichos objetos y sus atributos en la base de datos a través de mapeadores relacionales de objetos. Esta técnica también nos ayuda a convertir datos entre sistemas incompatibles utilizando aplicaciones de programación orientada a objetos.

Un ORM es una biblioteca que nos ayuda a interactuar fácilmente con múltiples bases de datos o sistemas utilizando el idioma de nuestra elección. Nuestro código ahora está asignado al lenguaje de consulta específico de las bases de datos.

Normalmente, necesitamos usar un lenguaje específico de base de datos para interactuar con una base de datos. Por ejemplo, para interactuar con una base de datos mysql, necesitamos usar el lenguaje de consulta estructurado (SQL), pero estos lenguajes pueden diferir de una plataforma a otra.

Por ejemplo, si bien siguen siendo similares, la sintaxis de una base de datos postgres es diferente del lenguaje de consulta utilizado en una base de datos [SQL de Microsoft](https://www. microsoft.com/en-us/sql-server/sql-server-2019) base de datos. Un ORM ayuda a salvar esa diferencia y conecta nuestro software a diferentes sistemas de bases de datos con facilidad.

Otros beneficios de usar un ORM incluyen acelerar el proceso de desarrollo ya que los desarrolladores no tienen que escribir el código de acceso a la base de datos y repetirlo cada vez que quieren acceder a una base de datos. Una vez que se diseña un modelo y se escribe el código de manipulación, no es necesario volver a hacerlo, lo que hace que el código sea fácil de actualizar, mantener y reutilizar.

Sin embargo, existen algunos inconvenientes asociados con los ORM e incluyen:

  • Los ORM tienden a ser lentos en algunas situaciones en cuanto al rendimiento.
  • Para consultas complejas como uniones, los ORM a veces no pueden sustituir consultas SQL sin procesar
  • Debido a las abstracciones introducidas por un ORM, el desarrollador puede perder la comprensión de SQL y cómo se logra la administración de la base de datos entre bastidores.

Hibernar

Hibernar es un marco que permite a los desarrolladores conservar fácilmente los datos de la aplicación en bases de datos relacionales mediante JDBC. Es una implementación de la API de persistencia de Java (JPA), lo que significa que se puede utilizar en cualquier sistema que admita JPA, como la edición estándar (Java SE) y la edición empresarial (Java EE).

Hibernate es una herramienta ligera y de código abierto que simplifica la creación, la manipulación y el acceso a datos de una base de datos en aplicaciones basadas en Java. Funciona asignando un objeto creado a partir de una clase Java y sus atributos a los datos almacenados en la base de datos.

Algunas ventajas de usar Hibernate incluyen:

  • Es de código abierto y liviano, lo que significa que es de uso gratuito y tiene una comunidad de colaboradores que lo mejora constantemente.
  • Hibernate utiliza un caché interno que mejora su rendimiento
  • Es independiente de la base de datos, lo que significa que puede usarse para acceder y manipular datos en varias bases de datos diferentes
  • Proporciona la funcionalidad para simplificar las uniones al obtener datos de varias tablas
  • Al crear tablas automáticamente, el desarrollador puede enfocarse en hacer otra lógica
  • Es un marco estable que existe desde hace 18 años.

Alternativas

Hibernate no es el único marco ORM que podemos usar en nuestras aplicaciones Java, otros incluyen:

  • jooq (Java Object Oriented Querying) es una biblioteca de software ligera de mapeo de bases de datos
  • JDBI brinda acceso a datos relacionales en Java de manera conveniente
  • Mi Batis es un framework mapeador SQL para integrar bases de datos relacionales
  • Ebean que se puede usar para aplicaciones basadas en Java y Kotlin
  • ORMLite que es un marco ligero para conservar objetos Java en bases de datos SQL

Estas son solo algunas de las alternativas para Hibernate, definitivamente hay más bibliotecas y marcos que se adaptan a muchos escenarios y bases de datos diferentes.

Implementación de Hibernate con Spring Boot

Configuración del proyecto

Para este proyecto de demostración, vamos a utilizar una base de datos PostgreSQL y las instrucciones de instalación se pueden encontrar aquí para las plataformas Mac OS, Linux y Windows.

Una vez configurado, podemos crear nuestra base de datos de demostración, phonesdemo. Administrador de página proporciona una interfaz de usuario para interactuar con una base de datos PostgreSQL, pero también se puede usar una terminal.

Veamos ahora Hibernate en acción usándolo en una muestra de la API Spring Boot, que arrancaremos con la herramienta Spring Initializr:

spring_intializr

Usaremos Java 8 y Maven para nuestra gestión de dependencias con algunas dependencias:

  • Spring Web Starter para ayudarnos a construir una aplicación basada en web
  • Spring Data JPA para proporcionar la API de acceso a datos que utilizará Hibernate
  • Base de datos H2 para traer la funcionalidad de Hibernate a nuestro proyecto
  • PostgreSQL para permitirnos conectarnos a una base de datos PostgreSQL

Una vez que hagamos clic en "Generar", recibiremos un archivo zip que contiene el proyecto y podemos comenzar a implementar la aplicación web que requiere una base de datos. Una vez que desempaquetamos este archivo zip en nuestra carpeta de trabajo, podemos probar que está listo para trabajar ejecutando el comando:

1
$ mvn spring-boot:run

Implementación

Modifiquemos nuestras application.properties para incluir los detalles de nuestra base de datos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Database Properties
spring.datasource.url=jdbc:postgresql://localhost:5432/phonesdemo
spring.datasource.username=postgres
spring.datasource.password=

# Hibernate Properties
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQL92Dialect

# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto=update

Hibernate proporciona una consola H2 que podemos usar para verificar el estado de la base de datos e incluso realizar la entrada de datos a través de una interfaz de usuario. Lo habilitamos agregando la siguiente línea a nuestra application.properties:

1
spring.h2.console.enabled=true

Luego iniciamos nuestra aplicación y navegamos a http://localhost:8080/h2-console para probar si todo funciona. Obtenemos una página donde podemos probar si la conexión a nuestra base de datos funciona:

h2_database

Para el menú desplegable de configuraciones guardadas, elegiremos Generic PostgreSQL y también actualizaremos la URL de JDBC para que coincida con el nombre de nuestra base de datos de prueba. Luego completamos el nombre de usuario y la contraseña de nuestra base de datos y hacemos clic en "Probar conexión" solo para asegurarnos de que nuestra aplicación Spring pueda conectarse a nuestra base de datos. Si todo está bien configurado, recibimos un mensaje de éxito.

Si hacemos clic en "Conectar", obtenemos esta página:

h2_console_full

Aquí podemos navegar por nuestra base de datos e incluso realizar consultas SQL.

La API que construiremos se usará para almacenar y manipular teléfonos y sus atributos, como el nombre y el sistema operativo. Con nuestra base de datos en su lugar y conectada, creemos una clase de entidad (Phone.java) que asignará los atributos de nuestro objeto a la base de datos y nos permitirá realizar operaciones CRUD en la base de datos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// Imports truncated for brevity, refer to GitHub link below for the full code

@Entity
@Table(name = "phones")
@EntityListeners(AuditingEntityListener.class)
public class Phone {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id; // Each phone will be given an auto-generated unique identifier when stored

    @Column(name = "phone_name", nullable = false)
    private String phoneName; // Save the name of the phone

    @Column(name = "os", nullable = false)
    private String os; // Save the operating system running in the phone
    
    // Standard getters and setters
}

La anotación @Entity le dice a Hibernate que esta clase representa una entidad que debe persistir.

La anotación @Table se usa para nombrar la tabla. Si se omite esta anotación, la tabla simplemente usará el nombre de la clase/entidad.

Del mismo modo, las anotaciones @Column también se pueden omitir, pero las columnas de la base de datos usarán los nombres de los campos tal como son y, a veces, este no es el comportamiento preferido, ya que los nombres de las columnas pueden ser mayúsculas y minúsculas y los nombres de los campos son mayúsculas y minúsculas, por ejemplo.

Cuando guardemos este archivo y reiniciemos nuestro servidor y verifiquemos nuestra base de datos, habrá una nueva tabla llamada phones y las columnas id, phone_name y os estarán presentes.

No habrá datos pero esto es Hibernate en el trabajo, si la tabla especificada en la clase de entidad no existe, Hibernate la creará para nosotros.

Ahora implementemos el controlador para ayudarnos a realizar operaciones en nuestra base de datos a través de una API:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
@RestController
@RequestMapping("/api/v1")
public class PhoneController {
    @Autowired
    private PhoneRepository phoneRepository;

    // GET method to fetch all phones
    @GetMapping("/phones")
    public List<Phone> getAllPhones() {
        return phoneRepository.findAll();
    }

    // GET method to fetch phone by Id
    @GetMapping("/phones/{id}")
    public ResponseEntity<Phone> getPhoneById(@PathVariable(value = "id") Long phoneId)
        throws Exception {
        Phone phone = phoneRepository.findById(phoneId)
               .orElseThrow(() -> new Exception("Phone " + phoneId + " not found"));
        return ResponseEntity.ok().body(phone);
    }
  
    // POST method to create a phone
    @PostMapping("/phones")
    public Phone createPhone(@Valid @RequestBody Phone phone) {
        return phoneRepository.save(phone);
    }
  
    // PUT method to update a phone's details
    @PutMapping("/phones/{id}")
    public ResponseEntity<Phone> updatePhone(
        @PathVariable(value="id") Long phoneId, @Valid @RequestBody Phone phoneDetails
    ) throws Exception {
        Phone phone = phoneRepository.findById(phoneId)
            .orElseThrow(() -> new Exception("Phone " + phoneId + " not found"));

        phone.setPhoneName(phoneDetails.getPhoneName());
        phone.setOs(phoneDetails.getOs());

        final Phone updatedPhone = phoneRepository.save(phone);
        return ResponseEntity.ok(updatedPhone);
    }
  
    // DELETE method to delete a phone
    @DeleteMapping("/phone/{id}")
    public Map<String, Boolean> deletePhone(@PathVariable(value="id") Long phoneId) throws Exception {
        Phone phone = phoneRepository.findById(phoneId)
            .orElseThrow(() -> new Exception("Phone " + phoneId + " not found"));

        phoneRepository.delete(phone);
        Map<String, Boolean> response = new HashMap<>();
        response.put("deleted", Boolean.TRUE);
        return response;
    }
}

En nuestra clase de controlador, anotamos nuestra clase con @RestController para indicar que esta es la clase de controlador de solicitudes que manejará la funcionalidad REST para nuestra API. Luego definimos métodos para manejar cada una de las cuatro operaciones RESTful: GET, POST, PUT y DELETE. Estos métodos proporcionarán una interfaz para que interactuemos con nuestra API y gestionemos los datos.

Nuestra API, a su vez, utilizará Hibernate para reflejar nuestras operaciones sobre dichos datos en nuestra base de datos.

Comencemos por crear un solo teléfono a través de nuestra API:

phone_create

Recibimos la respuesta de nuestra API, pero revisemos la base de datos usando la Consola H2 para confirmar:

database_confirm

Como puede ver, en la captura de pantalla anterior, para obtener los datos en nuestra base de datos, usamos el comando SQL SELECT * FROM phone. Para conseguir lo mismo en nuestro código a través del ORM, es tan sencillo como utilizar la línea:

1
phoneRepository.findAll();

Esto nos resulta más amigable y familiar ya que se logra en el mismo lenguaje que estamos usando mientras implementamos el resto de nuestro proyecto.

Conclusión

Creamos con éxito un objeto de teléfono y guardamos sus atributos en nuestra base de datos PostgreSQL usando Hibernate en nuestra API Spring Boot. Podemos agregar más teléfonos, eliminar teléfonos y actualizar los datos del teléfono interactuando con la API e Hibernate reflejará los cambios en nuestra base de datos.

Hibernate nos ha facilitado la interacción con una base de datos desde nuestra aplicación Spring Boot y la gestión de nuestros datos. El tiempo de desarrollo también se ha reducido significativamente ya que no tenemos que escribir los comandos SQL para administrar los datos nosotros mismos.

El código fuente de este proyecto está disponible aquí en GitHub. -example).