package com.yj.earth.common.util; import org.apache.commons.dbcp2.BasicDataSource; import java.sql.*; import java.util.*; import java.lang.reflect.*; import java.util.concurrent.ConcurrentHashMap; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; /** * 优化版SQLite工具类 */ public class SQLiteUtil { // 加载SQLite JDBC驱动(静态初始化) static { try { Class.forName("org.sqlite.JDBC"); } catch (ClassNotFoundException e) { throw new RuntimeException("无法加载SQLite JDBC驱动", e); } } // 统一日期格式(LocalDateTime) private static final DateTimeFormatter LOCAL_DATE_TIME_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME; // 连接池缓存:key=数据库文件路径、value=DBCP2数据源(支持多连接复用) private static final Map DATA_SOURCE_POOL = new ConcurrentHashMap<>(); // 字段缓存:缓存类的字段映射(避免反射重复开销) private static final Map, Map> FIELD_CACHE = new ConcurrentHashMap<>(); // ========================== 连接池核心方法 ========================== /** * 从DBCP2连接池获取连接(自动复用/创建连接) * * @param dbFilePath 数据库文件绝对路径 * @return 线程安全的Connection(使用后需通过try-with-resources自动归还) */ public static Connection getConnection(String dbFilePath) throws SQLException { if (dbFilePath == null || dbFilePath.trim().isEmpty()) { throw new IllegalArgumentException("数据库文件路径不能为空"); } // 不存在则创建数据源、存在则直接从池获取连接 BasicDataSource dataSource = DATA_SOURCE_POOL.computeIfAbsent(dbFilePath, SQLiteUtil::createDataSource); return dataSource.getConnection(); } /** * 创建DBCP2数据源(配置SQLite性能参数) * * @param dbFilePath 数据库文件路径 * @return 配置优化后的BasicDataSource */ private static BasicDataSource createDataSource(String dbFilePath) { BasicDataSource dataSource = new BasicDataSource(); // 1. 基础JDBC配置 dataSource.setDriverClassName("org.sqlite.JDBC"); dataSource.setUrl("jdbc:sqlite:" + dbFilePath); // 2. 连接池核心参数(根据并发量调整、SQLite不建议过多连接) dataSource.setMaxTotal(30); // 最大连接数:20-50(根据服务器CPU/内存调整) dataSource.setMaxIdle(15); // 最大空闲连接:保留部分连接避免频繁创建 dataSource.setMinIdle(5); // 最小空闲连接:保证基础并发响应速度 dataSource.setTimeBetweenEvictionRunsMillis(60000); // 连接检测间隔:1分钟 dataSource.setMinEvictableIdleTimeMillis(300000); // 连接空闲超时:5分钟(清理长期空闲连接) // 3. 连接有效性验证(避免使用失效连接) dataSource.setTestOnBorrow(true); // 借连接时验证 dataSource.setTestOnReturn(false); // 还连接时不验证(减少开销) dataSource.setValidationQuery("SELECT 1"); // 轻量验证SQL(SQLite支持) dataSource.setValidationQueryTimeout(2); // 验证超时:2秒 // 4. PreparedStatement缓存(减少SQL解析开销) dataSource.setPoolPreparedStatements(true); dataSource.setMaxOpenPreparedStatements(100); // 最大缓存100个PreparedStatement // 5. SQLite底层性能优化(关键!提升并发能力) try (Connection conn = dataSource.getConnection(); Statement stmt = conn.createStatement()) { stmt.execute("PRAGMA journal_mode=WAL;"); // 启用WAL模式:支持多读者+单写者(核心优化) stmt.execute("PRAGMA cache_size=-20000;"); // 页面缓存20MB(负号表示KB单位、内存足可调大) stmt.execute("PRAGMA synchronous=NORMAL;"); // 同步级别:平衡性能与安全(崩溃最多丢WAL日志) stmt.execute("PRAGMA temp_store=MEMORY;"); // 临时表/索引存内存(减少磁盘IO) stmt.execute("PRAGMA busy_timeout=2000;"); // 忙等待超时:2秒(避免瞬时并发锁等待) } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException("初始化SQLite数据源失败(路径:" + dbFilePath + ")+ 原因是:" + e.getMessage()); } return dataSource; } /** * 关闭指定数据库的数据源(释放所有连接) * * @param dbFilePath 数据库文件路径 */ public static void closeDataSource(String dbFilePath) { if (dbFilePath == null) return; BasicDataSource dataSource = DATA_SOURCE_POOL.remove(dbFilePath); if (dataSource != null) { try { dataSource.close(); // DBCP2会自动关闭所有活跃/空闲连接 } catch (SQLException e) { System.err.println("关闭SQLite数据源失败(路径:" + dbFilePath + "):" + e.getMessage()); } } } /** * 关闭所有数据源(JVM退出时自动调用) */ public static void closeAllDataSources() { for (BasicDataSource dataSource : DATA_SOURCE_POOL.values()) { try { dataSource.close(); } catch (SQLException e) { System.err.println("关闭SQLite数据源失败:" + e.getMessage()); } } // 清理缓存(避免内存泄漏) DATA_SOURCE_POOL.clear(); FIELD_CACHE.clear(); } // JVM关闭钩子:确保程序退出时释放所有数据源资源 static { Runtime.getRuntime().addShutdownHook(new Thread(SQLiteUtil::closeAllDataSources)); } // ========================== 数据查询核心方法 ========================== /** * 执行查询并返回单个对象(优化版:连接池+字段缓存) * * @param dbFilePath 数据库路径 * @param sql 查询SQL * @param params SQL参数 * @param clazz 返回对象类型 * @return 单个对象(无结果返回null) */ public static T queryForObject(String dbFilePath, String sql, List params, Class clazz) throws SQLException { List results = queryForList(dbFilePath, sql, params, clazz); return results.isEmpty() ? null : results.get(0); } /** * 执行查询并返回对象列表(优化版:预构建字段映射+资源自动回收) * * @param dbFilePath 数据库路径 * @param sql 查询SQL * @param params SQL参数 * @param clazz 返回对象类型 * @return 结果列表(无结果返回空列表) */ public static List queryForList(String dbFilePath, String sql, List params, Class clazz) throws SQLException { List resultList = new ArrayList<>(); if (sql == null || sql.trim().isEmpty()) { throw new IllegalArgumentException("查询SQL不能为空"); } // 预加载字段映射(缓存生效、避免重复反射) Map fieldMap = getFieldMap(clazz); // try-with-resources:自动关闭Connection、PreparedStatement、ResultSet try (Connection conn = getConnection(dbFilePath); PreparedStatement pstmt = createPreparedStatement(conn, sql, params); ResultSet rs = pstmt.executeQuery()) { ResultSetMetaData metaData = rs.getMetaData(); int columnCount = metaData.getColumnCount(); // 预构建「列名-字段」映射(一次构建、循环复用) ColumnFieldMapping[] mappings = buildColumnFieldMappings(metaData, columnCount, fieldMap); // 处理结果集(反射赋值) while (rs.next()) { try { T obj = clazz.getDeclaredConstructor().newInstance(); // 无参构造器(需确保类有) for (int i = 0; i < columnCount; i++) { ColumnFieldMapping mapping = mappings[i]; if (mapping.field != null) { Object value = rs.getObject(i + 1); // ResultSet列索引从1开始 setFieldValueOptimized(obj, mapping.field, value); } } resultList.add(obj); } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { throw new SQLException("创建对象实例失败(类型:" + clazz.getName() + ")", e); } } } catch (SQLException e) { // 异常时关闭当前数据源(避免后续请求使用异常连接) closeDataSource(dbFilePath); throw new SQLException("执行查询失败(SQL:" + sql + ")", e); } return resultList; } /** * 执行增删改SQL(优化版:连接池+自动提交) * * @param dbFilePath 数据库路径 * @param sql 增删改SQL * @param params SQL参数 * @return 影响行数 */ public static int executeUpdate(String dbFilePath, String sql, List params) throws SQLException { if (sql == null || sql.trim().isEmpty()) { throw new IllegalArgumentException("执行SQL不能为空"); } try (Connection conn = getConnection(dbFilePath); PreparedStatement pstmt = createPreparedStatement(conn, sql, params)) { return pstmt.executeUpdate(); } catch (SQLException e) { closeDataSource(dbFilePath); throw new SQLException("执行增删改失败(SQL:" + sql + ")", e); } } /** * 执行计数查询(优化版:轻量结果处理) * * @param dbFilePath 数据库路径 * @param sql 计数SQL(如SELECT COUNT(*) ...) * @param params SQL参数 * @return 计数结果(无结果返回0) */ public static int queryForCount(String dbFilePath, String sql, List params) throws SQLException { if (sql == null || sql.trim().isEmpty()) { throw new IllegalArgumentException("计数SQL不能为空"); } try (Connection conn = getConnection(dbFilePath); PreparedStatement pstmt = createPreparedStatement(conn, sql, params); ResultSet rs = pstmt.executeQuery()) { return rs.next() ? rs.getInt(1) : 0; } catch (SQLException e) { closeDataSource(dbFilePath); throw new SQLException("执行计数查询失败(SQL:" + sql + ")", e); } } /** * 执行DDL语句(创建表/索引等) * * @param dbFilePath 数据库路径 * @param sql DDL语句 * @param params SQL参数(可选、如动态表名) */ public static void executeDDL(String dbFilePath, String sql, List params) throws SQLException { if (dbFilePath == null || dbFilePath.trim().isEmpty()) { throw new IllegalArgumentException("数据库文件路径不能为空"); } if (sql == null || sql.trim().isEmpty()) { throw new IllegalArgumentException("DDL语句不能为空"); } try (Connection conn = getConnection(dbFilePath); PreparedStatement pstmt = createPreparedStatement(conn, sql, params)) { pstmt.execute(); } catch (SQLException e) { closeDataSource(dbFilePath); throw new SQLException("执行DDL失败(SQL:" + sql + "、路径:" + dbFilePath + ")", e); } } /** * 重载:无参数的DDL执行 */ public static void executeDDL(String dbFilePath, String sql) throws SQLException { executeDDL(dbFilePath, sql, null); } // ========================== 工具辅助方法 ========================== /** * 创建PreparedStatement(复用Connection、避免重复获取) * * @param conn 已获取的Connection(从连接池来) * @param sql SQL语句 * @param params 参数列表 * @return 配置好的PreparedStatement */ private static PreparedStatement createPreparedStatement(Connection conn, String sql, List params) throws SQLException { PreparedStatement pstmt = conn.prepareStatement(sql); // 设置SQL参数(处理特殊类型如LocalDateTime、byte[]) if (params != null && !params.isEmpty()) { for (int i = 0; i < params.size(); i++) { setPreparedStatementValue(pstmt, i + 1, params.get(i)); } } return pstmt; } /** * 设置PreparedStatement参数(处理SQLite特殊类型映射) * * @param pstmt PreparedStatement对象 * @param index 参数索引(从1开始) * @param value 参数值 */ private static void setPreparedStatementValue(PreparedStatement pstmt, int index, Object value) throws SQLException { if (value == null) { pstmt.setNull(index, Types.NULL); return; } // 特殊类型处理 if (value instanceof LocalDateTime) { // LocalDateTime转字符串(SQLite无原生DateTime类型) String dateStr = ((LocalDateTime) value).format(LOCAL_DATE_TIME_FORMATTER); pstmt.setString(index, dateStr); } else if (value instanceof byte[]) { // 二进制数据(如BLOB) pstmt.setBytes(index, (byte[]) value); } else if (value instanceof java.util.Date) { // Date转Timestamp(兼容SQLite时间处理) pstmt.setTimestamp(index, new Timestamp(((java.util.Date) value).getTime())); } else { // 通用类型(依赖JDBC自动映射) pstmt.setObject(index, value); } } /** * 预构建「列名-字段」映射(支持下划线转驼峰) * * @param metaData 结果集元数据 * @param columnCount 列数 * @param fieldMap 类字段缓存 * @return 映射数组(与列顺序对应) */ private static ColumnFieldMapping[] buildColumnFieldMappings(ResultSetMetaData metaData, int columnCount, Map fieldMap) throws SQLException { ColumnFieldMapping[] mappings = new ColumnFieldMapping[columnCount]; for (int i = 0; i < columnCount; i++) { String columnName = metaData.getColumnName(i + 1); // 列名(如military_name) ColumnFieldMapping mapping = new ColumnFieldMapping(); mapping.columnName = columnName; // 1. 直接匹配字段名(如列名=字段名) Field field = fieldMap.get(columnName); // 2. 下划线转驼峰匹配(如military_name → militaryName) if (field == null) { String camelCaseName = underscoreToCamelCase(columnName); field = fieldMap.get(camelCaseName); } mapping.field = field; mappings[i] = mapping; } return mappings; } /** * 获取类的字段缓存(含父类字段、一次反射永久缓存) * * @param clazz 目标类 * @return 字段名→Field的映射(不可修改) */ private static Map getFieldMap(Class clazz) { return FIELD_CACHE.computeIfAbsent(clazz, key -> { Map fieldMap = new HashMap<>(); // 遍历当前类及所有父类(直到Object) Class currentClass = clazz; while (currentClass != null && currentClass != Object.class) { Field[] fields = currentClass.getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); // 突破访问权限(private字段可赋值) fieldMap.put(field.getName(), field); } currentClass = currentClass.getSuperclass(); } return Collections.unmodifiableMap(fieldMap); // 避免外部修改缓存 }); } /** * 优化的字段赋值(处理类型转换、避免重复异常捕获) * * @param obj 目标对象 * @param field 字段 * @param value 待赋值的值 */ private static void setFieldValueOptimized(Object obj, Field field, Object value) { if (value == null) return; try { Class fieldType = field.getType(); // 类型匹配直接赋值(无转换开销) if (fieldType.isInstance(value)) { field.set(obj, value); return; } // 类型不匹配时转换(支持基础类型、时间、二进制等) Object convertedValue = convertValueOptimized(fieldType, value); if (convertedValue != null) { field.set(obj, convertedValue); } } catch (IllegalAccessException e) { System.err.println("警告:字段赋值失败(字段:" + field.getName() + "、值类型:" + value.getClass().getName() + "):" + e.getMessage()); } } /** * 优化的类型转换(支持常见SQLite类型→Java类型) * * @param targetType 目标类型(字段类型) * @param value 原始值(ResultSet获取的值) * @return 转换后的值(无法转换返回null) */ private static Object convertValueOptimized(Class targetType, Object value) { // 基础数值类型转换 if (targetType == int.class || targetType == Integer.class) { if (value instanceof Number) return ((Number) value).intValue(); if (value instanceof String) return parseInteger((String) value); } else if (targetType == long.class || targetType == Long.class) { if (value instanceof Number) return ((Number) value).longValue(); if (value instanceof String) return parseLong((String) value); } else if (targetType == double.class || targetType == Double.class) { if (value instanceof Number) return ((Number) value).doubleValue(); if (value instanceof String) return parseDouble((String) value); } else if (targetType == boolean.class || targetType == Boolean.class) { if (value instanceof Number) return ((Number) value).intValue() != 0; if (value instanceof String) return "true".equalsIgnoreCase((String) value) || "1".equals(value); } // 字符串类型 else if (targetType == String.class) { return value.toString(); } // 时间类型(LocalDateTime) else if (targetType == LocalDateTime.class) { return convertToLocalDateTime(value); } // 时间类型(Date) else if (targetType == java.util.Date.class) { return convertToDate(value); } // 二进制类型(byte[]) else if (targetType == byte[].class) { return convertToByteArray(value); } // 不支持的类型转换(打印警告) System.err.println("警告:不支持的类型转换(目标类型:" + targetType.getName() + "、原始值类型:" + value.getClass().getName() + ")"); return null; } // ========================== 类型转换辅助方法 ========================== private static Integer parseInteger(String value) { try { return Integer.parseInt(value.trim()); } catch (NumberFormatException e) { System.err.println("警告:字符串转Integer失败(值:" + value + ")"); return null; } } private static Long parseLong(String value) { try { return Long.parseLong(value.trim()); } catch (NumberFormatException e) { System.err.println("警告:字符串转Long失败(值:" + value + ")"); return null; } } private static Double parseDouble(String value) { try { return Double.parseDouble(value.trim()); } catch (NumberFormatException e) { System.err.println("警告:字符串转Double失败(值:" + value + ")"); return null; } } private static LocalDateTime convertToLocalDateTime(Object value) { if (value instanceof String) { try { return LocalDateTime.parse((String) value, LOCAL_DATE_TIME_FORMATTER); } catch (DateTimeParseException e) { System.err.println("警告:字符串转LocalDateTime失败(值:" + value + ")"); return null; } } else if (value instanceof Timestamp) { return ((Timestamp) value).toLocalDateTime(); } return null; } private static java.util.Date convertToDate(Object value) { if (value instanceof Timestamp) { return new java.util.Date(((Timestamp) value).getTime()); } else if (value instanceof LocalDateTime) { return java.util.Date.from(((LocalDateTime) value).atZone(java.time.ZoneId.systemDefault()).toInstant()); } return null; } private static byte[] convertToByteArray(Object value) { if (value instanceof Blob) { Blob blob = (Blob) value; try { return blob.getBytes(1, (int) blob.length()); } catch (SQLException e) { System.err.println("警告:Blob转byte[]失败:" + e.getMessage()); } } return null; } /** * 下划线命名转驼峰命名(如military_type → militaryType) * * @param str 下划线字符串 * @return 驼峰字符串 */ private static String underscoreToCamelCase(String str) { if (str == null || str.isEmpty()) return str; StringBuilder sb = new StringBuilder(); boolean nextUpperCase = false; for (char c : str.toCharArray()) { if (c == '_') { nextUpperCase = true; } else { if (nextUpperCase) { sb.append(Character.toUpperCase(c)); nextUpperCase = false; } else { sb.append(Character.toLowerCase(c)); } } } return sb.toString(); } // ========================== 内部辅助类 ========================== /** * 列-字段映射模型(一次性构建、减少循环内计算) */ private static class ColumnFieldMapping { String columnName; // 数据库列名 Field field; // 对应的Java字段 } /** * 初始化模型相关表 */ public static void initializationModel(String modelPath) throws SQLException { String sql = """ CREATE TABLE IF NOT EXISTS "model_type" ( "id" TEXT, "name" TEXT, "parent_id" TEXT, "tree_index" INTEGER, "created_at" TEXT, "updated_at" TEXT, PRIMARY KEY ("id") ); """; executeDDL(modelPath, sql); sql = """ CREATE TABLE IF NOT EXISTS "model" ( "id" TEXT, "model_type_id" TEXT, "model_name" TEXT, "model_type" TEXT, "model_data" BLOB, "poster_type" TEXT, "poster_data" BLOB, "created_at" TEXT, "updated_at" TEXT, PRIMARY KEY ("id") ); """; executeDDL(modelPath, sql); } /** * 初始化军标相关表 */ public static void initializationMilitary(String militaryPath) throws SQLException { String sql = """ CREATE TABLE IF NOT EXISTS "military_type" ( "id" TEXT, "name" TEXT, "parent_id" TEXT, "tree_index" INTEGER, "created_at" TEXT, "updated_at" TEXT, PRIMARY KEY ("id") ); """; executeDDL(militaryPath, sql); sql = """ CREATE TABLE IF NOT EXISTS "military" ( "id" TEXT, "military_type_id" TEXT, "military_name" TEXT, "military_type" TEXT, "military_data" BLOB, "created_at" TEXT, "updated_at" TEXT, PRIMARY KEY ("id") ); """; executeDDL(militaryPath, sql); } /** * 初始化图标相关表 */ public static void initializationIcon(String iconPath) throws SQLException { String sql = """ CREATE TABLE IF NOT EXISTS "icon_type" ( "id" TEXT, "name" TEXT, "parent_id" TEXT, "tree_index" INTEGER, "created_at" TEXT, "updated_at" TEXT, PRIMARY KEY ("id") ); """; executeDDL(iconPath, sql); sql = """ CREATE TABLE IF NOT EXISTS "icon" ( "id" TEXT, "icon_type_id" TEXT, "icon_name" TEXT, "icon_type" TEXT, "icon_data" BLOB, "created_at" TEXT, "updated_at" TEXT, PRIMARY KEY ("id") ); """; executeDDL(iconPath, sql); } }