Files
yjearth/src/main/java/com/yj/earth/common/util/SQLiteUtil.java
2025-11-25 14:27:10 +08:00

657 lines
26 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<String, BasicDataSource> DATA_SOURCE_POOL = new ConcurrentHashMap<>();
// 字段缓存: 缓存类的字段映射(避免反射重复开销)
private static final Map<Class<?>, Map<String, Field>> 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"); // 轻量验证SQLSQLite支持
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("请检查路径是否存在");
}
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> T queryForObject(String dbFilePath, String sql, List<Object> params, Class<T> clazz) throws SQLException {
List<T> 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 <T> List<T> queryForList(String dbFilePath, String sql, List<Object> params, Class<T> clazz) throws SQLException {
List<T> resultList = new ArrayList<>();
if (sql == null || sql.trim().isEmpty()) {
throw new IllegalArgumentException("查询SQL不能为空");
}
// 预加载字段映射(缓存生效、避免重复反射)
Map<String, Field> 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<Object> 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<Object> 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<Object> 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<Object> 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<String, Field> 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<String, Field> getFieldMap(Class<?> clazz) {
return FIELD_CACHE.computeIfAbsent(clazz, key -> {
Map<String, Field> 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);
}
}