Persistencia (Hibernate / JPA)¶
Cómo persiste el backend: provider, dialecto propio, modo de flush, naming strategy y administración del DDL. Todo está configurado en application.yml y en com.tipre.tifacturaonlinemanager.action.db.SQLServerDialect + config.SeamCompatConfig. Para las entidades concretas ver Modelo de dominio; para por qué el flush es MANUAL, Invariantes.
De un vistazo¶
| Aspecto | Valor | Dónde |
|---|---|---|
| Provider | Hibernate 7.2.x (vía BOM Spring Boot 4.0) | pom.xml (BOM) |
| Starter | spring-boot-starter-data-jpa |
pom.xml |
| Dialecto | com.tipre.tifacturaonlinemanager.action.db.SQLServerDialect (propio) |
spring.jpa.database-platform |
ddl-auto |
none |
application.yml |
| Naming físico | PhysicalNamingStrategyStandardImpl |
application.yml |
open-in-view |
false |
application.yml |
| Flush por defecto | MANUAL (FlushModeType.COMMIT en el entityManager del shim) |
SeamCompatConfig |
| Driver | com.microsoft.sqlserver.jdbc.SQLServerDriver (mssql-jdbc 12.8.1.jre11) |
pom.xml + application-{dev,prod}.yml |
| Cache 2do nivel | No (sin @Cache; dependencia comentada en pom.xml) |
— |
| Tests | H2 (src/test/resources/application.yml pisa al base) |
pom.xml (scope test) |
Dialecto propio: SQLServerDialect¶
action/db/SQLServerDialect.java extiende org.hibernate.dialect.SQLServerDialect (el de Hibernate, ya version-aware) y solo sobreescribe el mapeo de BOOLEAN:
public class SQLServerDialect extends org.hibernate.dialect.SQLServerDialect {
@Override
protected String columnType(int sqlTypeCode) {
if (sqlTypeCode == SqlTypes.BOOLEAN) {
return "bit";
}
return super.columnType(sqlTypeCode);
}
}
Es un port del legacy (SPEC-002): el legacy registraba BOOLEAN -> "bit" para que los boolean del modelo (p. ej. EnteFacturador.habilitado, los flags de TipoComprobante) mapeen a columnas bit de SQL Server.
Hibernate 6 cambió la API
Hibernate 6 eliminó los dialectos versionados (SQLServer2012Dialect) y la API registerColumnType(int, String). El reemplazo idiomático es extender SQLServerDialect y sobreescribir columnType(int). El comentario del propio archivo lo deja claro.
El dialecto es prácticamente inerte
Con hbm2ddl=none la generación de DDL está deshabilitada, así que el override de BOOLEAN->bit solo afectaría a la generación de DDL, que no ocurre. Se conserva por fidelidad al legacy y trazabilidad (SPEC-002), no porque cambie el runtime.
DDL administrado por el cliente¶
spring.jpa.hibernate.ddl-auto: none. El esquema lo administra el cliente, la app NO lo genera ni lo altera. Esta es una invariante explícita en application.yml.
Las tablas nuevas que introdujo la migración (no del legacy) se versionan como scripts SQL en src/main/resources/db/migration/ para que el cliente los aplique a mano (no es Flyway/Liquibase automático — son scripts de referencia):
add-JobEjecucion.sql— tablaJobEjecucion.add-TrxErrorLog.sql— tablaTrxErrorLog.add-TrxErrorLog-mensajeCurado.sql— columnamensajeCuradoenTrxErrorLog(mensaje humano del error curado). Orden de deploy sagrado: correr el script ANTES de subir el jar que la mapea; con la columna ausente Hibernate rompe al insertar/leerTrxErrorLog.add-cert-EntesFacturadores.sql— columnascertP12*enEntesFacturadores.add-EntesFacturadores-cuit-unique.sql— índiceUQ_EntesFacturadores_cuitenEntesFacturadores(cuit), UNIQUE filtrado (WHERE cuit IS NOT NULL). El POS v2 identifica su comercio por CUIT, así que debe ser 1:1. Prerequisito: no puede haber CUIT duplicados antes de correrlo.add-validadoArca-SucPosPV.sql,add-unique-indexes-SucPosPV.sql,add-PV-validacion-obtencion-SucPosPV.sql— columnas/índices deSucPosPV.add-Trx-hot-query-indexes.sql— 4 índices NONCLUSTERED de soporte sobreTrx(2 filtrados) para las queries calientes: reconciliación de NC, dedup CAEA del notificador y el snapshot 5-min del cockpit. Ver detalle abajo.add-CockpitStat.sql,add-Trx-*.sql,schedule-nc-job.sql,cleanup-parametros-legacy.sql,drop-tablas-sin-uso.sql,verify-cae-caea-nc.sql.
Índices calientes de Trx (add-Trx-hot-query-indexes.sql)¶
Son puro soporte de lectura (no cambian datos ni constraints) para las queries calientes que ningún índice previo podía seekear: la reconciliación de NC (NotaCreditoReconciliacionService), el dedup CAEA del notificador (CaeaNotificacionService.findExistingCaea) y el snapshot 5-min del cockpit. Sin ellos, cada corrida diaria de reconciliación y cada dedup CAEA escanean el clustered completo del histórico Trx (minutos con años de datos).
| Índice | Columnas | Filtro | Sirve a |
|---|---|---|---|
IX_Trx_ticket_identity |
nroTicketPos, nroSuc, nroPos, idTipoComprobante, comprobanteFecha INCLUDE tipofacturacion |
WHERE nroTicketPos IS NOT NULL |
Sondas de identidad de ticket (CAE aprobados/huérfanos, hermanos CAEA). Excluye el grueso del histórico V1 (sin ticket). |
IX_Trx_v2_cae_fecha |
comprobanteFecha, resultado INCLUDE nroSuc, nroPos, reintentos |
WHERE version='V2' AND tipofacturacion='CAE' |
Barrido externo de la reconciliación + scan periódico del cockpit (covering). |
IX_Trx_caea_dedup |
ptoVta, comprobante |
WHERE version='V2' AND tipofacturacion='CAEA' |
Dedup CAEA del notificador (findExistingCaea). |
IX_Trx_trxoriginal_nc |
trxoriginal INCLUDE tipofacturacion |
WHERE trxoriginal IS NOT NULL |
Sonda NOT EXISTS de NC (nc.trxoriginal = cae.id). |
Los índices filtrados dependen de literales SQL
Los dos índices filtrados matchean porque las JPQL de la app renderean version / tipofacturacion como literales SQL ('V2', 'CAE', 'CAEA'). Si alguna de esas queries se parametrizara en el futuro, hay que revisar estos filtros. Correr una vez, en ventana de bajo tráfico (Standard Edition no tiene ONLINE=ON). Cada CREATE está guardado por IF NOT EXISTS (idempotente); aditivo y reversible (DROP INDEX ... ON dbo.Trx).
Validar mapeos contra la DB real
El perfil de test application-realdb.yml (LOCAL, gitignored) apunta a una copia de prod restaurada y usa ddl-auto: none igualmente — la validación real son las queries del test, no el validate de Hibernate (pedante con tipos equivalentes como numeric vs bigint que en runtime andan).
Naming strategy¶
spring.jpa.hibernate.naming.physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl.
La estrategia estándar respeta @Table/@Column tal cual: nombres literales en PascalCase, sin convertir a snake_case. Es el comportamiento del Hibernate 4 del legacy. Por eso las entidades pueden mapear tablas como EntesFacturadores, TiposComprobante, Caeas o columnas como idEnteFacturador y fFechaUltObtencionPOS sin transformación.
Flush MANUAL¶
El bean entityManager que usa el seam-compat se crea en SeamCompatConfig como shared entity manager y se le fija FlushModeType.COMMIT:
@Bean(name = "entityManager")
public EntityManager seamEntityManager(EntityManagerFactory emf) {
EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf);
try { em.setFlushMode(FlushModeType.COMMIT); } catch (Exception ignored) {} // INVARIANTE 5.1 (MANUAL ~ COMMIT)
return em;
}
Esto replica el default-flush-mode=MANUAL del components.xml legacy. En JPA no existe un MANUAL puro; COMMIT es el equivalente más cercano: Hibernate no auto-flushea antes de cada query, solo al commit de la transacción (o cuando se llama a flush() explícitamente).
Por eso el código de negocio hace flush() a mano donde necesita que algo pegue en la DB. El EntityHome del shim lo hace en persist()/update()/remove():
// org/jboss/seam/framework/EntityHome.java
getEntityManager().flush(); // INVARIANTE: flush explícito (modo MANUAL)
...
getEntityManager().flush(); // INVARIANTE 5.2: SOLO flush, sin merge
update() = solo flush, sin merge (invariante §5.2)
EntityHome.update() hace únicamente flush() sobre la entidad ya managed; no llama a merge(). El patrón de negocio (ParentEntityManager.updateOrPersistTrx) carga/setea la instancia en el Home y la deja en estado managed para que el flush la escriba. Romper esto (meter un merge) cambia la semántica de persistencia.
Otros puntos con flush() explícito: SucPosPVAdminService (backstop para que una violación de UQ salte en el flush y no en el commit), CaeaConsultaService, CockpitStatSnapshot, TrxNextGenPersistHelper.
Driver y datasource¶
Driver mssql-jdbc 12.8.1.jre11 (scope runtime; inerte en tests, que usan H2). La URL JDBC incluye encrypt=true;trustServerCertificate=true en dev y prod:
Detalle de hosts/credenciales por perfil y variables de entorno en Config y perfiles.
Cache de segundo nivel¶
No hay 2nd-level cache hoy
El legacy tenía 2nd-level + query cache con ehcache (persistence-prod.xml), pero ninguna entidad migrada lleva @Cache, así que el cache no se usa. La dependencia (hibernate-jcache + ehcache) está comentada en el pom.xml (rotulada SPEC-002): hibernate-ehcache no existe en Hibernate 7 (el 2nd-level pasó a JCache), y habría que reescribir el bloque al abordar SPEC-002. open-in-view está en false, así que tampoco hay sesión abierta en la capa de vista.
Por dónde seguir¶
- Modelo de dominio — entidades y sus tablas.
- Config y perfiles — datasource por perfil y env vars.
- Invariantes — flush, idempotencia, lookup.