Files
jRDC-MultiDB-Hikari/RDCConnector.bas
2025-10-28 21:09:15 -06:00

253 lines
9.7 KiB
QBasic

B4J=true
Group=Default Group
ModulesStructureVersion=1
Type=Class
Version=4.19
@EndOfDesignText@
' Módulo de clase: RDCConnector
' Esta clase gestiona el pool de conexiones a una base de datos específica.
' REFRACTORIZADO: Usa ConnectionPoolManager y delega a HikariCP.
Sub Class_Globals
' --- Variables globales de la clase ---
Private pool As ConnectionPoolManager ' Usa el Manager para la modularidad
Private MyHikariPool As HikariConnectionPool ' <-- NUEVO: Pool dedicado a esta DBKey.
Private DebugQueries As Boolean = False ' Bandera para activar/desactivar el modo de depuración
Public commands As Map ' Comandos SQL cargados
Public serverPort As Int
Public usePool As Boolean = True
Public config As Map ' Configuración completa (JdbcUrl, User, Password, etc.)
Public IsParameterToleranceEnabled As Boolean ' Tolerancia a parámetros de más
Dim driverProperties As Map ' CRÍTICO: Propiedades específicas del driver (MySQL statement caching)
Private configLocation As String ' Ubicación del archivo de configuración
Private poolProperties As Map
End Sub
' Subrutina de inicialización para el conector de una base de datos específica.
Public Sub Initialize(DB As String)
' Nota: Este código asume que MyHikariPool ya está declarado en Class_Globals
Dim dbKey As String = DB ' Usaremos DB como la llave
If DB.EqualsIgnoreCase("DB1") Then dbKey = ""
poolProperties.Initialize
driverProperties.Initialize
' PASO 1: Cargar la configuración desde el archivo .properties correspondiente.
config = LoadConfigMap(dbKey) ' Aseguramos la carga en la variable de clase [1]
' Lectura de la configuración de tolerancia de parámetros
Dim toleranceSetting As Int = config.GetDefault("parameterTolerance", 0).As(Int)
IsParameterToleranceEnabled = (toleranceSetting = 1)
If IsParameterToleranceEnabled Then
Log($"RDCConnector.Initialize para ${dbKey}: Tolerancia a parámetros extras, HABILITADA."$)
Else
Log($"RDCConnector.Initialize para ${dbKey}: Tolerancia a parámetros extras, DESHABILITADA (modo estricto)."$)
End If
' Bloque Try-Catch para la inicialización y configuración del pool.
Try
Dim driverClass As String = config.Get("DriverClass")
Dim jdbcUrl As String = config.Get("JdbcUrl")
Dim aUser As String = config.Get("User")
Dim aPassword As String = config.Get("Password")
Dim poolType As String = "Hikari" ' Forzamos Hikari
' *** INICIO DE LA LÓGICA DE PRECEDENCIA DE TAMAÑO (HIKARI) ***
Dim maxSizeKey As String = $"pool.${poolType.ToLowerCase}.maximumpoolsize"$
Dim poolSizeString As String
Dim poolSize As Int
' Intentamos leer el valor específico (pool.hikari.maximumpoolsize).
If config.ContainsKey(maxSizeKey) Then
poolSizeString = config.Get(maxSizeKey)
poolSize = poolSizeString.As(Int)
Else
' Si no está definido, usamos el default recomendado por Hikari (10). [2]
poolSize = 10
End If
If poolSize < 1 Then poolSize = 10 ' Mantenemos la sensatez
Log($"RDCConnector: Usando MaximumPoolSize para ${poolType} calculado: ${poolSize}"$)
' *** PASO 2: INICIALIZA/CREA EL POOL LOCALMENTE (Decoupling CRÍTICO) ***
If MyHikariPool.IsInitialized = False Then MyHikariPool.Initialize ' Inicializa el wrapper local
' Crea el pool subyacente (DataSource) en esta instancia dedicada. [3]
MyHikariPool.CreatePool2(driverClass, jdbcUrl, aUser, aPassword, poolSize)
' PASO 3a: Cargar y filtrar SOLO las propiedades del Pool (ej. las que comienzan con 'pool.hikari.')
LoadPoolProperties(poolType, config)
' PASO 3b: Aplicar propiedades de ESTABILIDAD (Pool Properties)
If poolProperties.Size > 0 Then
' Aplicación directa al pool local. [4]
CallSub2(MyHikariPool, "SetProperties", poolProperties)
End If
' PASO 4: Cargar propiedades específicas del Driver (ej. Statement Caching)
If config.ContainsKey("DriverShortName") Then
LoadDriverProperties(config.Get("DriverShortName"), config)
End If
' PASO 5: Aplicar propiedades de RENDIMIENTO (Driver Properties)
If driverProperties.Size > 0 Then
' Aplicación directa al pool local. [5]
CallSub2(MyHikariPool, "SetDriverProperties", driverProperties)
Log($"RDCConnector.Initialize para ${DB}: {driverProperties.Size} propiedades del Driver aplicadas a HikariCP."$)
End If
' PASO 6 (Prueba de vida): Forzar la creación de conexiones iniciales y verificar el estado.
' Esto garantiza el fail-fast. [6]
Dim tempCon As SQL = MyHikariPool.GetConnection
If tempCon.IsInitialized Then
tempCon.Close
End If
' Cargar configuración estática en el cache global
Dim dbKeyToStore As String = DB
If dbKeyToStore = "" Then dbKeyToStore = "DB1"
' Almacenamos el mapa completo (configuración estática + métricas dinámicas iniciales) en el cache global.
' GetPoolStats ahora usa MyHikariPool.
Dim initialPoolStats As Map = GetPoolStats
Main.LatestPoolStats.Put(dbKeyToStore, initialPoolStats)
Catch
' Si ocurre un error durante la inicialización del pool o al forzar la conexión.
Dim ErrorMsg As String = $"RDCConnector.Initialize para ${DB}: ERROR CRÍTICO al inicializar/forzar conexión: ${LastException.Message}"$
Log(ErrorMsg)
Main.LogServerError("ERROR", "RDCConnector.Initialize", ErrorMsg, DB, Null, Null)
' Si falla la inicialización, la instancia local MyHikariPool se queda inutilizada.
' Aquí podríamos considerar la opción de llamar a MyHikariPool.ClosePool para asegurar
' que no queden recursos parciales, aunque HikariCP debería manejarse con fail-fast.
End Try
' Carga los comandos SQL predefinidos de esta base de datos en el mapa global 'commandsMap' de Main.
If dbKey = "" Then dbKey = "DB1"
LoadSQLCommands(config, dbKey)
serverPort = config.Get("ServerPort")
End Sub
' Carga el mapa de configuración
Private Sub LoadConfigMap(DB As String) As Map
Private DBX As String = ""
If DB <> "" Then DBX = "." & DB
Log($"RDCConnector.LoadConfigMap: Leemos el config${DBX}.properties"$)
Return File.ReadMap("./", "config" & DBX & ".properties")
End Sub
' Obtiene la sentencia SQL completa para un comando dado.
Public Sub GetCommand(DB As String, Key As String) As String
commands = Main.commandsMap.Get(DB).As(Map)
If commands.ContainsKey("sql." & Key) = False Then
Dim ErrorMsg As String = $"RDCConnector.GetCommand: *** Comando no encontrado: '${Key}' para DB: '${DB}' ***"$
Log(ErrorMsg)
Main.LogServerError("ERROR", "RDCConnector.GetCommand", ErrorMsg, DB, Key, Null)
End If
Return commands.Get("sql." & Key)
End Sub
' Obtiene una conexión SQL funcional del pool de conexiones.
Public Sub GetConnection(DB As String) As SQL
' If DB.EqualsIgnoreCase("DB1") Then DB = ""
' If DebugQueries Then LoadSQLCommands(LoadConfigMap(DB), DB) ' Deshabilitado por defecto. [13]
' Devolvemos la conexión del pool local, si está inicializado.
If MyHikariPool.IsInitialized Then
Return MyHikariPool.GetConnection
Else
Log($"ERROR: Intento de obtener conexión de DBKey ${DB}, pero MyHikariPool no está inicializado."$)
' Devolver Null o lanzar excepción, dependiendo del manejo de errores deseado.
Return Null
End If
' ANTES: Return Main.ConnectionPoolManager1.GetConnection
End Sub
' Carga todos los comandos SQL del mapa de configuración en el mapa global 'commandsMap' de Main.
Private Sub LoadSQLCommands(config2 As Map, DB As String)
Dim newCommands As Map
newCommands.Initialize
For Each k As String In config2.Keys
If k.StartsWith("sql.") Then
newCommands.Put(k, config2.Get(k))
End If
Next
commands = newCommands
Main.commandsMap.Put(DB, commands)
End Sub
' ** Delegación de estadísticas de C3P0 a HikariCP **
Public Sub GetPoolStats As Map
Dim stats As Map
stats.Initialize
If MyHikariPool.IsInitialized Then
Try
' 2. Llamamos al método delegado GetStats en el wrapper de HikariCP.
Dim hikariStats As Map = MyHikariPool.GetStats
Return hikariStats
Catch
' Fallo en el método GetStats del wrapper.
Dim ErrorMsg As String = $"RDCConnector.GetPoolStats: ERROR CRÍTICO al obtener estadísticas de HikariCP: ${LastException.Message}"$
Log(ErrorMsg)
stats.Put("Error", LastException.Message)
End Try
Else
stats.Put("Error", "Pool local MyHikariPool no inicializado.")
End If
Return stats
End Sub
' *** NUEVA SUBRUTINA: Cierra el pool de conexiones de forma ordenada ***
Public Sub Close
If MyHikariPool.IsInitialized Then
' Cierre limpio del pool subyacente.
MyHikariPool.ClosePool
Log($"RDCConnector.Close: Pool Hikari cerrado limpiamente para este conector."$)
End If
' Ya NO delegamos el cierre al Manager.
' ANTES: Main.ConnectionPoolManager1.ClosePoolByType(poolType) [15]
End Sub
' --- SUBRUTINAS DE UTILIDAD PARA CARGA DE PROPIEDADES ---
' [2]
Private Sub LoadDriverProperties(driverShortName As String, config_ As Map)
driverProperties = ExtractProperties($"driver.${driverShortName.trim}."$, config_, Null, Null)
End Sub
' [3]
Private Sub ExtractProperties(prefix As String, input As Map, newPrefix As String, output As Map) As Map
Dim properties As Map
If output = Null Or output.IsInitialized = False Then
properties.Initialize
Else
properties = output
End If
If newPrefix.EqualsIgnoreCase(Null) Then newPrefix = ""
Dim prefixLength As Int = prefix.Length
' Log($"Prefijo=${prefix}, ${newPrefix}"$)
For Each k As String In input.Keys
' Log(k)
If k.ToLowerCase.StartsWith(prefix) Then
' Log($"found ${prefix}"$)
Dim standardizedKey As String = k.SubString(prefixLength).ToLowerCase
' Log("Ponemos: " & $"${newPrefix}${k.SubString(prefixLength)}, ${input.Get(k)}"$)
properties.Put($"${newPrefix}${standardizedKey}"$, input.Get(k))
End If
Next
Return properties
End Sub
Private Sub LoadPoolProperties(poolType As String, config_ As Map)
' Busca entradas como 'pool.hikari.<propiedad>' y las extrae.
poolProperties = ExtractProperties($"pool.${poolType.ToLowerCase}."$, config_, Null, Null)
End Sub