模型库、矢量文件

This commit is contained in:
ZZX9599
2025-09-29 17:34:21 +08:00
parent 94add25716
commit efe4ddf97b
11 changed files with 1159 additions and 719 deletions

View File

@ -7,6 +7,7 @@ import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Data
@Component
public class ServerConfig {

View File

@ -29,20 +29,20 @@ public class FileCommonUtil {
public static MediaType getImageMediaType(String suffix) {
String lowerSuffix = suffix.toLowerCase();
switch (lowerSuffix) {
case ".jpg":
case ".jpeg":
case "jpg":
case "jpeg":
return MediaType.IMAGE_JPEG;
case ".glb":
case "glb":
return MediaType.valueOf("model/gltf-binary");
case ".png":
case "png":
return MediaType.IMAGE_PNG;
case ".gif":
case "gif":
return MediaType.IMAGE_GIF;
case ".bmp":
case "bmp":
return MediaType.valueOf("image/bmp");
case ".webp":
case "webp":
return MediaType.valueOf("image/webp");
case ".svg":
case "svg":
return MediaType.valueOf("image/svg+xml");
default:
return MediaType.APPLICATION_OCTET_STREAM;

View File

@ -1,274 +1,249 @@
package com.yj.earth.common.util;
import cn.hutool.core.lang.UUID;
import java.sql.*;
import java.util.UUID;
import java.time.LocalDateTime;
public class SQLiteConverter {
// SQLite JDBC驱动
private static final String JDBC_DRIVER = "org.sqlite.JDBC";
// 源数据库和目标数据库路径
private String sourceDbPath;
private String targetDbPath;
// 批处理大小、可根据内存情况调整
private static final int BATCH_SIZE = 100;
// 原始数据库路径
private static final String ORIGINAL_DB_PATH = "jdbc:sqlite:D:\\YJEarth.model";
public SQLiteConverter(String sourceDbPath, String targetDbPath) {
this.sourceDbPath = sourceDbPath;
this.targetDbPath = targetDbPath;
}
// 新数据库路径
private static final String NEW_DB_PATH = "jdbc:sqlite:E:\\通用模型库.model";
public void convert() {
Connection sourceConn = null;
Connection targetConn = null;
try {
// 加载驱动
Class.forName(JDBC_DRIVER);
// 连接源数据库和目标数据库
sourceConn = DriverManager.getConnection("jdbc:sqlite:" + sourceDbPath);
targetConn = DriverManager.getConnection("jdbc:sqlite:" + targetDbPath);
// 禁用自动提交、以便在出现错误时可以回滚
targetConn.setAutoCommit(false);
// 创建目标表结构
createTargetTables(targetConn);
// 复制并转换数据
copyModeTypesData(sourceConn, targetConn);
copyModelsData(sourceConn, targetConn);
// 为model表添加索引
createModelTableIndexes(targetConn);
// 提交事务
targetConn.commit();
System.out.println("数据库转换成功!");
} catch (Exception e) {
e.printStackTrace();
try {
if (targetConn != null) {
targetConn.rollback();
System.out.println("转换失败、已回滚操作!");
}
} catch (SQLException ex) {
ex.printStackTrace();
}
} finally {
// 关闭连接
try {
if (sourceConn != null) sourceConn.close();
if (targetConn != null) targetConn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
private void createTargetTables(Connection conn) throws SQLException {
System.out.println("开始创建目标表结构...");
Statement stmt = conn.createStatement();
String sql = """
CREATE TABLE "model_type" (
"id" TEXT,
"name" TEXT,
"parent_id" TEXT,
"tree_index" INTEGER,
"created_at" TEXT,
"updated_at" TEXT,
PRIMARY KEY ("id")
);
""";
stmt.execute(sql);
sql = """
CREATE TABLE "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")
);
""";
stmt.execute(sql);
stmt.close();
System.out.println("目标表结构创建完成");
}
/**
* 为 model 表的每个字段创建索引
*/
private void createModelTableIndexes(Connection conn) throws SQLException {
System.out.println("开始为创建索引...");
Statement stmt = conn.createStatement();
String sql = """
CREATE INDEX idx_model_covering ON model(
model_type_id,
id,
model_name,
model_type,
poster_type,
created_at,
updated_at
);
""";
stmt.execute(sql);
stmt.close();
System.out.println("model表索引创建完成");
}
private int getTotalRecords(Connection conn, String tableName) throws SQLException {
PreparedStatement stmt = conn.prepareStatement("SELECT COUNT(*) AS total FROM " + tableName);
ResultSet rs = stmt.executeQuery();
int total = rs.next() ? rs.getInt("total") : 0;
rs.close();
stmt.close();
return total;
}
private void copyModeTypesData(Connection sourceConn, Connection targetConn) throws SQLException {
int totalRecords = getTotalRecords(sourceConn, "mode_types");
System.out.println("开始转换 mode_types 表数据、共" + totalRecords + "条记录");
PreparedStatement sourceStmt = sourceConn.prepareStatement("SELECT * FROM mode_types");
ResultSet rs = sourceStmt.executeQuery();
PreparedStatement targetStmt = targetConn.prepareStatement(
"INSERT INTO model_type (id, name, parent_id, tree_index, created_at, updated_at) " +
"VALUES (?, ?, ?, ?, ?, ?)"
);
int count = 0;
while (rs.next()) {
targetStmt.setString(1, rs.getString("type_id"));
targetStmt.setString(2, rs.getString("type_name"));
targetStmt.setString(3, rs.getString("p_id"));
targetStmt.setInt(4, 0);
targetStmt.setObject(5, LocalDateTime.now());
targetStmt.setObject(6, LocalDateTime.now());
// 添加到批处理
targetStmt.addBatch();
count++;
// 每达到批处理大小或最后一条记录时执行批处理
if (count % BATCH_SIZE == 0 || count == totalRecords) {
targetStmt.executeBatch();
displayProgress(count, totalRecords, "mode_types 表");
}
}
System.out.println("\n成功转换 mode_types 表数据:" + count + "条记录");
rs.close();
sourceStmt.close();
targetStmt.close();
}
private void copyModelsData(Connection sourceConn, Connection targetConn) throws SQLException {
int totalRecords = getTotalRecords(sourceConn, "models");
System.out.println("开始转换 models 表数据、共" + totalRecords + "条记录");
// 对于大字段、使用向前滚动的结果集、避免缓存所有数据
PreparedStatement sourceStmt = sourceConn.prepareStatement(
"SELECT * FROM models",
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY
);
sourceStmt.setFetchSize(100);
ResultSet rs = sourceStmt.executeQuery();
PreparedStatement targetStmt = targetConn.prepareStatement(
"INSERT INTO model (id, model_type_id, model_name, model_type, model_data, " +
"poster_type, poster_data, created_at, updated_at) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
);
int count = 0;
while (rs.next()) {
targetStmt.setString(1, rs.getString("id"));
targetStmt.setString(2, rs.getString("p_id"));
targetStmt.setString(3, rs.getString("model_name"));
targetStmt.setString(4, rs.getString("model_type"));
byte[] dataBytes = rs.getBytes("data");
if (dataBytes != null) {
targetStmt.setBytes(5, dataBytes);
} else {
targetStmt.setNull(5, Types.BLOB);
}
targetStmt.setString(6, rs.getString("poster_type"));
byte[] posterBytes = rs.getBytes("poster");
if (posterBytes != null) {
targetStmt.setBytes(7, posterBytes);
}
targetStmt.setObject(8, LocalDateTime.now());
targetStmt.setObject(9, LocalDateTime.now());
// 添加到批处理
targetStmt.addBatch();
count++;
// 执行批处理
if (count % BATCH_SIZE == 0 || count == totalRecords) {
targetStmt.executeBatch();
displayProgress(count, totalRecords, "models 表");
}
}
System.out.println("\n成功转换 models 表数据:" + count + "条记录");
rs.close();
sourceStmt.close();
targetStmt.close();
}
/**
* 显示进度信息
*/
private void displayProgress(int current, int total, String tableName) {
double percentage = (double) current / total * 100;
// 清除当前行并显示进度
System.out.printf("\r%s: 已完成 %.1f%% (%d/%d条)", tableName, percentage, current, total);
}
public static void main(String[] args) {
System.out.println("===== 开始数据库转换程序 =====");
System.out.println("原始数据库: " + ORIGINAL_DB_PATH);
System.out.println("目标数据库: " + NEW_DB_PATH + "\n");
// 源数据库路径
String sourcePath = "F:\\公司通用模型库.model";
// 目标数据库路径
String targetPath = "F:\\通用模型库.model";
// 使用try-with-resources自动管理连接资源
try (Connection originalConn = DriverManager.getConnection(ORIGINAL_DB_PATH);
Connection newConn = DriverManager.getConnection(NEW_DB_PATH)) {
System.out.println("开始数据库转换...");
System.out.println("源数据库: " + sourcePath);
System.out.println("目标数据库: " + targetPath);
System.out.println("✅ 成功连接到原始数据库");
System.out.println("✅ 成功创建并连接到新数据库\n");
long startTime = System.currentTimeMillis();
// 在新数据库中创建表结构
System.out.println("===== 开始创建新表结构 =====");
createNewTables(newConn);
System.out.println("===== 新表结构创建完成 =====");
// 创建转换器并执行转换
SQLiteConverter converter = new SQLiteConverter(sourcePath, targetPath);
converter.convert();
// 迁移mode_types表数据到model_type表
System.out.println("\n===== 开始迁移mode_types表数据 =====");
MigrationResult modeTypesResult = migrateModeTypes(originalConn, newConn);
System.out.println("===== mode_types表数据迁移完成 =====");
System.out.printf(" 成功: %d 条, 失败: %d 条, 总计: %d 条%n",
modeTypesResult.successCount,
modeTypesResult.failureCount,
modeTypesResult.totalCount);
// 迁移models表数据到model表
System.out.println("\n===== 开始迁移models表数据 =====");
MigrationResult modelsResult = migrateModels(originalConn, newConn);
System.out.println("===== models表数据迁移完成 =====");
System.out.printf(" 成功: %d 条, 失败: %d 条, 总计: %d 条%n",
modelsResult.successCount,
modelsResult.failureCount,
modelsResult.totalCount);
if (modeTypesResult.failureCount > 0 || modelsResult.failureCount > 0) {
System.out.println("\n⚠ 注意:部分记录迁移失败、已跳过错误记录");
}
System.out.println("\n===== 数据库转换处理完成! =====");
} catch (SQLException e) {
System.err.println("\n❌ 数据库连接错误: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 迁移结果数据类
*/
private static class MigrationResult {
int successCount;
int failureCount;
int totalCount;
MigrationResult(int success, int failure, int total) {
this.successCount = success;
this.failureCount = failure;
this.totalCount = total;
}
}
/**
* 在新数据库中创建表结构
*/
private static void createNewTables(Connection conn) throws SQLException {
try (Statement stmt = conn.createStatement()) {
// 创建model_type表
System.out.println("正在创建model_type表...");
String createModelTypeTable = "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\")" +
");";
stmt.execute(createModelTypeTable);
System.out.println("✅ model_type表创建完成");
// 创建model表
System.out.println("正在创建model表...");
String createModelTable = "CREATE TABLE IF NOT EXISTS \"model\" (" +
"\"id\" TEXT," +
"\"model_type_id\" TEXT," +
"\"model_name\" TEXT," +
"\"model_type\" TEXT," +
"\"poster_type\" TEXT," +
"\"poster\" TEXT," +
"\"data\" TEXT," +
"\"data_bytes\" BLOB," +
"\"view\" TEXT," +
"\"created_at\" TEXT," +
"\"updated_at\" TEXT," +
"PRIMARY KEY (\"id\")" +
");";
stmt.execute(createModelTable);
System.out.println("✅ model表创建完成");
}
}
/**
* 迁移mode_types表数据到model_type表
*/
private static MigrationResult migrateModeTypes(Connection originalConn, Connection newConn) throws SQLException {
// 先获取记录总数、用于显示进度
int totalCount;
try (PreparedStatement countStmt = originalConn.prepareStatement("SELECT COUNT(*) FROM mode_types");
ResultSet countRs = countStmt.executeQuery()) {
countRs.next();
totalCount = countRs.getInt(1);
System.out.println("发现 " + totalCount + " 条mode_types记录待迁移");
}
// 查询原始表数据
try (PreparedStatement selectStmt = originalConn.prepareStatement("SELECT * FROM mode_types");
ResultSet rs = selectStmt.executeQuery()) {
// 插入到新表、关闭自动提交以提高性能
newConn.setAutoCommit(false);
try (PreparedStatement insertStmt = newConn.prepareStatement(
"INSERT INTO model_type (" +
"id, name, parent_id, tree_index, created_at, updated_at" +
") VALUES (?, ?, ?, ?, ?, ?)")) {
int successCount = 0;
int failureCount = 0;
while (rs.next()) {
try {
// 生成新的UUID作为ID
String newId = UUID.randomUUID().toString().replaceAll("-", "");
String typeName = rs.getString("type_name");
// 映射字段
insertStmt.setString(1, newId);
insertStmt.setString(2, typeName);
insertStmt.setString(3, rs.getString("p_id"));
insertStmt.setInt(4, 0);
insertStmt.setString(5, rs.getString("created_at"));
insertStmt.setString(6, rs.getString("updated_at"));
insertStmt.executeUpdate();
successCount++;
// 每迁移10条记录或最后一批记录时打印进度
if (successCount % 10 == 0 || (successCount + failureCount) == totalCount) {
printProgress(successCount + failureCount, totalCount);
}
} catch (SQLException e) {
// 捕获单条记录的异常、跳过该记录
failureCount++;
System.err.printf("\n❌ 迁移mode_types记录失败 (序号: %d): %s%n",
successCount + failureCount, e.getMessage());
}
}
newConn.commit();
return new MigrationResult(successCount, failureCount, totalCount);
} catch (SQLException e) {
newConn.rollback();
throw e;
} finally {
newConn.setAutoCommit(true);
}
}
}
/**
* 迁移models表数据到model表
*/
private static MigrationResult migrateModels(Connection originalConn, Connection newConn) throws SQLException {
// 先获取记录总数、用于显示进度
int totalCount;
try (PreparedStatement countStmt = originalConn.prepareStatement("SELECT COUNT(*) FROM models");
ResultSet countRs = countStmt.executeQuery()) {
countRs.next();
totalCount = countRs.getInt(1);
System.out.println("发现 " + totalCount + " 条models记录待迁移");
}
// 查询原始表数据
try (PreparedStatement selectStmt = originalConn.prepareStatement("SELECT * FROM models");
ResultSet rs = selectStmt.executeQuery()) {
// 插入到新表、关闭自动提交以提高性能
newConn.setAutoCommit(false);
try (PreparedStatement insertStmt = newConn.prepareStatement(
"INSERT INTO model (" +
"id, model_type_id, model_name, model_type, poster_type, " +
"poster, data, data_bytes, view, created_at, updated_at" +
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")) {
int successCount = 0;
int failureCount = 0;
while (rs.next()) {
try {
// 生成新的UUID作为ID
String newId = UUID.randomUUID().toString().replaceAll("-", "");
String modelName = rs.getString("model_name");
// 映射字段
insertStmt.setString(1, newId);
insertStmt.setString(2, rs.getString("p_id"));
insertStmt.setString(3, modelName);
insertStmt.setString(4, rs.getString("model_type"));
insertStmt.setString(5, rs.getString("poster_type"));
// 处理BLOB字段
byte[] posterBlob = rs.getBytes("poster");
insertStmt.setString(6, posterBlob != null ? new String(posterBlob) : null);
insertStmt.setString(7, null);
byte[] dataBlob = rs.getBytes("data");
insertStmt.setBytes(8, dataBlob);
insertStmt.setString(9, null);
insertStmt.setString(10, rs.getString("created_at"));
insertStmt.setString(11, rs.getString("updated_at"));
insertStmt.executeUpdate();
successCount++;
// 每迁移10条记录或最后一批记录时打印进度
if (successCount % 10 == 0 || (successCount + failureCount) == totalCount) {
printProgress(successCount + failureCount, totalCount);
}
} catch (SQLException e) {
// 捕获单条记录的异常、跳过该记录
failureCount++;
System.err.printf("\n❌ 迁移models记录失败 (序号: %d, 名称: %s): %s%n",
successCount + failureCount,
rs.getString("model_name"),
e.getMessage());
}
}
newConn.commit();
return new MigrationResult(successCount, failureCount, totalCount);
} catch (SQLException e) {
newConn.rollback();
throw e;
} finally {
newConn.setAutoCommit(true);
}
}
}
/**
* 打印迁移进度
*/
private static void printProgress(int current, int total) {
double percentage = (current * 100.0) / total;
System.out.printf("迁移进度: %d/%d (%.1f%%)\r", current, total, percentage);
System.out.flush();
long endTime = System.currentTimeMillis();
double elapsedTime = (endTime - startTime) / 1000.0;
System.out.printf("转换完成、耗时: %.2f秒%n", elapsedTime);
}
}

View File

@ -1,16 +1,21 @@
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.Date;
import java.time.LocalDateTime; // 新增导入
import java.time.format.DateTimeFormatter; // 新增导入
import java.time.format.DateTimeParseException; // 新增导入
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 驱动
// 加载SQLite JDBC驱动(静态初始化)
static {
try {
Class.forName("org.sqlite.JDBC");
@ -19,140 +24,508 @@ public class SQLiteUtil {
}
}
// 统一日期格式(匹配数据库TEXT字段存储的格式yyyy-MM-dd'T'HH:mm:ss.SSS
// 统一日期格式(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 {
String url = "jdbc:sqlite:" + dbFilePath;
return DriverManager.getConnection(url);
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
*/
public static <T> T queryForObject(String dbFilePath, String sql, List<Object> params, Class<T> clazz) throws SQLException, IllegalAccessException, InstantiationException {
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) {
throw new RuntimeException("初始化SQLite数据源失败路径" + dbFilePath + "", e);
}
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, IllegalAccessException, InstantiationException {
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不能为空");
}
// 使用try-with-resources确保资源自动关闭
// 预加载字段映射(缓存生效,避免重复反射)
Map<String, Field> fieldMap = getFieldMap(clazz);
// try-with-resources自动关闭Connection、PreparedStatement、ResultSet
try (Connection conn = getConnection(dbFilePath);
PreparedStatement pstmt = createPreparedStatement(conn, sql, params)) {
PreparedStatement pstmt = createPreparedStatement(conn, sql, params);
ResultSet rs = pstmt.executeQuery()) {
// 执行查询
try (ResultSet rs = pstmt.executeQuery()) {
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
// 预构建「列名-字段」映射(一次构建,循环复用)
ColumnFieldMapping[] mappings = buildColumnFieldMappings(metaData, columnCount, fieldMap);
// 处理结果集
while (rs.next()) {
T obj = clazz.newInstance();
for (int i = 1; i <= columnCount; i++) {
String columnName = metaData.getColumnName(i);
Object value = rs.getObject(i);
// 设置对象属性(自动处理类型转换)
setFieldValue(obj, columnName, value);
// 处理结果集(反射赋值)
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语句
* 执行增删改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);
}
}
/**
* 创建并设置参数化的PreparedStatement关键处理LocalDateTime转String
* 执行计数查询(优化版:轻量结果处理
*
* @param dbFilePath 数据库路径
* @param sql 计数SQL如SELECT COUNT(*) ...
* @param params SQL参数
* @return 计数结果无结果返回0
*/
private static PreparedStatement createPreparedStatement(Connection conn, String sql, List<Object> params)
throws SQLException {
PreparedStatement pstmt = conn.prepareStatement(sql);
if (params != null && !params.isEmpty()) {
int index = 1;
for (Object value : params) {
// 新增LocalDateTime类型转为String、适配SQLite的TEXT字段
if (value instanceof LocalDateTime) {
String dateStr = ((LocalDateTime) value).format(LOCAL_DATE_TIME_FORMATTER);
pstmt.setObject(index++, dateStr);
}
// 新增byte[]类型显式指定为BLOB避免SQLite自动转换异常
else if (value instanceof byte[]) {
pstmt.setBytes(index++, (byte[]) value);
} else {
pstmt.setObject(index++, value);
}
}
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 setFieldValue(Object obj, String columnName, Object 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 {
Field field = findField(obj.getClass(), columnName);
if (field != null) {
field.setAccessible(true);
Object convertedValue = convertValue(field.getType(), value);
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("警告: 无法设置字段 " + columnName + " 的值 - " + e.getMessage());
System.err.println("警告:字段赋值失败(字段" + field.getName() + ",值类型:" + value.getClass().getName() + "" + e.getMessage());
}
}
/**
* 查找字段、支持下划线命名转驼峰命名如created_at → createdAt
* 优化的类型转换支持常见SQLite类型→Java类型
*
* @param targetType 目标类型(字段类型)
* @param value 原始值ResultSet获取的值
* @return 转换后的值无法转换返回null
*/
private static Field findField(Class<?> clazz, String columnName) {
// 1. 直接匹配字段名(如数据库列名与字段名一致)
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 clazz.getDeclaredField(columnName);
} catch (NoSuchFieldException e) {
// 2. 下划线转驼峰后匹配如created_at → createdAt
String camelCaseName = underscoreToCamelCase(columnName);
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 clazz.getDeclaredField(camelCaseName);
} catch (NoSuchFieldException e1) {
// 3. 递归查找父类(支持继承场景)
if (clazz.getSuperclass() != null) {
return findField(clazz.getSuperclass(), columnName);
}
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;
}
if (str == null || str.isEmpty()) return str;
StringBuilder sb = new StringBuilder();
boolean nextUpperCase = false;
for (char c : str.toCharArray()) {
@ -163,7 +536,6 @@ public class SQLiteUtil {
sb.append(Character.toUpperCase(c));
nextUpperCase = false;
} else {
// 修正首字母小写如CREATED_AT → createdAt、而非CreatedAt
sb.append(Character.toLowerCase(c));
}
}
@ -171,136 +543,26 @@ public class SQLiteUtil {
return sb.toString();
}
// ========================== 内部辅助类 ==========================
/**
* 核心类型转换新增LocalDateTime解析、优化BLOB适配
* 列-字段映射模型(一次性构建,减少循环内计算
*/
private static Object convertValue(Class<?> targetType, Object value) {
if (value == null) {
return null;
}
// 类型已匹配、直接返回
if (targetType.isInstance(value)) {
return value;
}
// 1. 数字类型转换int/long/double等
if (targetType == Integer.class || targetType == int.class) {
return ((Number) value).intValue();
} else if (targetType == Long.class || targetType == long.class) {
return ((Number) value).longValue();
} else if (targetType == Double.class || targetType == double.class) {
return ((Number) value).doubleValue();
} else if (targetType == Float.class || targetType == float.class) {
return ((Number) value).floatValue();
}
// 2. 布尔类型转换(支持数字/字符串转布尔)
else if (targetType == Boolean.class || targetType == boolean.class) {
if (value instanceof Number) {
return ((Number) value).intValue() != 0;
} else if (value instanceof String) {
return "true".equalsIgnoreCase((String) value);
}
}
// 3. 字符串类型转换所有类型转String
else if (targetType == String.class) {
return value.toString();
}
// 4. 日期类型转换java.util.Date
else if (targetType == Date.class) {
if (value instanceof Timestamp) {
return new Date(((Timestamp) value).getTime());
} else if (value instanceof LocalDateTime) {
// 支持LocalDateTime转Date如需
return Date.from(((LocalDateTime) value).atZone(java.time.ZoneId.systemDefault()).toInstant());
}
}
// 5. 新增LocalDateTime类型转换SQLite TEXT → Java LocalDateTime
else if (targetType == LocalDateTime.class) {
if (value instanceof String) {
try {
// 解析数据库存储的ISO格式字符串如"2025-09-18T17:30:27.143"
return LocalDateTime.parse((String) value, LOCAL_DATE_TIME_FORMATTER);
} catch (DateTimeParseException e) {
System.err.println("警告: 日期解析失败、字符串=" + value + "、格式应为yyyy-MM-dd'T'HH:mm:ss.SSS - " + e.getMessage());
return null;
}
} else if (value instanceof Timestamp) {
// 兼容Timestamp类型如其他数据库迁移场景
return ((Timestamp) value).toLocalDateTime();
}
}
// 6. 新增byte[]类型转换SQLite BLOB → Java byte[]
else if (targetType == byte[].class && 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;
}
}
// 无法转换时返回原始值(避免崩溃、打印警告)
System.err.println("警告: 不支持的类型转换、目标类型=" + targetType.getName() + "、原始值类型=" + value.getClass().getName());
return value;
private static class ColumnFieldMapping {
String columnName; // 数据库列名
Field field; // 对应的Java字段
}
/**
* 执行DDL语句CREATE, ALTER, DROP等
*/
private static void executeDDL(String dbFilePath, String sql, List<Object> params) throws SQLException {
try (Connection conn = getConnection(dbFilePath);
PreparedStatement pstmt = createPreparedStatement(conn, sql, params)) {
pstmt.execute();
}
}
// ========================== 初始化表结构方法(保留原逻辑) ==========================
/**
* 执行无参数的DDL语句
* 初始化模型相关表model_type + model
*/
public static void executeDDL(String dbFilePath, String sql) {
try {
executeDDL(dbFilePath, sql, null);
} catch (SQLException e) {
throw new RuntimeException("执行DDL语句失败、SQL=" + sql, e);
}
}
/**
* 执行查询并返回count结果
*/
public static int queryForCount(String dbFilePath, String sql, List<Object> params) throws SQLException {
try (Connection conn = getConnection(dbFilePath);
PreparedStatement pstmt = createPreparedStatement(conn, sql, params);
ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
return rs.getInt(1);
}
return 0;
}
}
/**
* 执行无参数的查询并返回count结果
*/
public static int queryForCount(String dbFilePath, String sql) throws SQLException {
return queryForCount(dbFilePath, sql, null);
}
/**
* 初始化数据库表
*/
public static void initializationModel(String modelPath) {
// 创建模型类型表
public static void initializationModel(String modelPath) throws SQLException {
String sql = """
CREATE TABLE "model_type" (
CREATE TABLE IF NOT EXISTS "model_type" (
"id" TEXT,
"name" TEXT,
"parent_id" TEXT,
@ -312,9 +574,8 @@ public class SQLiteUtil {
""";
executeDDL(modelPath, sql);
// 创建模型表
sql = """
CREATE TABLE "model" (
CREATE TABLE IF NOT EXISTS "model" (
"id" TEXT,
"model_type_id" TEXT,
"model_name" TEXT,
@ -331,12 +592,11 @@ public class SQLiteUtil {
}
/**
* 初始化数据库表
* 初始化军标相关表military_type + military
*/
public static void initializationMilitary(String modelPath) {
// 创建军标类型表
public static void initializationMilitary(String militaryPath) throws SQLException {
String sql = """
CREATE TABLE "military_type" (
CREATE TABLE IF NOT EXISTS "military_type" (
"id" TEXT,
"name" TEXT,
"parent_id" TEXT,
@ -346,29 +606,29 @@ public class SQLiteUtil {
PRIMARY KEY ("id")
);
""";
executeDDL(modelPath, sql);
executeDDL(militaryPath, sql);
// 创建军标表
sql = """
CREATE TABLE "military" (
CREATE TABLE IF NOT EXISTS "military" (
"id" TEXT,
"military_type_id" TEXT,
"military_name" TEXT,
"military_type" TEXT,
"data" TEXT,
"view" TEXT,
"military_data" BLOB,
"created_at" TEXT,
"updated_at" TEXT,
PRIMARY KEY ("id")
);
""";
executeDDL(modelPath, sql);
executeDDL(militaryPath, sql);
}
public static void initializationIcon(String iconPath) {
// 创建图标类型表
/**
* 初始化图标相关表icon_type + icon
*/
public static void initializationIcon(String iconPath) throws SQLException {
String sql = """
CREATE TABLE "icon_type" (
CREATE TABLE IF NOT EXISTS "icon_type" (
"id" TEXT,
"name" TEXT,
"parent_id" TEXT,
@ -380,9 +640,8 @@ public class SQLiteUtil {
""";
executeDDL(iconPath, sql);
// 创建图标表
sql = """
CREATE TABLE "icon" (
CREATE TABLE IF NOT EXISTS "icon" (
"id" TEXT,
"icon_type_id" TEXT,
"icon_name" TEXT,