Jakarta EE 11 - Jakarta Data - Parte 1


 A la fecha de esta publicación, aún no se ha lanzado oficialmente Jakarta EE 11. Pero ya hay unos avances de ciertas especificaciones, como Jakarta Data la que yo considero una de las más interesantes e importantes.

En este post veremos cómo podemos configurar nuestro proyecto con Jakarta EE 11 + Jakarta Data, utilizando la implementación de Hibernate sobre Payara Server

 

Creación del proyecto

Para comenzar, crearemos un proyecto utilizando un arquetipo de Maven:

mvn archetype:generate \
  -DarchetypeGroupId=com.apuntesdejava \
  -DarchetypeArtifactId=jakarta-ee-essentials \
  -DarchetypeVersion=0.0.2 \
  -DjakartaProfile=core

Con este comando mostrará la siguiente pantalla para completar los parámetros como groupId, artifactId, version y package


 

Que finalmente creará el proyecto con la siguiente estructura:


 Ese arquetipo lo he creado, y el detalle de sus parámetros lo pueden encontrar aquí: https://jakarta-coffee-builder.github.io/pages/archetype.html

Agregando dependencias

 Comenzaremos por agregar las dependencias necesarias. Necesitaremos:

  • El driver de la base de datos. Para este ejemplo usaremos h2.
  • Hibernate ORM
  • La declaración de jakarta.data-api
     <properties>
<!-- otras propiedades -->
        <hibernate.version>6.6.9.Final</hibernate.version>
    </properties>

    <dependencies>
<!-- ... otras dependencias -->
        <dependency>
            <groupId>jakarta.data</groupId>
            <artifactId>jakarta.data-api</artifactId>
            <version>1.0.1</version>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>2.3.232</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate.orm</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate.version}</version>
        </dependency>

Y debemos configurar el plugin para generar el código para Hibernate

    <build>
        <plugins>
            <!-- otros plugins -->
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.13.0</version>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.hibernate.orm</groupId>
                            <artifactId>hibernate-jpamodelgen</artifactId>
                            <version>${hibernate.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>

Configuración del DataSource

Existen dos maneras estándar para declarar un datasource: en el archivo web.xml y declarándolo en un clase. Esta vez lo haremos en el archivo web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
         version="6.0">

    <data-source>
        <name>java:global/ExampleDataSource</name>
        <class-name>org.h2.jdbcx.JdbcDataSource</class-name>
        <url>jdbc:h2:mem:</url>
        <user>sa</user>
        <password>sa</password>
        <property>
            <name>fish.payara.log-jdbc-calls</name>
            <value>true</value>
        </property>
    </data-source>

</web-app>

Configuración de persistence.xml

Ahora, como toda configuración de persistencia, necesitamos establecer la Unidad de Persisencia y asociarla al DataSource

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd"
             version="3.0">
    <persistence-unit name="defaultPU">
        <!-- utilizar el provider de hibernate -->
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <jta-data-source>java:global/ExampleDataSource</jta-data-source>
        <properties>
            <!-- estos son necesarios -->
            <property name="hibernate.enhancer.enableDirtyTracking" value="false"/>
            <property name="hibernate.enhancer.enableLazyInitialization" value="false"/>
            <property name="hibernate.transaction.jta.platform" value="org.hibernate.service.jta.platform.internal.SunOneJtaPlatform"/>

            <!-- dicen que no es necesario este campo, pero si no se pone, no funciona -->
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
             
             <!-- otros atributos -->
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.hbm2ddl.auto" value="create"/>
        </properties>
    </persistence-unit>
</persistence>

El endpoint

Necesitamos manejar el endpoint, así que tendremos las siguientes record para ser usados como request y response

CoffeeRequest.java

package com.example.example.jakarta.data.dto;

public record CoffeeRequest(String name, Double price) {

}

CoffeeResponse

package com.example.example.jakarta.data.dto;

import com.example.example.jakarta.data.entity.CoffeeEntity;

public record CoffeeResponse(Long id, String name, Double price) {

    public static CoffeeResponse of(CoffeeEntity coffeeEntity) {
        return new CoffeeResponse(coffeeEntity.getId(),
                coffeeEntity.getName(),
                coffeeEntity.getPrice());
    }

}

También necesitaremos el endpoint en sí:

CoffeeResource.java

package com.example.example.jakarta.data.resources;

import com.example.example.jakarta.data.dto.CoffeeRequest;
import com.example.example.jakarta.data.service.CoffeeService;
import jakarta.inject.Inject;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
import jakarta.ws.rs.core.Response;

@Path("coffee")
@Produces(APPLICATION_JSON)
@Consumes(APPLICATION_JSON)
public class CoffeeResource {

    @Inject
    private CoffeeService coffeeService;

    @POST
    public Response save(CoffeeRequest coffeeRequest) {
        var coffeeSaved = coffeeService.create(coffeeRequest);
        return Response.ok(coffeeSaved).build();
    }

    @GET
    public Response list() {
        var coffeeList = coffeeService.listAll();
        return Response.ok(coffeeList).build();
    }

}

La clase CoffeeService se encargará de hacer convertir las entidades a las clases request y response, ya que no deberíamos exponer la entidad en sí.

Por tanto, ésta es la clase:

CoffeeService.java

package com.example.example.jakarta.data.service;

import com.example.example.jakarta.data.dto.CoffeeRequest;
import com.example.example.jakarta.data.dto.CoffeeResponse;
import com.example.example.jakarta.data.entity.CoffeeEntity;
import com.example.example.jakarta.data.repository.CoffeeRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.util.List;

@ApplicationScoped
public class CoffeeService {

    @Inject
    private CoffeeRepository coffeeRepository;

    public List<CoffeeResponse> listAll() {
        return coffeeRepository.findAll()
                .map(CoffeeResponse::of)
                .toList();
    }

    public CoffeeResponse create(CoffeeRequest coffeeRequest) {
        var coffeeEntity = new CoffeeEntity();
        coffeeEntity.setName(coffeeRequest.name());
        coffeeEntity.setPrice(coffeeRequest.price());
        var saved = coffeeRepository.save(coffeeEntity);
        return CoffeeResponse.of(saved);
    }
}

La capa de persistencia

Ahora bien, esta es la capa de persistencia en sí. Solo heredar la interfaz jakarta.data.repository.CrudRepository y tendríamos todas las funcionalidades ya hechas

Entidad CoffeeEntity.java

package com.example.example.jakarta.data.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import static jakarta.persistence.GenerationType.IDENTITY;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "coffee")
public class CoffeeEntity {
    
    @Id
    @GeneratedValue(strategy = IDENTITY)
    private Long id;

    @Column (
        name = "coffee_name",
        length = 100,
        unique = true,
        nullable = false
    )
    private String name;

    private Double price;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }    
    
}

Repositorio CoffeeRepository.java

package com.example.example.jakarta.data.repository;

import com.example.example.jakarta.data.entity.CoffeeEntity;
import jakarta.data.repository.CrudRepository;
import jakarta.data.repository.Repository;

@Repository
public interface CoffeeRepository extends CrudRepository<CoffeeEntity, Long>{
    
}

Código fuente

El código fuente está disponible aquí: https://github.com/apuntesdejava/example-jakarta-data y también incluye ejemplos de cómo invocar a los endpoint.

Vídeo

También hay una explicación en vivo de este código

Comentarios

Entradas más populares de este blog

Cambiar ícono a un JFrame

Portales en Java

UML en NetBeans