335 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
		
		
			
		
	
	
			335 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
|   | package com.yj.earth.common.util;
 | |||
|  | 
 | |||
|  | 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; // 新增导入
 | |||
|  | 
 | |||
|  | public class SQLiteUtil {
 | |||
|  | 
 | |||
|  |     // 加载 SQLite JDBC 驱动
 | |||
|  |     static {
 | |||
|  |         try {
 | |||
|  |             Class.forName("org.sqlite.JDBC");
 | |||
|  |         } catch (ClassNotFoundException e) {
 | |||
|  |             throw new RuntimeException("无法加载SQLite JDBC驱动", e);
 | |||
|  |         }
 | |||
|  |     }
 | |||
|  | 
 | |||
|  |     // 统一日期格式(匹配数据库TEXT字段存储的格式:yyyy-MM-dd'T'HH:mm:ss.SSS)
 | |||
|  |     private static final DateTimeFormatter LOCAL_DATE_TIME_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
 | |||
|  | 
 | |||
|  |     /**
 | |||
|  |      * 根据数据库文件绝对路径获取连接
 | |||
|  |      */
 | |||
|  |     public static Connection getConnection(String dbFilePath) throws SQLException {
 | |||
|  |         String url = "jdbc:sqlite:" + dbFilePath;
 | |||
|  |         return DriverManager.getConnection(url);
 | |||
|  |     }
 | |||
|  | 
 | |||
|  |     /**
 | |||
|  |      * 执行查询并返回单个对象
 | |||
|  |      */
 | |||
|  |     public static <T> T queryForObject(String dbFilePath, String sql, List<Object> params, Class<T> clazz) throws SQLException, IllegalAccessException, InstantiationException {
 | |||
|  |         List<T> results = queryForList(dbFilePath, sql, params, clazz);
 | |||
|  |         return results.isEmpty() ? null : results.get(0);
 | |||
|  |     }
 | |||
|  | 
 | |||
|  |     /**
 | |||
|  |      * 执行查询并返回对象列表
 | |||
|  |      */
 | |||
|  |     public static <T> List<T> queryForList(String dbFilePath, String sql, List<Object> params, Class<T> clazz)
 | |||
|  |             throws SQLException, IllegalAccessException, InstantiationException {
 | |||
|  |         List<T> resultList = new ArrayList<>();
 | |||
|  | 
 | |||
|  |         // 使用try-with-resources确保资源自动关闭
 | |||
|  |         try (Connection conn = getConnection(dbFilePath);
 | |||
|  |              PreparedStatement pstmt = createPreparedStatement(conn, sql, params)) {
 | |||
|  | 
 | |||
|  |             // 执行查询
 | |||
|  |             try (ResultSet rs = pstmt.executeQuery()) {
 | |||
|  |                 ResultSetMetaData metaData = rs.getMetaData();
 | |||
|  |                 int columnCount = metaData.getColumnCount();
 | |||
|  | 
 | |||
|  |                 // 处理结果集
 | |||
|  |                 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);
 | |||
|  |                     }
 | |||
|  |                     resultList.add(obj);
 | |||
|  |                 }
 | |||
|  |             }
 | |||
|  |         }
 | |||
|  | 
 | |||
|  |         return resultList;
 | |||
|  |     }
 | |||
|  | 
 | |||
|  |     /**
 | |||
|  |      * 执行增删改SQL语句
 | |||
|  |      */
 | |||
|  |     public static int executeUpdate(String dbFilePath, String sql, List<Object> params) throws SQLException {
 | |||
|  |         try (Connection conn = getConnection(dbFilePath);
 | |||
|  |              PreparedStatement pstmt = createPreparedStatement(conn, sql, params)) {
 | |||
|  |             return pstmt.executeUpdate();
 | |||
|  |         }
 | |||
|  |     }
 | |||
|  | 
 | |||
|  |     /**
 | |||
|  |      * 创建并设置参数化的PreparedStatement(关键:处理LocalDateTime转String)
 | |||
|  |      */
 | |||
|  |     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);
 | |||
|  |                 }
 | |||
|  |             }
 | |||
|  |         }
 | |||
|  | 
 | |||
|  |         return pstmt;
 | |||
|  |     }
 | |||
|  | 
 | |||
|  |     /**
 | |||
|  |      * 通过反射设置对象的字段值
 | |||
|  |      */
 | |||
|  |     private static void setFieldValue(Object obj, String columnName, Object value) {
 | |||
|  |         try {
 | |||
|  |             Field field = findField(obj.getClass(), columnName);
 | |||
|  |             if (field != null) {
 | |||
|  |                 field.setAccessible(true);
 | |||
|  |                 Object convertedValue = convertValue(field.getType(), value);
 | |||
|  |                 field.set(obj, convertedValue);
 | |||
|  |             }
 | |||
|  |         } catch (IllegalAccessException e) {
 | |||
|  |             System.err.println("警告: 无法设置字段 " + columnName + " 的值 - " + e.getMessage());
 | |||
|  |         }
 | |||
|  |     }
 | |||
|  | 
 | |||
|  |     /**
 | |||
|  |      * 查找字段、支持下划线命名转驼峰命名(如created_at → createdAt)
 | |||
|  |      */
 | |||
|  |     private static Field findField(Class<?> clazz, String columnName) {
 | |||
|  |         // 1. 直接匹配字段名(如数据库列名与字段名一致)
 | |||
|  |         try {
 | |||
|  |             return clazz.getDeclaredField(columnName);
 | |||
|  |         } catch (NoSuchFieldException e) {
 | |||
|  |             // 2. 下划线转驼峰后匹配(如created_at → createdAt)
 | |||
|  |             String camelCaseName = underscoreToCamelCase(columnName);
 | |||
|  |             try {
 | |||
|  |                 return clazz.getDeclaredField(camelCaseName);
 | |||
|  |             } catch (NoSuchFieldException e1) {
 | |||
|  |                 // 3. 递归查找父类(支持继承场景)
 | |||
|  |                 if (clazz.getSuperclass() != null) {
 | |||
|  |                     return findField(clazz.getSuperclass(), columnName);
 | |||
|  |                 }
 | |||
|  |                 return null;
 | |||
|  |             }
 | |||
|  |         }
 | |||
|  |     }
 | |||
|  | 
 | |||
|  |     /**
 | |||
|  |      * 下划线命名转驼峰命名(工具方法)
 | |||
|  |      */
 | |||
|  |     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 {
 | |||
|  |                     // 修正:首字母小写(如CREATED_AT → createdAt、而非CreatedAt)
 | |||
|  |                     sb.append(Character.toLowerCase(c));
 | |||
|  |                 }
 | |||
|  |             }
 | |||
|  |         }
 | |||
|  |         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;
 | |||
|  |     }
 | |||
|  | 
 | |||
|  |     /**
 | |||
|  |      * 执行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语句
 | |||
|  |      */
 | |||
|  |     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);
 | |||
|  |     }
 | |||
|  | 
 | |||
|  |     /**
 | |||
|  |      * 初始化数据库表(model_type + model)
 | |||
|  |      */
 | |||
|  |     public static void initialization(String modelPath) {
 | |||
|  |         // 创建模型类型表
 | |||
|  |         String sql = """
 | |||
|  |                     CREATE TABLE "model_type" (
 | |||
|  |                       "id" TEXT,
 | |||
|  |                       "name" TEXT,
 | |||
|  |                       "parent_id" TEXT,
 | |||
|  |                       "created_at" TEXT,
 | |||
|  |                       "updated_at" TEXT,
 | |||
|  |                       PRIMARY KEY ("id")
 | |||
|  |                     );
 | |||
|  |                 """;
 | |||
|  |         executeDDL(modelPath, sql);
 | |||
|  | 
 | |||
|  |         // 创建模型表
 | |||
|  |         sql = """
 | |||
|  |                   CREATE TABLE "model" (
 | |||
|  |                     "id" TEXT,
 | |||
|  |                     "model_type_id" TEXT,
 | |||
|  |                     "model_name" TEXT,
 | |||
|  |                     "model_type" TEXT,
 | |||
|  |                     "poster_type" TEXT,
 | |||
|  |                     "poster" TEXT,
 | |||
|  |                     "data" TEXT,
 | |||
|  |                     "view" TEXT,
 | |||
|  |                     "created_at" TEXT,
 | |||
|  |                     "updated_at" TEXT,
 | |||
|  |                     PRIMARY KEY ("id")
 | |||
|  |                   );
 | |||
|  |                 """;
 | |||
|  |         executeDDL(modelPath, sql);
 | |||
|  |     }
 | |||
|  | 
 | |||
|  | }
 |