177 lines
6.9 KiB
Java
177 lines
6.9 KiB
Java
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> {
|
|
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=<redacted>");
|
|
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> T executeInTransaction(TransactionCallback<T> 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<redacted>");
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|