Parte 5 LAMP Carrito de compras sin REACT con Spring Data JPA

Hemos hablado antes de que muchas personas dejan mal configurados los servidores. Yo no pretendo decirte como usar springboot o hibernate paso a paso. No conozco tu entorno. Pero si se que REACT está causando muchos problemas. El principal es que con conexion decente y pc decente en proyectos medio complicados cada build compile bien hecho puede tardar 8 minutos. Por ejemplo, para corregir una falta de ortografía o un cambio de color. Mucho del problema son los pasos intermedios que tiene REACT. Y que del 2022 al 2025 explotó en México esta tecnología del 2014, si.

En 2026, ¿por qué un «Hola Mundo» en React necesita descargar 400 MB de node_modules?

Sumale que Maven recompila todo cada vez y Gradle solo lo que cambió.

Vamos a revisar y actualizar un poco un código de terceros, que es de 2019, y que usa Thymeleaf y JPA Spring no hibernate en 2019. No es perfecto pero es una prueba de concepto.

Pero el primer punto es limitar la memoria. No lo hagas si no entiendes como configurar hibernate. Puedes seguir este «tutorial» sin hacer esto, pero este es el bonus experto:

 

La Precaución Crítica: Hibernate NO Puede Comerse Todo

El código que usaremos de ejemplo es de 2019. En ese entonces y ahora muchos asumen que Hibernate podía usar toda la RAM disponible era «aceptable» porque los servidores tenían 8-16 GB. En 2026, con empresas medianas, eso es suicidio por el costo de la nube sea cual sea tu memoria..

 Configuración application.properties REAL para mundo real:

# === LÍMITES DE POOL DE CONEXIONES ===
spring.datasource.hikari.maximum-pool-size=5
spring.datasource.hikari.minimum-idle=2

# === HIBERNATE: NO CACHEES TODO ===
spring.jpa.properties.hibernate.cache.use_second_level_cache=false
spring.jpa.properties.hibernate.cache.use_query_cache=false

# === LÍMITE DE STATEMENTS PREPARADOS ===
spring.datasource.hikari.max-lifetime=600000

# === LOGGING (para diagnosticar) ===
logging.level.org.hibernate.SQL=WARN
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=WARN

¿ Por Qué Estas Líneas Salvan Tu Servidor ?

Sin configurar:
– Pool conexiones: 10 (default) × 30 MB = 300 MB (y a veces tienes 20 o mas)
– Cache L2 Hibernate: Crece hasta 500-800 MB
– Statements preparados: Sin límite = leak de memoria
– Total desperdiciado: ~1.2 GB**

Con configuración:
– Pool conexiones: 5 × 30 MB = 150 MB
– Cache L2: Deshabilitado = 0 MB
– Statements: Con lifetime = No leak
– Total usado: ~200 MB

El Código que vamos a ver es de 2019 y luego lo vamos a analizar un poco.

NO LO ESCRIBI YO.

Nota muy interesante segun yo:

Quiero dejar claro que este tema me parece curioso especialmente porque en 2017 me encontraba en la situación de tener que contratar programadores php y les daba 3 horas para hacer desde cero, sin objetos, crud de tres tablas inventario, productos y ventas, con la salvedad que consideran que el producto podia tener dos proveedores. Solo el 2- 5 %  lo podian hacer. el 70% de los que llegaban no  pasaban a examen porque NO me podían hacer un hola mundo en php en la entrevista. Y cuando programadores existentes en una empresa me decían que no se podía hacer en ese tiempo, yo lo hacía desde cero, frente a ellos, sin internet en poco menos de hora y media. Y de pilón evaluaba su conocimiento de base de datos.

De este ejemplo EL concepto principal: Hay Alternativas a React probadas, mas rápidas y eficientes en memoria desde 2019

Ventajas: Muestra Thymeleaf y un crud funcional sin react. Con Thymeleaf Usa el Thymeleaf Layout Dialect, que puedes hacer algo real y reutilizable sin los problemas de REACT. Muestra Gradle, Bootstrap 4 y stack completo de manera mas eficiente que lo que muchos tienen en 2025. Es fácil de entender para los que vienen de otros frameworks porque no usa dependencias raras. Lo puedes reproducir sin descargar medio internet y actualizarlo no es tna difícil.

Problemas: No hay spring security, se reinicia el servidor y se pierde, no te dice nada de hibernate, javax ya fue sustituido por jakarta. Las validaciones solo son una buena idea y deberían ser en backend, es ventaja y deventaja. Podemos decir que es mala UX pero funcional, aunque te salva de ataques de inyección SQL y es simple. El evento viene como es, si se queda a la mitad puede haber venta guardada pero inventario no descontado.

  • el proceso se queda a la mitad, se guarda la venta pero no se descuenta el inventario. Eso en 2019 se arreglaba con un simple @Transactional. A final de cuentas no importa si el código original lo tiene o si es parte de las ‘minas’ que tenemos que desactivar al revisar código ajeno.

Resumen: Es simple, funciona, y si bien tiene que adaptarse para crecer, debes dar gracias si ves código como ese en lugar de 1600 líneas para validar existencias.

QUE HARÍA YO: Si me mostraban esto en 2019, o ahora, agarraba la base de datos, pasaba los campos de float a DECIMAL 14,6 o bigdecimal en términos java, y lo reescribía de cero en php y mostraba todo el sistema terminado al día siguiente en un solo archivo con niveles de acceso y respetando el histórico. Y lo hago todavía. En 2025 hice algo similar tres veces y fue un año flojo.

Que tiene este codigo de Parzebyte:

Es código real con problemas reales que resuelve problemas reales mas elegante que mucho que vas a ver. No te estoy diciendo que lo uses en producción, sino que gradle en lugar de maven y thymeleaf te pueden ayudar a reducir el gasto de nube y de pilón veremos como puede arreglarse código heredado sin reescribir todo.

Escribir esto me tomó unos 20 minutos. El análisis de abajo otra media hora.

En el tema anterior puse un carrito de compras que uso como ejemplo. Es muy buen front end pero muy mal back end.

Codigo fuente:

https://parzibyte.me/blog/posts/sistema-ventas-spring-mvc-mysql-bootstrap/

Base de datos:

La base de datos https://github.com/parzibyte/sistema-ventas-spring-boot/blob/master/src/main/resources/esquema_ventas_springboot.sql

Archivo Gradle https://github.com/parzibyte/sistema-ventas-spring-boot/blob/master/build.gradle

El código es limpio y por lo mismo «bien documentado» para entender gradle. Pero…. Hay demasiadas cosas que van a explotar. De entrada no te dice que version de gradle ni requisitos de compilación ni limitaciones a tomar en cuenta. No me extrañaria que el pom sea de otro lugar.

Vuelvo a decir : No lo escribí yo.

Este código es un EXCELENTE tutorial de Spring Boot. Cumple su propósito educativo.

PERO si lo llevas a producción tal cual: Perderás dinero (Float)  Tendrás inconsistencias (MyISAM)  Crashearás (sin WHERE) y Te multarán (sin @Transactional).

El método de separación de clases se usa para que varios programadores puedan trabajar el mismo tiempo en diferentes partes del proyecto sin pisarse los pies. Pero, siendo sinceros, en 33 años de programación he manejado sistemas con 90 a 100 tablas o con información delicada , y los cambios son relativamente frecuentes y no triviales. Asií que srping boot en un sistema de 50 tablas tienes un problema serio.

La idea de usar hace años laravel y ahora react y springboot (que son de hace diez años ahora en 2026) es ser productivo de inmediato. El código que mencione está hecho sobre spring data JPA que lleva un ORM integrado. Haciendo una revisión en este momento pienso lo msimo que habría pensado en 2019. La persona tomó un curso pero no ha trabajado en algo real. Se agradece la intención eso sí y sirve como ejemplo de FRONT END.

El primer punto que se destaca para mi es que la base de datos usa campos float debiendo ser campos DECIMAL. Es un tema no menor, pero en dinero o vidas humanas cuenta.  Usar double, money, currency, float son igualmente malos.

El segundo es que es MYISAM, no INNODB, lo cual es en muchos aspectos mala práctica desde 2013 sea el lenguaje que sea por varias razones.

Un ejemplo simple que puedes buscar el significado en internet.

La Solución de Oro (Que ya existía en 2019): Para arreglar el «Back-end» en cuanto a conexiones inestables, solo falta una línea arriba del método terminarVenta en el VenderController.java:

Java

@Transactional(rollbackFor = Exception.class)
public String terminarVenta(HttpServletRequest request, ...) {
    // Si algo falla aquí adentro, Spring hace ROLLBACK automático
    // y la base de datos queda como si nada hubiera pasado.
}
  • Nota Senior: Si el autor hubiera usado esta anotación junto con InnoDB, el sistema sería 95% confiable. Pero como usó MyISAM, ¡la anotación no sirve de nada! MyISAM no sabe qué es un Rollback. Es el «doble error» de este proyecto. Pero Inno existe desde varios añes antes de 2019 que se hizo el tutorial.

EL 95% de los problemas estarían resueltos si :

  • usando campos decimal en lugar de float y en lasvariables y métodos respectivos.
  • innodb en lugar de myisam
  • uso de Transactional

Otros puntos intersantes:

  • No vi usar where en ningun lado. OutOfMemory con muchos datos o muchas aplicaciones en el servidor o si el usuario esta viendo videos en facebook al mismo tiempo
  • En mi punto de vista editar el nombre de producto es un gran NONO jamas y deberia comentarse en cualquier carrito de compras . No puedes sobreescribir codigo de barras, clave hacienda, Nombre o descripción del producto. Te lo digo por experiencia . Hay casos extremos como que reusen el Código para otro producto y es un mega desastre. No es algo menor el problema de recliclar claves de producto. Literalmente Cuesta vidas. Siempre es mejor crear una clave de producto nueva.
  • Por JPA y el ORM la base de datos es … nominal. No hay numero de versión.  que pasa cuando permites o pones un campo nuevo como tipo de impuesto con el ORM ? y si cambias lo campos de float a decimal ? Deberías checar que versión de base de datos es y si el campo existe.
  • request.getSession().setAttribute(«carrito», carrito); // el carrito no expira nunca

A propósito. Estoy de acuerdo en usar el id del producto en el get, pero lo hago por bitácora, no por las mismas razones y borrar lo permito solo a usuarios identificados, y si no hay historial de ventas del producto.

Revisando el navegador del repositorio https://github.com/parzibyte/sistema-ventas-spring-boot

Estas son las notas que tomé..

producto.java

private Float existencia;

// Así se vería  variable para mapear el DECIMAL(14,6)
private BigDecimal precio;

// Al recuperarlo de la base de datos o crearlo:
BigDecimal valor = new BigDecimal(«12345678.123456»);

En lugar de bigdecimal o decimal Hay métodos adicionales de multiplicacion que a veces se usan pero son raro verlos. Aquí afortunadamente no estan. que pueden llevar a overflow entre 32 y 64 bits de compilador.

Precisión inmutable: Nunca perderás un micro-centavo en una suma de 10,000 facturas.

/ProductosController.java

Problema: recibe id del formulario, asi que con un f12 o o postmanpuede borrar cualquier producto del sistema. No hay permiso para verifiar si el usuario puede hacerlo o si hay historial asociado de pedidos, existencia compras oventas.

@valid solo verifica que sea mayor a 0. Pero si hay concurrencia dos usuarios sobreescriben el registro. La existencia se leyó hace cinco minutos que cargaron el formulario.

(Como dije antes en lo personal yo considero que es correcto tener el id en el get pero por bitacora, No por las razones que menciona el.)

producto vendido .java.

Poner aqui una lista de precio seria horrible. y como consultas el histórico ? otro problema.

public Float getTotal() {

el modelo en gettotal no considera que pasa si la existencia es cero. y si el precio es negativo ? no deberia ser pero en ocasiones te lo encuentras por kits, por validaciones (ej precio = -99 significa en varios de mis sistemas ese producto no se puede vender a ese cliente ) se le llama valor centinela. Debería documentarse en el código pero no hay reglas de negocio en el código ni precauciones básicas de concurrencia que yo vi desde Clipper 5 en 1993.

O sea si la existencia es cero, estas documentando que alguien se llevo un medicamento que no tenias ?

vender controller.java

espera una buena conexion., a veces el cliente empieza en una pestaña y termina en otra. Su bucle de terminar venta viola los principios transaccionales, y el principio de atomicidad . Dos cajeros pueden vender elproducto al mismo tiempo. O dos clientes pueden por ejemplo en línea comprar boletos para un concierto.

float total = 0;
for (ProductoParaVender p: carrito) total += p.getTotal();

que puede llevar a errores de redondeo y que no cuadre la contabilidad o la venta del día o que no puedas timbrar una factura. Facturas rechazadas y multas por simplemente usar float.

venta.java

public Float getTotal() {
Float total = 0f;
for (ProductoVendido productoVendido : this.productos) {
total += productoVendido.getTotal();
}
return total;
}
Segun yo debió ser….

import java.math.BigDecimal;
import java.math.RoundingMode;

public BigDecimal getTotal() {
// Inicializamos con CERO absoluto, no con una aproximación binaria
BigDecimal total = BigDecimal.ZERO;

if (this.productos == null) return total;

for (ProductoVendido productoVendido : this.productos) {
// Obtenemos el total del producto (que también debería ser BigDecimal)
BigDecimal subtotal = productoVendido.getTotal();

// Sumamos con precisión exacta
total = total.add(subtotal);
}

// Establecemos la escala de salida (ej. 2 decimales para dinero)
return total.setScale(2, RoundingMode.HALF_UP);
}

y tendriamos que cambiar en productovendido.java // En ProductoVendido.java
public BigDecimal getTotal() {
// cantidad (ej. 1.500) * precio (ej. 10.50)
return this.cantidad.multiply(this.precio)
.setScale(2, RoundingMode.HALF_UP);
}

La base de datos la genera el ORM pero además está en https://github.com/parzibyte/sistema-ventas-spring-boot/blob/master/src/main/resources/esquema_ventas_springboot.sql

es my isam, no soportatransacciones ni acid e ignora muchas vecesllaves foraneas.

Los campos son float cuando deberian ser decimal (14,6);

CREATE TABLE `hibernate_sequence` (
`next_val` bigint(20) DEFAULT NULL
)

Usa un bigint para la secuencia, pero las tablas usan int(11). En el momento que la secuencia pase de 2,147,483,647, Hibernate intentará insertar ese valor en un int(11) y la base de datos colapsará. Es una inconsistencia de tipos clásica de «programador de tutorial».

Tipos de datos: Confirmado el desastre del FLOAT

fecha y hora deberia ser date time o timestamp. Yo me iriía por datetime

14 archivos de templates…. y es mas sano ue react

Analizando el pom.xml https://github.com/parzibyte/sistema-ventas-spring-boot/blob/master/pom.xml

Ni siquiera creo que sea de este proyecto. que hace aquí ?

no hay dependencia de jpa ni de mysql o maria db. spring-boot-starter-data-jpa El conector de MySQL o MariaDB.

No hay rastro de spring-boot-starter-security. Confirmado: Seguridad 0.

No hay librerías de precisión como Jackson-datatype-jsr310

El groupId dice org.springframework. Eso es un error de práctica. El groupId debería ser el dominio del autor (ej. me.parzibyte). Usar el de Spring es como si tú pusieras en la cabecera de tus archivos PHP que el autor es php.net.

que le falta al pom ? bueno…

De entrada es buena práctica especificar el spring security aunque tengas java superior al 17. Y además hay conflictos entre versiones mayores de spring security, por ejemplo la 7. (recuerda que segun yo este proyecto usa gradle, no pom)

Motor de persistencia.

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

driver

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

La validacion de bean es una especificacion java

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>