package com.mzh.library.util; import com.mzh.library.exception.DaoException; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; public final class JdbcUtil { private static final Logger LOGGER = Logger.getLogger(JdbcUtil.class.getName()); private static final String CONFIG_FILE = "db.properties"; private static final String DEFAULT_DRIVER = "com.mysql.cj.jdbc.Driver"; private static final String DRIVER_KEY = "db.driver"; private static final String URL_KEY = "db.url"; private static final String USERNAME_KEY = "db.username"; private static final String PASSWORD_KEY = "db.password"; @FunctionalInterface public interface TransactionCallback { T execute(Connection connection) throws SQLException; } private JdbcUtil() { } public static Connection getConnection() { Properties properties = loadProperties(); String driver = properties.getProperty(DRIVER_KEY, DEFAULT_DRIVER); String url = required(properties, URL_KEY); String username = required(properties, USERNAME_KEY); String password = required(properties, PASSWORD_KEY); LOGGER.info("Database connection configuration resolved" + " file=" + CONFIG_FILE + " driverKey=" + DRIVER_KEY + " driver=" + safeLogValue(driver) + " jdbcUrl=" + redactJdbcUrl(url) + " usernameKey=" + USERNAME_KEY + " usernameConfigured=" + !username.isEmpty() + " password="); LOGGER.info("Database connection attempt" + " driverKey=" + DRIVER_KEY + " driver=" + safeLogValue(driver) + " jdbcUrl=" + redactJdbcUrl(url) + " usernameKey=" + USERNAME_KEY); try { Class.forName(driver); Connection connection = DriverManager.getConnection(url, username, password); LOGGER.info("Database connection established jdbcUrl=" + redactJdbcUrl(url) + " usernameKey=" + USERNAME_KEY); return connection; } catch (ClassNotFoundException ex) { LOGGER.log(Level.SEVERE, "JDBC driver unavailable driver=" + safeLogValue(driver) + " jdbcUrl=" + redactJdbcUrl(url) + " usernameKey=" + USERNAME_KEY, ex); throw new DaoException("Unable to open database connection", ex); } catch (SQLException ex) { SQLException safeException = safeSqlException(ex); LOGGER.log(Level.SEVERE, "Database connection failed driver=" + safeLogValue(driver) + " jdbcUrl=" + redactJdbcUrl(url) + " usernameKey=" + USERNAME_KEY, safeException); throw new DaoException("Unable to open database connection", safeException); } } public static T executeInTransaction(TransactionCallback callback) { try (Connection connection = getConnection()) { connection.setAutoCommit(false); try { T result = callback.execute(connection); connection.commit(); return result; } catch (SQLException ex) { rollbackQuietly(connection); throw new DaoException("Unable to complete database transaction", ex); } catch (RuntimeException ex) { rollbackQuietly(connection); throw ex; } } catch (SQLException ex) { throw new DaoException("Unable to complete database transaction", ex); } } private static void rollbackQuietly(Connection connection) { try { connection.rollback(); } catch (SQLException ignored) { // Preserve the original transaction failure for callers and logs. } } private static Properties loadProperties() { try (InputStream inputStream = Thread.currentThread() .getContextClassLoader() .getResourceAsStream(CONFIG_FILE)) { if (inputStream == null) { LOGGER.severe("Database configuration file not found file=" + CONFIG_FILE); throw new DaoException("Missing database configuration file: " + CONFIG_FILE, null); } Properties properties = new Properties(); properties.load(inputStream); LOGGER.info("Database configuration loaded file=" + CONFIG_FILE + " driverConfigured=" + hasText(properties, DRIVER_KEY) + " urlConfigured=" + hasText(properties, URL_KEY) + " usernameConfigured=" + hasText(properties, USERNAME_KEY) + " passwordConfigured=" + hasText(properties, PASSWORD_KEY)); return properties; } catch (IOException ex) { LOGGER.log(Level.SEVERE, "Unable to read database configuration file=" + CONFIG_FILE, ex); throw new DaoException("Unable to read database configuration", ex); } } private static String required(Properties properties, String key) { String value = properties.getProperty(key); if (value == null || value.trim().isEmpty()) { LOGGER.severe("Missing database configuration value key=" + key); throw new DaoException("Missing database configuration value: " + key, null); } return value.trim(); } private static String redactJdbcUrl(String value) { if (value == null) { return ""; } return safeLogValue(redactSensitive(value)); } private static SQLException safeSqlException(SQLException ex) { SQLException safeException = new SQLException( safeLogValue(redactSensitive(ex.getMessage())), ex.getSQLState(), ex.getErrorCode() ); safeException.setStackTrace(ex.getStackTrace()); return safeException; } private static String redactSensitive(String value) { if (value == null) { return ""; } return value.replaceAll("(?i)(password|pwd|pass|secret|token)(\\s*[=:]\\s*)([^;&\\s]*)", "$1$2"); } private static boolean hasText(Properties properties, String key) { String value = properties.getProperty(key); return value != null && !value.trim().isEmpty(); } private static String safeLogValue(String value) { if (value == null) { return ""; } StringBuilder builder = new StringBuilder(); int limit = Math.min(value.length(), 240); for (int i = 0; i < limit; i++) { char current = value.charAt(i); builder.append(Character.isISOControl(current) ? '?' : current); } if (value.length() > limit) { builder.append("..."); } return builder.toString(); } }