Saltar al contenido principal

TP1 - Configuración de Datasource y Pool de Conexiones

Objetivos del TP​

En este TP, usted va a:

  1. ✅ Configurar un datasource H2 para desarrollo
  2. ✅ Configurar un datasource PostgreSQL para producción
  3. ✅ Optimizar los parámetros de HikariCP
  4. ✅ Monitorizar el pool de conexiones con Actuator
  5. ✅ Simular una saturación del pool

Duración estimada: 1 hora


Requisitos previos​

  • Módulos 1 a 6 completados
  • IntelliJ IDEA o VS Code
  • PostgreSQL instalado (o Docker)
  • Maven

Parte 1: Creación del Proyecto​

1.1 Crear el Proyecto con Spring Initializr​

Cree un nuevo proyecto con:

  • Artifact: datasource-demo
  • Dependencies:
    • Spring Web
    • Spring Data JPA
    • H2 Database
    • PostgreSQL Driver
    • Spring Boot Actuator
    • Lombok

1.2 Estructura del Proyecto​

datasource-demo/
├── src/main/java/com/example/datasourcedemo/
│ ├── DatasourceDemoApplication.java
│ ├── model/
│ │ └── Product.java
│ ├── repository/
│ │ └── ProductRepository.java
│ ├── service/
│ │ └── ProductService.java
│ └── controller/
│ └── ProductController.java
└── src/main/resources/
├── application.properties
├── application-dev.properties
└── application-prod.properties

Parte 2: Entidad y Repository​

2.1 Crear la Entidad Product​

// src/main/java/com/example/datasourcedemo/model/Product.java
package com.example.datasourcedemo.model;

import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;

import java.math.BigDecimal;
import java.time.LocalDateTime;

@Entity
@Table(name = "products")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private String name;

private String description;

@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal price;

@Column(nullable = false)
private Integer stock;

@Column(name = "created_at")
private LocalDateTime createdAt = LocalDateTime.now();
}

2.2 Crear el Repository​

// src/main/java/com/example/datasourcedemo/repository/ProductRepository.java
package com.example.datasourcedemo.repository;

import com.example.datasourcedemo.model.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
List<Product> findByStockLessThan(Integer threshold);
}

2.3 Crear el Service​

// src/main/java/com/example/datasourcedemo/service/ProductService.java
package com.example.datasourcedemo.service;

import com.example.datasourcedemo.model.Product;
import com.example.datasourcedemo.repository.ProductRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@RequiredArgsConstructor
@Slf4j
public class ProductService {

private final ProductRepository productRepository;

public List<Product> getAllProducts() {
log.info("Recuperando todos los productos");
return productRepository.findAll();
}

public Product createProduct(Product product) {
log.info("Creando el producto: {}", product.getName());
return productRepository.save(product);
}

public Product getProduct(Long id) {
return productRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Producto no encontrado: " + id));
}

// Método para simular una consulta lenta
public List<Product> getAllProductsSlow() throws InterruptedException {
log.info("Inicio de consulta lenta...");
Thread.sleep(5000); // 5 segundos
log.info("Fin de consulta lenta");
return productRepository.findAll();
}
}

2.4 Crear el Controller​

// src/main/java/com/example/datasourcedemo/controller/ProductController.java
package com.example.datasourcedemo.controller;

import com.example.datasourcedemo.model.Product;
import com.example.datasourcedemo.service.ProductService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/products")
@RequiredArgsConstructor
public class ProductController {

private final ProductService productService;

@GetMapping
public List<Product> getAllProducts() {
return productService.getAllProducts();
}

@GetMapping("/{id}")
public Product getProduct(@PathVariable Long id) {
return productService.getProduct(id);
}

@PostMapping
public Product createProduct(@RequestBody Product product) {
return productService.createProduct(product);
}

// Endpoint para probar las conexiones
@GetMapping("/slow")
public List<Product> getProductsSlow() throws InterruptedException {
return productService.getAllProductsSlow();
}
}

Parte 3: Configuración de Desarrollo (H2)​

3.1 Configuración Básica​

# application.properties (configuración común)

# Perfil activo por defecto
spring.profiles.active=dev

# Actuator
management.endpoints.web.exposure.include=health,metrics,info
management.endpoint.health.show-details=always

# Logging
logging.level.com.zaxxer.hikari=DEBUG
logging.level.org.hibernate.SQL=DEBUG

3.2 Configuración H2 (Desarrollo)​

# application-dev.properties

# ===== DATASOURCE H2 =====
spring.datasource.url=jdbc:h2:mem:devdb;DB_CLOSE_DELAY=-1
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

# Consola H2
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

# ===== JPA =====
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

# ===== HIKARI (Pool de conexiones) =====
spring.datasource.hikari.pool-name=DevPool
spring.datasource.hikari.minimum-idle=2
spring.datasource.hikari.maximum-pool-size=5
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.idle-timeout=300000
spring.datasource.hikari.max-lifetime=600000

# Detección de fugas (30 segundos)
spring.datasource.hikari.leak-detection-threshold=30000

3.3 Probar​

# Iniciar la aplicación
mvn spring-boot:run

# O con el perfil explícito
mvn spring-boot:run -Dspring-boot.run.profiles=dev

Verificaciones:

  1. Abrir http://localhost:8080/h2-console
  2. JDBC URL: jdbc:h2:mem:devdb
  3. Conexión con sa / (vacío)

Parte 4: Configuración de Producción (PostgreSQL)​

4.1 Iniciar PostgreSQL con Docker​

# Crear e iniciar PostgreSQL
docker run --name postgres-demo \
-e POSTGRES_USER=demo \
-e POSTGRES_PASSWORD=demo123 \
-e POSTGRES_DB=productdb \
-p 5432:5432 \
-d postgres:15

4.2 Configuración PostgreSQL​

# application-prod.properties

# ===== DATASOURCE POSTGRESQL =====
spring.datasource.url=jdbc:postgresql://localhost:5432/productdb
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.username=demo
spring.datasource.password=demo123

# Consola H2 desactivada
spring.h2.console.enabled=false

# ===== JPA =====
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect

# ===== HIKARI (Pool optimizado para producción) =====
spring.datasource.hikari.pool-name=ProdPool
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-lifetime=1800000

# Validación de conexiones
spring.datasource.hikari.connection-test-query=SELECT 1

# Detección de fugas (60 segundos)
spring.datasource.hikari.leak-detection-threshold=60000

4.3 Iniciar en Modo Producción​

# Con el perfil prod
mvn spring-boot:run -Dspring-boot.run.profiles=prod

Parte 5: Monitorización con Actuator​

5.1 Endpoints HikariCP​

Inicie la aplicación y pruebe estas URLs:

# Salud global
curl http://localhost:8080/actuator/health

# Métricas HikariCP
curl http://localhost:8080/actuator/metrics/hikaricp.connections

# Conexiones activas
curl http://localhost:8080/actuator/metrics/hikaricp.connections.active

# Conexiones inactivas
curl http://localhost:8080/actuator/metrics/hikaricp.connections.idle

# Conexiones en espera
curl http://localhost:8080/actuator/metrics/hikaricp.connections.pending

# Tiempo de adquisición de conexión
curl http://localhost:8080/actuator/metrics/hikaricp.connections.acquire

5.2 Crear un Endpoint de Diagnóstico​

// src/main/java/com/example/datasourcedemo/controller/PoolInfoController.java
package com.example.datasourcedemo.controller;

import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.HikariPoolMXBean;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/api/pool")
@RequiredArgsConstructor
public class PoolInfoController {

private final DataSource dataSource;

@GetMapping("/info")
public Map<String, Object> getPoolInfo() {
HikariDataSource hikariDS = (HikariDataSource) dataSource;
HikariPoolMXBean poolMXBean = hikariDS.getHikariPoolMXBean();

Map<String, Object> info = new HashMap<>();
info.put("poolName", hikariDS.getPoolName());
info.put("maximumPoolSize", hikariDS.getMaximumPoolSize());
info.put("minimumIdle", hikariDS.getMinimumIdle());
info.put("activeConnections", poolMXBean.getActiveConnections());
info.put("idleConnections", poolMXBean.getIdleConnections());
info.put("totalConnections", poolMXBean.getTotalConnections());
info.put("threadsAwaitingConnection", poolMXBean.getThreadsAwaitingConnection());

return info;
}
}

Prueba:

curl http://localhost:8080/api/pool/info

Resultado esperado:

{
"poolName": "DevPool",
"maximumPoolSize": 5,
"minimumIdle": 2,
"activeConnections": 0,
"idleConnections": 2,
"totalConnections": 2,
"threadsAwaitingConnection": 0
}

Parte 6: Simular una Saturación del Pool​

6.1 Configuración para la Prueba​

Modifique temporalmente application-dev.properties:

# Pool muy pequeño para probar
spring.datasource.hikari.maximum-pool-size=2
spring.datasource.hikari.connection-timeout=5000 # 5 segundos

6.2 Script de Prueba​

Cree un script para enviar varias solicitudes simultáneas:

# test-pool.sh (Linux/Mac)
#!/bin/bash

echo "Enviando 5 solicitudes simultáneas..."

for i in {1..5}; do
curl -s http://localhost:8080/api/products/slow &
done

wait
echo "¡Terminado!"

O con PowerShell (Windows):

# test-pool.ps1
Write-Host "Enviando 5 solicitudes simultáneas..."

$jobs = @()
for ($i = 1; $i -le 5; $i++) {
$jobs += Start-Job -ScriptBlock {
Invoke-RestMethod -Uri "http://localhost:8080/api/products/slow"
}
}

$jobs | Wait-Job | Receive-Job
Write-Host "¡Terminado!"

6.3 Observar los Logs​

HikariPool-1 - Pool stats (total=2, active=2, idle=0, waiting=3)
HikariPool-1 - Connection is not available, request timed out after 5001ms.

6.4 Corregir​

# Aumentar el pool
spring.datasource.hikari.maximum-pool-size=10

# O aumentar el timeout
spring.datasource.hikari.connection-timeout=60000

Parte 7: Datos de Prueba​

7.1 Crear data.sql​

-- src/main/resources/data.sql
INSERT INTO products (name, description, price, stock, created_at) VALUES
('Laptop Pro', 'Portátil de alto rendimiento', 1299.99, 50, CURRENT_TIMESTAMP),
('Souris Sans Fil', 'Ratón ergonómico Bluetooth', 49.99, 200, CURRENT_TIMESTAMP),
('Clavier Mécanique', 'Teclado gaming RGB', 129.99, 75, CURRENT_TIMESTAMP),
('Écran 27 pouces', 'Monitor 4K IPS', 399.99, 30, CURRENT_TIMESTAMP),
('Webcam HD', 'Cámara 1080p con micrófono', 79.99, 100, CURRENT_TIMESTAMP);

7.2 Activar la Inicialización​

# application-dev.properties
spring.jpa.defer-datasource-initialization=true
spring.sql.init.mode=always

Verificación Final​

Checklist​

  • La aplicación inicia con el perfil dev (H2)
  • La consola H2 es accesible
  • Los endpoints /api/products funcionan
  • Las métricas de Actuator están disponibles
  • /api/pool/info devuelve las estadísticas del pool
  • La prueba de saturación muestra los timeouts
  • La aplicación inicia con el perfil prod (PostgreSQL)

Pruebas a Realizar​

# 1. Crear un producto
curl -X POST http://localhost:8080/api/products \
-H "Content-Type: application/json" \
-d '{"name":"Test","price":99.99,"stock":10}'

# 2. Listar los productos
curl http://localhost:8080/api/products

# 3. Verificar el pool
curl http://localhost:8080/api/pool/info

# 4. Verificar la salud
curl http://localhost:8080/actuator/health

Resumen​

En este TP, usted ha aprendido a:

CompetenciaDescripción
Configuración multi-perfildev (H2) vs prod (PostgreSQL)
HikariCPParámetros del pool de conexiones
ActuatorMonitorización de métricas
DiagnósticoEndpoint personalizado para el pool
Pruebas de cargaSimular una saturación

🎯 Próximo Paso​

Ahora que domina la configuración de datasources, aprenda a gestionar las transacciones en el próximo TP.

➡️ TP7.2: Transacciones y Rollback


⬅️ Volver al Módulo 7