Programación Funcional en Kotlin: Lambdas Explicadas – Parte 1

La adopción de expresiones lambda en Kotlin ha revolucionado la manera en que desarrollamos software. Ofrecen un mecanismo poderoso y expresivo para manipular datos y comportamientos de manera concisa. Este post se sumerge en las profundidades de las lambdas en Kotlin, explorando su sintaxis, uso práctico y la potente combinación con referencias a miembros y funciones de la biblioteca estándar de Kotlin.

¿Qué son las Lambdas?

Las lambdas, en esencia, son funciones anónimas que pueden ser usadas como expresiones. Permiten encapsular una porción de código para ser ejecutada en el futuro, posibilitando un código más limpio y modular. Kotlin no solo soporta lambdas sino que las integra como un componente fundamental del lenguaje, abriendo un mundo de posibilidades para los desarrolladores.

Caso de Uso: Manipulación de Eventos

Consideremos el caso de asignar un manejador de eventos a un botón en una interfaz gráfica:

button.setOnClickListener(object: OnClickListener {
override fun onClick(v: View) {
println("El botón fue clickeado!")
}
})

Aunque funcional, este enfoque es verboso. Kotlin nos permite simplificarlo considerablemente mediante el uso de lambdas:

button.setOnClickListener { println("El botón fue clickeado!") }

Esta simplificación no solo reduce la cantidad de código sino que lo hace más legible y directo.

Lambdas y Colecciones

Una de las fortalezas de Kotlin es cómo las lambdas se integran con las colecciones, facilitando operaciones que en otros lenguajes serían tediosas y verbosas.

Ejemplo: Encontrando el Mayor Elemento

Imaginemos que tenemos una lista de Producto y queremos encontrar el de mayor precio. Sin el uso de lambdas, necesitaríamos implementar un bucle para recorrer manualmente la lista:

fun findTheMostExpensiveProduct(products: List) {
   var highestPrice = 0.0
   var theMostExpensive: Product? = null
   for (product in products) {
      if (product.price > highestPrice) {
         highestPrice = product.price 
         theMostExpensive = product
      }
   }
   println(theMostExpensive)
}

Este enfoque, resulta verboso y alejado de lo que realmente queremos expresar: simplemente encontrar el producto más caro.

Con lambdas, Kotlin nos brinda una solución más elegante y directamente alineada con nuestra intención:

println(products.maxByOrNull { it.price })

En esta versión, la lambda pasada a maxByOrNull especifica que queremos comparar los productos por su propiedad price. Esto no solo reduce significativamente la cantidad de código sino que lo hace más declarativo, expresando claramente nuestra intención sin distracciones.

Simplificación de Llamadas con Lambdas en Kotlin

Kotlin proporciona una serie de optimizaciones sintácticas que nos permiten escribir código más limpio y conciso, especialmente cuando trabajamos con colecciones y funciones que requieren de predicados o acciones como parámetros. Un excelente ejemplo de esto es cuando queremos determinar el producto más caro de una lista utilizando la función maxByOrNull.

El texto que que acompaña este segmento ilustra cómo Kotlin nos brinda distintas maneras de formular una lambda que elige la propiedad a comparar, en este caso, el precio de un producto.

Examinemos cómo estas simplificaciones se aplican en la práctica con una lista de productos:

  1. products.maxByOrNull({ p: Product -> p.price }) – La lambda completa con tipo explícito.
  2. products.maxByOrNull() { p: Product -> p.price } – Movemos la lambda fuera de los paréntesis.
  3. products.maxByOrNull { p: Product -> p.price } – Kotlin permite omitir los paréntesis si la lambda es el único argumento.
  4. products.maxByOrNull { p -> p.price } – Cuando el contexto es claro, podemos omitir el tipo del parámetro.
  5. products.maxByOrNull { it.price } – Para un solo parámetro, usamos el nombre implícito ‘it’ para más concisión.
  6. products.maxByOrNull(Product::price) – Una referencia a miembro para la máxima simplificación y claridad.

Es crucial recordar que, aunque la convención de it es útil para acortar el código, su uso debe ser medido. En situaciones con lambdas anidadas, puede volverse ambiguo qué valor representa it. En tales casos, es mejor declarar los parámetros de cada lambda explícitamente. Además, si el significado o tipo del parámetro no es claro por el contexto, también es preferible una declaración explícita.

Este enfoque no solo mejora la legibilidad del código sino que también evita errores comunes que pueden ocurrir al manipular las colecciones, permitiéndonos escribir expresiones que son más declarativas y cercanas a nuestra intención original.

Referencias a Miembros: Maximizando la Simplicidad

Kotlin va más allá de las expresiones lambda al introducir las referencias a miembros, una característica que permite referenciar métodos o propiedades directamente, simplificando aún más el código cuando queremos acceder a ellos.

Sintaxis y Uso

La sintaxis para utilizar una referencia a miembro es sencilla: solo necesitamos el operador ::. Por ejemplo, si queremos referenciar la propiedad price de un objeto Product, lo haríamos de la siguiente manera: Product::price.

Imaginemos que tenemos una lista de productos y queremos obtener una lista de sus precios. En lugar de definir una lambda que acceda a la propiedad price de cada producto, podemos usar una referencia a miembro para hacerlo de forma más directa y legible:

val products = listOf(Product("Laptop", 999.99), Product("Smartphone", 499.99))
val prices = products.map(Product::price)

Este método no solo resulta ser más conciso, sino que también mejora la legibilidad al reducir la necesidad de declarar explícitamente una función lambda solo para acceder a una propiedad. Así, las referencias a miembros en Kotlin nos ofrecen una forma elegante y eficiente de trabajar con propiedades y métodos de objetos.

La imagen de la figura #1 que se muestra a continuación ilustra visualmente cómo se forma una referencia a miembro en Kotlin. Como puede verse, la sintaxis utiliza el operador :: para vincular directamente la clase Producto con su miembro precio, destacando la simplicidad y la potencia de este enfoque.

Figura #1

Este diagrama muestra la estructura de Producto::precio, donde Producto es la clase y precio es el atributo que queremos referenciar. La separación por dos puntos dobles es lo que define la referencia a miembro en Kotlin, proporcionándonos una manera directa y legible de acceder a las propiedades de nuestros objetos dentro de diversas operaciones funcionales.

Lambdas en Kotlin con Receptores

Kotlin también introduce el concepto de lambdas con receptores, lo que nos permite llamar métodos en un objeto específico dentro de la lambda, extendiendo aún más la flexibilidad y potencia de las lambdas.

Ejemplo Práctico: Configuración de Objetos

Un caso de uso común es la configuración de objetos al momento de su creación. Kotlin nos facilita este patrón a través de la función apply:

val myTextView = TextView(context).apply { 
text = "Texto de ejemplo"
textSize = 16f
setPadding(10, 0, 10, 0)
}

Aquí, apply nos permite configurar el objeto TextView recién creado de una manera fluida y natural, manteniendo nuestro código limpio y conciso.

Conclusión

Las lambdas y referencias a miembros en Kotlin son herramientas poderosas que te permiten escribir código más limpio, conciso y expresivo. Al integrar estas características de manera profunda en el lenguaje, Kotlin no solo facilita patrones comunes de programación sino que abre nuevas posibilidades para la abstracción y reutilización de código. Ya sea que estés manipulando colecciones, gestionando eventos de usuario o simplemente necesitas pasar comportamiento alrededor, las lambdas en Kotlin te ofrecen una forma elegante y eficiente de hacerlo.

Referencias:

https://www.manning.com/books/kotlin-in-action

Programación Funcional con Lambdas en Kotlin: Parte 2

Lambdas en Kotlin

Dominando más funciones Lambdas en Kotlin: ‘with’, ‘apply’ y ‘also’

En esta segunda parte exporaremos las características y uso de más funcioes Lambdas en Kotlin.

Ejecutando múltiples acciones sobre un objeto: with

La función with de Kotlin es una joya para quienes buscan claridad y concisión en su código. A diferencia de muchos lenguajes que requieren estructuras verbosas, with permite actuar sobre un objeto con múltiples operaciones en un contexto temporal, sin tener que repetir el nombre del objeto. Esto es especialmente útil en situaciones donde se requiere configurar un objeto con varios pasos de inicialización o modificaciones.

Aplicación Práctica de with con Lambdas en Kotlin

Imagina que necesitas componer un informe detallado de un producto, con varias características que debes concatenar. Podrías hacerlo con una serie de operaciones sobre un StringBuilder, pero with te permite hacerlo de forma más elegante:

fun productReport(product: Product): String = with(StringBuilder()) {
    append("Reporte del Producto: ${product.name}\n")
    append("Precio: ${product.price}\n")
    append("Categoría: ${product.category}\n")
    append("Calificación: ${product.rating}\n")
    toString()
}

En este refactorizado, StringBuilder actúa como el receptor dentro de la lambda de with, permitiéndote llamar directamente a append sin repetir result.

Refinamiento con with

La llamada a with se presenta tan natural en el código que parece ser parte del lenguaje mismo. La primera entrada para with define el contexto del receptor, mientras que la segunda entrada es la lambda que describe las operaciones a realizar.

Lambdas en Kotlin con Receptor: Extensión de Funcionalidad

El paradigma de las lambdas con receptor se extiende más allá del uso de with. Es similar a escribir una función de extensión: proporciona un receptor implícito, permitiendo un acceso directo a sus miembros y métodos.

Inicialización y Configuración con apply

apply lleva la inicialización de objetos a otro nivel en Kotlin. Es ideal cuando tienes un objeto y quieres aplicarle una serie de operaciones de configuración antes de utilizarlo.

Digamos que estás estableciendo un DataLogger para registrar información en un archivo. Con apply, puedes configurarlo de manera limpia y legible:

val dataLogger = DataLogger().apply {
    initialize()
    setFormat("csv")
    setFileName("log_${getCurrentDate()}.csv")
    start()
}

El resultado de apply es el propio objeto DataLogger, ya configurado y listo para usar.

Uso Práctico de apply

La capacidad de apply para devolver el objeto configurado lo hace ideal en la creación de objetos detallados. Por ejemplo, si estás configurando una instancia de un objeto User, apply te permite hacerlo todo en una declaración:

val newUser = User().apply {
    name = "John Doe"
    age = 30
    email = "john.doe@example.com"
    subscribeToNewsletter = true
}

Ejecución de acciones adicionales: also

Con also, Kotlin proporciona una manera de realizar operaciones adicionales en objetos sin alterar el flujo principal del código. Es similar a apply, pero en lugar de usar el receptor implícito this, usa el parámetro it, que representa el objeto.

Refactorización con also

Supongamos que estás procesando una lista de países para un análisis geopolítico. Deseas registrar cada paso sin modificar la lista principal:

val countries = listOf("Colombia", "Nicaragua", "Peru", "Brasil")                
val processedCountries = countries
    .filter { it.length > 5 }
    .also { log("Países con nombres largos: $it") }
    .map { it.uppercase() }
    .also { log("Países en mayúsculas: $it") }
    .sorted()

Aquí, also se usa para efectos colaterales como el registro o la depuración, permitiendo una visión clara de cada etapa del procesamiento.

Conclusión de Lambdas en Kotlin

En la continuación de nuestra exploración de la programación funcional en Kotlin, nos sumergimos más profundamente en cómo Kotlin simplifica la escritura de código claro y eficiente. En esta segunda parte, descubrimos las potentes funciones ‘with’, ‘apply’ y ‘also’ que Kotlin ofrece para realizar operaciones en los objetos sin la redundancia del código imperativo.

Exploramos ‘with’ para ejecutar múltiples acciones en un objeto de manera compacta, ‘apply’ para la inicialización y configuración eficiente de objetos, y ‘also’ para acciones adicionales que queremos realizar en objetos mientras mantenemos el flujo de operaciones inmutable. A través de ejemplos prácticos y una refactorización cuidadosa, mostramos cómo estas funciones mejoran la legibilidad del código y la expresividad del lenguaje. Este conocimiento avanzado eleva nuestras habilidades en Kotlin y nos prepara para construir aplicaciones robustas y mantenibles.

Referencias:

https://www.manning.com/books/kotlin-in-action

Gestión de Transacciones en Jakarta EE: Lo básico

Gestión de Transacciones en Jakarta EE: Lo básico

La gestión de transacciones es fundamental e integral en las aplicaciones empresariales para garantizar la preservación de la consistencia e integridad de los datos. Una transacción, desde el punto de vista de una base de datos, representa una unidad lógica de trabajo que abarca una o múltiples operaciones de base datos(insertar, update, borrar).

Jakarta EE, al igual que su predecesor JavaEE, ofrece una abstracción integral para gestionar transacciones, ya sea programáticamente a través de las API de JTA o de manera declarativa mediante anotaciones. Esta última es más popular debido a su simplicidad y la capacidad de mantener límites de transacción claros sin abrumar el código lógico del negocio. En este artículo, exploraremos la anotación @Transactional en Jakarta EE, y veremos  diferentes comportamientos bajo varias configuraciones y algunas mejores prácticas.

La Anotación @Transactional

La anotación @Transactional es la que ofrece las capacidades de gestión de transacciones declarativas de Jakarta EE. Básicamente, cuando un método está decorado con @Transactional, Jakarta EE se asegura de que se inicie una transacción antes de la ejecución del método y posteriormente se confirme si la ejecución continúa normalmente, o se revierta si se produce una excepción.

Por defecto, la anotación @Transactional tiene un comportamiento de propagación REQUERIDO. Es decir, Si hay una transacción activa, únase a ella. Si no hay una transacción activa, inicie una nueva.

Vease:

https://jakarta.ee/specifications/transactions/2.0/apidocs/jakarta/transaction/transactional

Ejemplo:

@Stateless
public class MyService {

  @Inject
  private MyRepository repository;
  
  @Transactional
  public void performBusinessOperation() {
    // lógica del negocio
    repository.save(entity);
  }
}

Repository Code.

@Stateless
public class MyRepository {

  @PersistenceContext
  private EntityManager entityManager;
  
  @Transactional
  public void save(MyEntity entity) {
    entityManager.persist(entity);
  }
}

En el ejemplo anterior, invocar performBusinessOperation() inicializa una nueva transacción. Cuando save() se llama posteriormente dentro de esa transacción, save() se une a la transacción existente en lugar de iniciar una nueva debido a su comportamiento de propagación REQUERIDO por defecto.

Sin embargo, vale la pena señalar que el uso superfluo de anotaciones @Transactional, especialmente cuando las transacciones ya están definidas en la capa de servicio, puede provocar confusión y comportamientos no anticipados. Por lo tanto, generalmente es una buena práctica definir transacciones a nivel de operaciones comerciales (como en la capa de servicio).

Manejo de errores (RollBack/Deshacer)

La anotacion @Transactional no es magica, hay alcances y caracteristicas que se deben manejar como el asilamiento de la persistencia de datos y el RollBack, esto se logra lanzando excpeciones del tipo Runtime, si usa una excepcion custom, esta debe heredar o ser del tipo Runtime.

Mejores Prácticas

Los límites de transacción deben reflejar los requisitos comerciales. Por lo general, es recomendable iniciar una nueva transacción para cada operación comercial distinta. Las demarcaciones de transacciones suelen ser mejor ubicadas en la capa de servicio, que se alinea bien con la comprensión de las operaciones comerciales. Por otro lado, anotar cada método a nivel de repositorio puede hacer que cada operación CRUD sea transaccional y, en consecuencia, puede crear una multitud de transacciones breves.

Sin embargo, vale la pena considerar el costo de una transacción. Cuanto más larga sea una transacción, más tiempo persistirán los bloqueos en los datos, limitando las posibilidades de acceso concurrente a los datos.

Conclusión

Todo lo que es entendible es hackeable: Un sólido entendimiento de las transacciones y @Transactional es indispensable para diseñar aplicaciones empresariales robustas. Seleccionar límites y configuraciones precisas para sus transacciones es crucial para mantener la integridad de los datos, el rendimiento y la escalabilidad de sus aplicaciones. Si bien Jakarta EE simplifica significativamente las complejidades de la gestión con la anotación @Transactional, ser consciente de sus comportamientos bajo diferentes configuraciones es crucial.


Jakarta EE: Aplicación monolitica con Java Server Faces.

Con el auge de los microservicios se podria pensar que el tipo aplicaciones monolitcas, ntier, hibridas o distribuiridas estan muertas, pero no es así, estas aun tienen una gran parte del mercado y son una solucion viable, sobre todo para aplicaciones pequeñas , empresas pequeñas, startups o midlewares medianos.

¿que es una aplicación monolitica?

Una aplicacion monolitica es aquella cuyos componentes estan estrechamente ligados de tal forma que cada uno de las clases o archivos dependen uno del otro. Una aplicacion monolitica puede estar formada por 1 o cientos de archivos en una sola carpeta, y para poder hacer funcionar se requieren todo los archivos o componente, logicamente representan toda la solución, en Java, son entregados en un archivo JAR , WAR o EAR.

¿Que es Java Server Faces?

JavaServer Faces (JSF) es un framework de desarrollo de aplicaciones web Java que se utiliza para simplificar la creación de interfaces de usuario en aplicaciones web utilizando el concepto llamado: Server side render.

¿que es “Server-Side Rendering” (SSR)?

Es una técnica utilizada en el desarrollo web para generar y renderizar contenido web en el servidor antes de enviarlo al navegador del usuario. A diferencia de las aplicaciones de representación del lado del cliente (Client-Side Rendering o CSR), donde la mayor parte del procesamiento y la renderización ocurre en el navegador del cliente, en SSR, gran parte de este trabajo se realiza en el servidor.

Esto tiene la notable ventaja que los componentes visuales pueden serreutilizados , ademas permite al equipo de desarrollo centrarse en la logica de negocio y no en la apariencia, permitiendo acelerar el desarrollo y simplicar el mantenimiento.

Esta tecnologia fué un BOOM en el 2005 al 2015, actualmetne con el auge de las apliaciones distribuidas, los microservicios y los servicios rest que permitieron la proliferacion de los frameworks javascript estan cada vez en menos uso, pero son bien apreciados en muchas empresas.

Frameworks que utilizan SSR

  • JavaServer Faces (JSF): es un marco de desarrollo web Java que permite la creación de aplicaciones web con representación del lado del servidor.
  • Thymeleaf: Thymeleaf es un motor de plantillas Java que permite la creación de páginas web con representación del lado del servidor. Es especialmente popular en combinación con el marco Spring.
  • Spring Boot: Si está utilizando Spring Boot como su marco de desarrollo, puede implementar SSR combinando Spring Boot con Thymeleaf u otras tecnologías de plantillas.
  • JSP (JavaServer Pages): JSP es una tecnología de Java EE que permite la creación de páginas web dinámicas con representación del lado del servidor. Aunque se ha vuelto menos popular en comparación con otras tecnologías de plantillas, todavía se utiliza en algunas aplicaciones Java legadas.
  • Vaadin: Vaadin es un marco de desarrollo de aplicaciones web en Java que ofrece una interfaz de usuario rica y permite la representación del lado del servidor. Es especialmente útil para aplicaciones empresariales.

Librerias Java Server Faces

  1. PrimeFaces: PrimeFaces es una biblioteca de componentes de código abierto muy popular para JSF. Ofrece una amplia variedad de componentes ricos y temas personalizables para mejorar la apariencia y la funcionalidad de las aplicaciones JSF.
  2. RichFaces: RichFaces es otra biblioteca de componentes para JSF que proporciona componentes AJAX ricos y herramientas para mejorar la experiencia del usuario en aplicaciones web JSF.
  3. BootsFaces: BootsFaces combina JSF con Bootstrap, el popular framework de diseño web, para facilitar la creación de aplicaciones web modernas y receptivas con JSF.
  4. IceFaces: IceFaces es una biblioteca de componentes JSF que se centra en proporcionar funcionalidad AJAX avanzada para aplicaciones JSF, lo que permite una experiencia de usuario rica y dinámica.
  5. Apache MyFaces: MyFaces es un proyecto de código abierto de la Apache Software Foundation que ofrece una implementación de JSF. Incluye un conjunto de componentes y herramientas para el desarrollo de aplicaciones JSF.
  6. OmniFaces: OmniFaces es una biblioteca de utilidades que complementa JSF y resuelve diversos problemas comunes en el desarrollo de aplicaciones JSF. Ofrece un conjunto de extensiones útiles.
  7. Mojarra (Oracle JSF Reference Implementation): Mojarra es la implementación de referencia de JSF proporcionada por Oracle. Aunque es una implementación estándar de JSF, se utiliza comúnmente en aplicaciones JSF.
  8. Cuban Platform: Cuban Platform es un conjunto de herramientas y bibliotecas que extienden JSF y otros marcos de Java para el desarrollo de aplicaciones empresariales.

En este articulo vamos a trabajar con PrimeFaces.

https://primefaces.org/showcase/index.xhtml?jfwid=c972f

Creando aplicación web empresarial paso a paso:

Se puede comenzar a crear la aplicacion desde el website de jakarta o desde el IDE. El startup de jakarta nos permite una gran configuracion inicial para nuestro proyecto.

  1. Crear el proyecto, para esto utilizaremos IntelliJIDEa

Codigo del ejercicio:

El codigo del ejercicio lo pueden encontrar en github del grupo:

https://github.com/berroteran/demoJakarta10JSF

Posibles problemas y soluciones.

Referencias:

https://blog.payara.fish/getting-started-with-jakarta-ee-9-jakarta-faces-jsf

https://blog.payara.fish/a-quick-look-at-faces-jsf-4.0-in-jakarta-ee-10

https://github.com/eclipse-ee4j/mojarra

https://primefaces.github.io/primefaces/12_0_0/#/?id=main

Ejemplos que te pueden ser utiles:

https://github.com/aturanj/java-jakarta-primefaces


JavaFX no está muerto

JavaFX no está muerto. Aunque ha habido ciertos cambios en el panorama de desarrollo de interfaces de usuario en Java en los últimos años, JavaFX sigue siendo una opción válida y activamente utilizada por muchos desarrolladores.

JavaFX es una biblioteca de software de plataforma cruzada utilizada para desarrollar aplicaciones de escritorio y móviles con una interfaz gráfica de usuario (GUI). Proporciona una amplia gama de componentes y herramientas para crear interfaces de usuario modernas y atractivas. Además, JavaFX se integra bien con el lenguaje de programación Java y se puede utilizar en combinación con otros marcos y tecnologías de Java.

Aunque algunos desarrolladores han optado por utilizar otros marcos y tecnologías para el desarrollo de interfaces de usuario, como HTML5/CSS/JavaScript o bibliotecas como Swing, JavaFX todavía tiene una base de usuarios activa y continúa siendo compatible y mantenido por Oracle y la comunidad de desarrolladores de Java.

JavaFX no está muerto y sigue siendo una opción viable para el desarrollo de aplicaciones de escritorio y móviles con una interfaz gráfica de usuario en Java.

Alternativas

Existen varias alternativas a JavaFX que se pueden considerar para el desarrollo de interfaces gráficas de usuario en Java. Algunas de ellas son:

  1. Swing: Es una biblioteca de componentes gráficos incluida en el kit de desarrollo de Java (JDK). Aunque Swing es una tecnología más antigua, sigue siendo ampliamente utilizada y ofrece una amplia gama de componentes y herramientas para crear interfaces de usuario.
  2. AWT (Abstract Window Toolkit): Es una biblioteca de bajo nivel para crear interfaces gráficas de usuario en Java. AWT proporciona un conjunto básico de componentes y se utiliza a menudo junto con Swing para aprovechar las características adicionales que ofrece.
  3. SWT (Standard Widget Toolkit): Es una biblioteca de interfaz de usuario desarrollada por Eclipse Foundation. SWT se enfoca en proporcionar una integración más nativa con los sistemas operativos subyacentes y ofrece una API de alto rendimiento para crear interfaces de usuario en Java.
  4. JavaFX Ports: JavaFX Ports es una versión portada de JavaFX que se ejecuta en diferentes plataformas, como Android e iOS. Permite desarrollar aplicaciones de escritorio y móviles con una interfaz de usuario moderna y atractiva utilizando JavaFX.
  5. LibGDX: Es una biblioteca de desarrollo de juegos multiplataforma escrita en Java. Aunque se centra en el desarrollo de juegos, también se puede utilizar para crear interfaces gráficas de usuario para aplicaciones no relacionadas con juegos.

Estas son solo algunas de las alternativas disponibles para el desarrollo de interfaces gráficas de usuario en Java. La elección de la herramienta depende de los requisitos específicos del proyecto, la plataforma objetivo y las preferencias del desarrollador.

Referencias

Construir una aplicación REST con Spring Boot y Base de Datos Oracle ejecutada desde Docker

Introducción 

En este artículo, vamos a crear una aplicación REST con Spring Boot conectada a una base de datos Oracle.  

Herramientas

  • Java 17
  • Spring Tool Suite (STS) o Intellij IDEA 
  • Oracle XE
  • Docker
  • Postman

Crear un proyecto Spring Boot

Vamos a ingresar a la siguiente url https://start.spring.io/, como se puede observar en la Figura #1.

Paso # 1:

  • Seleccionamos en el proyecto la opción Maven, el lenguaje Java, la versión de Spring Boot para este ejemplo 2.7.8

Paso # 2:

  • En el Project Metadata: Diligenciamos la información del proyecto, como el Group, Artifact, Nombre, si requiere de adicionar una descripción del proyecto, nombre del paquete con su respectivo empaquetamiento. Para esta demo en el Packaging seleccionamos Jar y en la versión de Java la número 17 que es la version LTS.  
Figura 1

Paso # 3:

Continuando con la gestión del información del proyectó adicionamos las siguientes dependencias.

  • Lombok: Es una librería java que contiene anotaciones que generan automáticamente métodos de ayuda después de la compilación como por ejemplo (setters, getters, constructores).
  • Spring Web: Proporciona características de integración comunes específicas de la web, para construir aplicaciones web, incluyendo RESTful, utilizando Spring MVC.

  • Spring Data JPA: Proporciona soporte de repositorio para Java Persistence API (JPA). Facilitando el desarrollo de aplicaciones que necesitan acceder a fuentes de datos JPA.

  • Spring Boot DevTools: Proporciona reinicio rápido de las aplicaciones.

  • Oracle Driver: Driver que proporciona acceso a Oracle.

  • Validator: Es el estándar para implementar la lógica de validación en el ecosistema Java, por ejemplo lo utilizamos en la clase validando los campos con sus respectivas restricciones. Por otro lado está bien integrado con Spring y Spring Boot.

  • Spring-boot-starter-parent: Todos los proyectos de spring boot construidos en maven utilizan spring boot starter parent en su pom.xml. Estas son algunas acciones que realiza :

    • Configurar la versión y las propiedades de Java.
    • Gestionar las dependencias y sus versiones.

Paso #4:

  • Hacemos click para generar el proyecto.

Revisamos la estructura del proyecto como se puede visualizar en la Figura #2, debería quedar algo así.

Figura 2

En este punto vamos a revisar el archivo pom.xml como se muestra en la Figura #3.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.8</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.geovanny.code</groupId>
	<artifactId>demo-app-oracle</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>demo-app-oracle</name>
	<description>Demo project for Spring Boot with Oracle</description>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>com.oracle.database.jdbc</groupId>
			<artifactId>ojdbc8</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

En el siguiente paso vamos a crear los paquetes con sus respectivas clases e interfaces.

1. Entidad Estudiante

Creamos un paquete con el nombre model y dentro de el una Clase con el nombre Estudiante que contendrá toda la información básica sobre el estudiante, como la identificación, nombre, apellido, dirección, país, ciudad, edad.

package com.geovanny.code.model;
import lombok.NoArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Builder;
import lombok.AllArgsConstructor;
import lombok.Data;
import javax.persistence.GeneratedValue;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Id;
import javax.persistence.Column;
import javax.persistence.GenerationType;
import javax.persistence.SequenceGenerator;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@EqualsAndHashCode(callSuper=false)
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Data
@Table(name="TBL_ESTUDIANTES")
public class Estudiante{
        @Id
        @Column(name = "ID")
        @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seqEstudiantes")
        @SequenceGenerator(name = "seqEstudiantes", allocationSize = 1, sequenceName = "SEQ_ESTUDIANTES")
        @Builder.Default
        Long id=0L;
        @NotNull @NotBlank
        String nombres;
        @NotNull @NotBlank
        String apellidos;
        Integer edad;
        @NotBlank(message = "Direccion es requerida")
        @Size(min = 5, max = 50)
        String direccion;
        String ciudad;
        String pais;
}

2. Interfaz Repositorio JPA

Contendrá los métodos CRUD habituales y más para manejar las operaciones básicas. Por lo tanto, para crear nuestro repositorio personal, necesitamos extender el repositorio JPA de Spring Data. Permite que los servicios se comuniquen con la base de datos.

package com.geovanny.code.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.geovanny.code.model.Estudiante;
@Repository
public interface EstudianteRepositorio extends JpaRepository<Estudiante, Long> {
}

3. Servicio

La aplicación Spring Boot del lado del Backend utiliza Clases de Servicio para representan el núcleo de su aplicación. Para este ejemplo crearemos una interfaz donde crearemos todos los métodos para realizar las operaciones de la lógica de negocio. 

package com.geovanny.code.service;
import java.util.List;
import java.util.Optional;
import com.geovanny.code.model.Estudiante;
public interface EstudianteService {
	
    public List<Estudiante> findEstudianteAll();
    public Estudiante createEstudiante(Estudiante estudiante);
    public Estudiante updateEstudiante(Estudiante estudiante);
    public void deleteEstudiante(Long id);
    public Optional<Estudiante> getEstudiante(Long id);
}

3.1 Implementación del Servicio

En esta clase se implementa toda la lógica de negocio de nuestro ejemplo de estudiante, adicionamos la anotación @Service e implementamos la clase desde la interfaz de servicio.

package com.geovanny.code.service;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.geovanny.code.model.Estudiante;
import com.geovanny.code.repository.EstudianteRepositorio;
@Service
public class EstudianteServiceImpl implements EstudianteService{
	@Autowired
	private EstudianteRepositorio estudianteRepositorio;
	
	@Override
	public List<Estudiante> findEstudianteAll() {
		return estudianteRepositorio.findAll();
	}
	@Override
	public Estudiante createEstudiante(Estudiante estudiante) {
		return estudianteRepositorio.saveAndFlush(estudiante);
	}
	@Override
	public Estudiante updateEstudiante(Estudiante estudiante) {
		
		return estudianteRepositorio.save(estudiante);
	}
	@Override
	public void deleteEstudiante(Long id) {
		estudianteRepositorio.deleteById(id);
	}
	@Override
	public Optional<Estudiante> getEstudiante(Long id) {
		return estudianteRepositorio.findById(id);
	}
}

4. Excepción

Para este ejemplo solamente utilizaremos la excepción NOT_FOUND, queda para el lector si desea adicionar otra tipo de excepcion por ejemplo NO_CONTENT

package com.geovanny.code.exception;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.http.HttpStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ModeloNotFoundException extends RuntimeException {
	
	public ModeloNotFoundException(String mensaje)
	{
		super(mensaje);
	}
}

5. Controlador

Creamos el controlador que se utilizará para interactuar con los estudiantes y realizar las acciones CRUD. Permite que los Servicios se comuniquen con el Frontend.

package com.geovanny.code.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.geovanny.code.exception.ModeloNotFoundException;
import com.geovanny.code.model.Estudiante;
import com.geovanny.code.service.EstudianteService;
import java.util.ArrayList;
import java.util.List;
import javax.validation.Valid;
@RestController
@RequestMapping("/estudiantes")
public class EstudianteController {
	@Autowired
	private EstudianteService service;
	
	@GetMapping(produces = "application/json")
	public ResponseEntity<List<Estudiante>> listar() {
		List<Estudiante> estudiantes = new ArrayList<>();
		 estudiantes = service.findEstudianteAll();
		return new ResponseEntity<List<Estudiante>>(estudiantes, HttpStatus.OK);
	}
	
	@PostMapping
    public ResponseEntity<Estudiante> crearEstudiante(@Valid @RequestBody Estudiante estudiante){
        service.createEstudiante(estudiante);
        return new ResponseEntity<Estudiante>(HttpStatus.CREATED);
    }
	
	@GetMapping("/{id}")
	public ResponseEntity<Estudiante> getEstudianteById(@PathVariable("id") Long id){
		Estudiante estudiante= service.getEstudiante(id).orElseThrow(() -> new ModeloNotFoundException("Estudiante no encontrado $id"));
		return new ResponseEntity<Estudiante>(estudiante, HttpStatus.OK);
	}
	@DeleteMapping("/{id}")
	public ResponseEntity<Estudiante> deleteEstudiante(@PathVariable("id") Long id){
		service.deleteEstudiante(id);
		return ResponseEntity.ok().build();
	}
	@PutMapping("/{id}")
	public Estudiante updateEstudiante(@PathVariable("id") Long id, @Valid @RequestBody Estudiante estudiante){
		Estudiante dbestudiante =  service.getEstudiante(id).orElseThrow(() -> new ModeloNotFoundException("Estudiante No enocntrado"));
		dbestudiante.setNombres(estudiante.getNombres());
		dbestudiante.setApellidos(estudiante.getApellidos());
		dbestudiante.setDireccion(estudiante.getDireccion());
		dbestudiante.setEdad(estudiante.getEdad());
		dbestudiante.setCiudad(estudiante.getCiudad());
		dbestudiante.setPais(estudiante.getPais());
		return service.updateEstudiante(dbestudiante);
	}
		

6. Configuración de Application.yml

En este archivo .yaml se configura el puerto y la conexión a la base de datos de oracle.  

server:
  port: 8081
    
# Oracle #
spring:
  datasource:
    url: jdbc:oracle:thin:@localhost:1521:xe
    username: system
    password: oracle
    driverClassName: oracle.jdbc.driver.OracleDriver
  jpa:
    properties:
      hibernate:
        dialect: org.hibernate.dialect.Oracle12cDialect
    show-sql: true
    hibernate:
      ddl-auto: update

7. Creación de la imagen con Docker Compose

Creamos un archivo con el siguiente nombre dev-stack.yml y adicionaremos el siguiente código dentro del archivo.

version: '3.3'
services:
  oracle:
    image: truevoly/oracle-12c 
    container_name: oracledb    
    ports:    
      - 1521:1521
    environment:      
      - "ORACLE_PASSWORD=oracle"

Ejecutamos el siguiente comando en una terminal.

  • Docker compose -f dev-stack.yml up -d

8. Creación de la Base de Datos

Crearemos una carpeta en dentro de resource con el nombre script y adicionamos el siguiente código en sql que nos permite  crear una tabla en la base de datos. 

CREATE TABLE TBL_ESTUDIANTES(
    ID            NUMERIC(4) NOT NULL PRIMARY KEY,
    NOMBRES       VARCHAR2(60) NOT NULL UNIQUE,
    APELLIDOS     VARCHAR2(300) NOT NULL,
    DIRECCION	  VARCHAR2(150) NULL,
    EDAD          INTEGER NOT NULL,
    CIUDAD        VARCHAR2(50),
    PAIS          VARCHAR2(50),
   CONSTRAINT PK_TBL_ESTUDIANTES PRIMARY KEY (ID)
 );
CREATE SEQUENCE SEQ_ESTUDIANTES;

8.1 Insertar registro en la tabla de la base de datos

Esquema para insertar registros en la tabla de tbl_estudiantes.

INSERT INTO TBL_ESTUDIANTES (ID, NOMBRES, APELLIDOS, DIRECCION, EDAD, CIUDAD, PAIS) VALUES (SEQ_ESTUDIANTES.NEXTVAL, 'GEOVANNY', 'MENDOZA', 'Calle 41 San Jose',28,'Barranquilla', 'Colombia');
INSERT INTO TBL_ESTUDIANTES (ID, NOMBRES, APELLIDOS, DIRECCION, EDAD, CIUDAD, PAIS) VALUES (SEQ_ESTUDIANTES.NEXTVAL, 'OMAR', 'BERROTERAN', 'Bo grenada Casa C22',27,'Managua', 'Nicaragua');
COMMIT;

9. Gestión de Base de Datos

En esta sección exploraremos la gestión de la base de datos con el mismo intellij como se puede observar en la figura #4. 

Figura #4

9.1 Conectar a la interfaz de la base de Datos

Después seleccionar la Database adicionamos la conexión con nuestra base de datos en oracle como se muestra en la figura #5.  

Figura #5
  1. Seleccionamos el signo (+) y nos ubicamos donde dice Data Source.
  2. Seleccionamos la base de datos Oracle.  

9.2 Configuración el Data Sources y Drivers

En este paso ingresamos el user y password, para este ejemplo utilizamos system como user y el password oracle como se muestra en la figura #6. 

Figura #6

9.3 Ejecutar la aplicación

En este paso ejecutamos la aplicación para crear la tabla de estudiante como se muestra en la figura #7.

Nota: Tener el servicio de docker ejecutando 

Figura #7

Abrimos la console para realizar una consulta sobre la tabla tbl_estudiante como se muestra en la figura #8.

Figura #8

10. Prueba con Postman

Para este ejemplo abrimos postman e importamos el archivo de postman para configurar las pruebas. Esge archivo se encuentra en el proyecto de git y se encuentra en la siguiente url Blog.postman_collection.json

{
	"info": {
		"_postman_id": "23d8f83f-f55c-44fd-8411-aaf30b871850",
		"name": "Blog",
		"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
	},
	"item": [
		{
			"name": "JugNicaragua",
			"item": [
				{
					"name": "GET - Estudiantes",
					"request": {
						"method": "GET",
						"header": [],
						"url": {
							"raw": "http://localhost:8081/estudiantes",
							"protocol": "http",
							"host": [
								"localhost"
							],
							"port": "8081",
							"path": [
								"estudiantes"
							]
						}
					},
					"response": []
				},
				{
					"name": "POST - Estudiantes",
					"request": {
						"method": "POST",
						"header": [],
						"body": {
							"mode": "raw",
							"raw": "{\n    \"nombres\": \"Tzeitel\",\n    \"apellidos\": \"Zuleta\",\n    \"ciudad\": \"La Paz\",\n    \"direccion\": \"Calle 12 # 20-12\",\n    \"pais\": \"Colombia\",\n    \"edad\": 28 \n}\n",
							"options": {
								"raw": {
									"language": "json"
								}
							}
						},
						"url": {
							"raw": "http://localhost:8081/estudiantes",
							"protocol": "http",
							"host": [
								"localhost"
							],
							"port": "8081",
							"path": [
								"estudiantes"
							]
						}
					},
					"response": []
				},
				{
					"name": "GET {id} - Estudiantes",
					"request": {
						"method": "GET",
						"header": [],
						"url": {
							"raw": "http://localhost:8081/estudiantes/1",
							"protocol": "http",
							"host": [
								"localhost"
							],
							"port": "8081",
							"path": [
								"estudiantes",
								"1"
							]
						}
					},
					"response": []
				},
				{
					"name": "DELETE - Estudiantes",
					"request": {
						"method": "DELETE",
						"header": [],
						"url": {
							"raw": "http://localhost:8081/estudiantes/3",
							"protocol": "http",
							"host": [
								"localhost"
							],
							"port": "8081",
							"path": [
								"estudiantes",
								"3"
							]
						}
					},
					"response": []
				},
				{
					"name": "PUT - Estudiantes",
					"request": {
						"method": "PUT",
						"header": [],
						"body": {
							"mode": "raw",
							"raw": "{\n    \"id\": 2,\n    \"nombres\": \"Omar\",\n    \"apellidos\": \"Berroteran Silva\",\n    \"ciudad\": \"Managua\",\n    \"direccion\": \"Bo grenada Casa C22\",\n    \"pais\": \"Nicaragua\",\n    \"edad\": 32 \n}\n",
							"options": {
								"raw": {
									"language": "json"
								}
							}
						},
						"url": {
							"raw": "http://localhost:8081/estudiantes/2",
							"protocol": "http",
							"host": [
								"localhost"
							],
							"port": "8081",
							"path": [
								"estudiantes",
								"2"
							]
						}
					},
					"response": []
				}
			]
		}
	]
}

11. Resultado de Postman 

En esta sección se visualiza las pruebas exitosas de postman con los métodos GET y POST como se muestran en la figura #9 y #10.

11.1 Prueba del Método GET: Acá se puede visualizar el resultado de la prueba del método GET donde nos muestra todo los estudiantes registrados, como se muestra en la figura #9     

Figura #9

11.2 Prueba del Método POST: Acá se puede visualizar la creación del estudiante con el método POST como se puede observar en la figura #10.

Conclusión

En este artículo hemos construido una aplicación REST con servicios básicos que se conectara a una base de datos Oracle, donde se crea una imagen en docker.

Repositorio

Referencias

  • Libros:
    • Full Stack Development with Spring Boot and React: Build modern and scalable web applications using the power of Java and React, 3rd Edition Aquí
    • Spring Boot in Practice, Somnath Musib Aquí
Nosotros y terceros seleccionados utilizamos cookies o tecnologías similares con fines técnicos y, con su consentimiento, para otras finalidades (“interacciones y funcionalidades básicas”, “mejora de la experiencia”, “medición” y “segmentación y publicidad”) según se especifica en la política de cookies. Usted es libre de otorgar, denegar o revocar su consentimiento en cualquier momento.    Configurar y más información
Privacidad