GDAL优化
This commit is contained in:
@ -29,7 +29,7 @@ public class GdalController {
|
|||||||
public void importDataStreamGzip(@Parameter(description = "矢量文件路径", required = true) @RequestParam("path") String path, HttpServletResponse response) throws IOException {
|
public void importDataStreamGzip(@Parameter(description = "矢量文件路径", required = true) @RequestParam("path") String path, HttpServletResponse response) throws IOException {
|
||||||
|
|
||||||
// 解析矢量文件、得到JSON数据
|
// 解析矢量文件、得到JSON数据
|
||||||
String jsonData = GdalUtil.readVectorToGeoJson(path);
|
String jsonData = GdalUtil.readVectorToSpecifiedGeoJson(path);
|
||||||
|
|
||||||
// 设置响应头
|
// 设置响应头
|
||||||
String filename = URLEncoder.encode("data.gz", StandardCharsets.UTF_8);
|
String filename = URLEncoder.encode("data.gz", StandardCharsets.UTF_8);
|
||||||
|
|||||||
@ -77,7 +77,7 @@ public class IconLibraryController {
|
|||||||
// 校验图标库文件是否已存在
|
// 校验图标库文件是否已存在
|
||||||
if (iconFile.exists()) {
|
if (iconFile.exists()) {
|
||||||
if (iconFile.isDirectory()) {
|
if (iconFile.isDirectory()) {
|
||||||
return ApiResponse.failure("同名目录已存在,无法创建文件:" + iconPath);
|
return ApiResponse.failure("同名目录已存在、无法创建文件:" + iconPath);
|
||||||
}
|
}
|
||||||
return ApiResponse.failure("图标库文件已存在:" + iconPath);
|
return ApiResponse.failure("图标库文件已存在:" + iconPath);
|
||||||
}
|
}
|
||||||
@ -391,7 +391,7 @@ public class IconLibraryController {
|
|||||||
|
|
||||||
private String getIconLibrary() {
|
private String getIconLibrary() {
|
||||||
LambdaQueryWrapper<IconLibrary> queryWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<IconLibrary> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
queryWrapper.eq(IconLibrary::getIsEnable, 1); // 1=启用,0=未启用
|
queryWrapper.eq(IconLibrary::getIsEnable, 1); // 1=启用、0=未启用
|
||||||
IconLibrary library = iconLibraryService.getOne(queryWrapper);
|
IconLibrary library = iconLibraryService.getOne(queryWrapper);
|
||||||
return library == null ? null : library.getPath();
|
return library == null ? null : library.getPath();
|
||||||
}
|
}
|
||||||
@ -404,7 +404,7 @@ public class IconLibraryController {
|
|||||||
iconLibraryService.updateById(library);
|
iconLibraryService.updateById(library);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查路径是否已存在(存在则启用,不存在则新增)
|
// 检查路径是否已存在(存在则启用、不存在则新增)
|
||||||
LambdaQueryWrapper<IconLibrary> pathWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<IconLibrary> pathWrapper = new LambdaQueryWrapper<>();
|
||||||
pathWrapper.eq(IconLibrary::getPath, iconPath);
|
pathWrapper.eq(IconLibrary::getPath, iconPath);
|
||||||
IconLibrary existLibrary = iconLibraryService.getOne(pathWrapper);
|
IconLibrary existLibrary = iconLibraryService.getOne(pathWrapper);
|
||||||
|
|||||||
@ -77,7 +77,7 @@ public class MilitaryLibraryController {
|
|||||||
// 校验军标库文件是否已存在
|
// 校验军标库文件是否已存在
|
||||||
if (militaryFile.exists()) {
|
if (militaryFile.exists()) {
|
||||||
if (militaryFile.isDirectory()) {
|
if (militaryFile.isDirectory()) {
|
||||||
return ApiResponse.failure("同名目录已存在,无法创建文件:" + militaryPath);
|
return ApiResponse.failure("同名目录已存在、无法创建文件:" + militaryPath);
|
||||||
}
|
}
|
||||||
return ApiResponse.failure("军标库文件已存在:" + militaryPath);
|
return ApiResponse.failure("军标库文件已存在:" + militaryPath);
|
||||||
}
|
}
|
||||||
@ -390,7 +390,7 @@ public class MilitaryLibraryController {
|
|||||||
|
|
||||||
private String getMilitaryLibrary() {
|
private String getMilitaryLibrary() {
|
||||||
LambdaQueryWrapper<MilitaryLibrary> queryWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<MilitaryLibrary> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
queryWrapper.eq(MilitaryLibrary::getIsEnable, 1); // 1=启用,0=未启用
|
queryWrapper.eq(MilitaryLibrary::getIsEnable, 1); // 1=启用、0=未启用
|
||||||
MilitaryLibrary library = militaryLibraryService.getOne(queryWrapper);
|
MilitaryLibrary library = militaryLibraryService.getOne(queryWrapper);
|
||||||
return library == null ? null : library.getPath();
|
return library == null ? null : library.getPath();
|
||||||
}
|
}
|
||||||
@ -403,7 +403,7 @@ public class MilitaryLibraryController {
|
|||||||
militaryLibraryService.updateById(library);
|
militaryLibraryService.updateById(library);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查路径是否已存在(存在则启用,不存在则新增)
|
// 检查路径是否已存在(存在则启用、不存在则新增)
|
||||||
LambdaQueryWrapper<MilitaryLibrary> pathWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<MilitaryLibrary> pathWrapper = new LambdaQueryWrapper<>();
|
||||||
pathWrapper.eq(MilitaryLibrary::getPath, militaryPath);
|
pathWrapper.eq(MilitaryLibrary::getPath, militaryPath);
|
||||||
MilitaryLibrary existLibrary = militaryLibraryService.getOne(pathWrapper);
|
MilitaryLibrary existLibrary = militaryLibraryService.getOne(pathWrapper);
|
||||||
|
|||||||
@ -2,7 +2,8 @@ package com.yj.earth.common.util;
|
|||||||
|
|
||||||
import org.gdal.gdal.gdal;
|
import org.gdal.gdal.gdal;
|
||||||
import org.gdal.ogr.*;
|
import org.gdal.ogr.*;
|
||||||
import org.gdal.osr.SpatialReference;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -11,88 +12,236 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class GdalUtil {
|
public class GdalUtil {
|
||||||
|
|
||||||
// 静态初始化
|
// 静态初始化ObjectMapper,避免重复创建
|
||||||
|
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||||
|
|
||||||
|
// 静态初始化GDAL
|
||||||
static {
|
static {
|
||||||
|
// 可以考虑设置缓存目录来提升性能
|
||||||
|
gdal.SetConfigOption("GDAL_CACHEMAX", "1024"); // 设置1GB缓存
|
||||||
gdal.SetConfigOption("PROJ_LIB", "./lib");
|
gdal.SetConfigOption("PROJ_LIB", "./lib");
|
||||||
gdal.AllRegister();
|
gdal.AllRegister();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 读取矢量文件并转换为标准GeoJSON格式
|
* 读取矢量文件、将每个图层转换为符合指定格式的 GeoJSON
|
||||||
*/
|
*/
|
||||||
public static String readVectorToGeoJson(String filePath) {
|
public static String readVectorToSpecifiedGeoJson(String filePath) {
|
||||||
// 打开矢量文件
|
DataSource dataSource = null;
|
||||||
DataSource dataSource = ogr.Open(filePath);
|
try {
|
||||||
if (dataSource == null) {
|
// 打开矢量文件,使用只读模式
|
||||||
throw new RuntimeException("无法打开矢量文件: " + filePath);
|
dataSource = ogr.Open(filePath, 0);
|
||||||
}
|
if (dataSource == null) {
|
||||||
|
throw new RuntimeException("无法打开矢量文件: " + filePath + ", 错误信息: " + gdal.GetLastErrorMsg());
|
||||||
// 创建GeoJSON根对象 - FeatureCollection
|
|
||||||
Map<String, Object> geoJson = new HashMap<>();
|
|
||||||
geoJson.put("type", "FeatureCollection");
|
|
||||||
|
|
||||||
// 存储所有要素
|
|
||||||
List<Map<String, Object>> features = new ArrayList<>();
|
|
||||||
geoJson.put("features", features);
|
|
||||||
|
|
||||||
// 获取所有图层
|
|
||||||
int layerCount = dataSource.GetLayerCount();
|
|
||||||
for (int i = 0; i < layerCount; i++) {
|
|
||||||
Layer layer = dataSource.GetLayer(i);
|
|
||||||
if (layer == null) continue;
|
|
||||||
|
|
||||||
// 获取空间参考信息
|
|
||||||
SpatialReference srs = layer.GetSpatialRef();
|
|
||||||
if (srs != null && features.isEmpty()) { // 只添加一次CRS信息
|
|
||||||
Map<String, Object> crs = new HashMap<>();
|
|
||||||
Map<String, Object> crsProps = new HashMap<>();
|
|
||||||
crsProps.put("name", srs.GetAttrValue("AUTHORITY", 0) + ":" + srs.GetAttrValue("AUTHORITY", 1));
|
|
||||||
crs.put("type", "name");
|
|
||||||
crs.put("properties", crsProps);
|
|
||||||
geoJson.put("crs", crs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 创建结果结构,预先估计容量
|
||||||
|
Map<String, Object> result = new HashMap<>(2);
|
||||||
|
int layerCount = dataSource.GetLayerCount();
|
||||||
|
List<Map<String, Object>> layerList = new ArrayList<>(layerCount);
|
||||||
|
|
||||||
|
// 处理每个图层
|
||||||
|
for (int i = 0; i < layerCount; i++) {
|
||||||
|
Layer layer = dataSource.GetLayer(i);
|
||||||
|
if (layer == null) continue;
|
||||||
|
|
||||||
|
// 为当前图层创建符合格式的 GeoJSON
|
||||||
|
Map<String, Object> layerGeoJson = convertLayerToSpecifiedFormat(layer);
|
||||||
|
if (layerGeoJson != null) {
|
||||||
|
layerList.add(layerGeoJson);
|
||||||
|
}
|
||||||
|
// 提前释放图层资源
|
||||||
|
layer.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
result.put("list", layerList);
|
||||||
|
|
||||||
|
// 使用单例ObjectMapper转换为JSON字符串
|
||||||
|
return OBJECT_MAPPER.writeValueAsString(result);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("处理矢量文件时发生错误: " + e.getMessage() + ", 详细错误: " + gdal.GetLastErrorMsg(), e);
|
||||||
|
} finally {
|
||||||
|
// 确保数据源被正确释放
|
||||||
|
if (dataSource != null) {
|
||||||
|
dataSource.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将单个图层转换为符合指定格式的 GeoJSON
|
||||||
|
*/
|
||||||
|
private static Map<String, Object> convertLayerToSpecifiedFormat(Layer layer) {
|
||||||
|
try {
|
||||||
|
// 获取要素数量,预分配集合大小
|
||||||
|
long featureCount = layer.GetFeatureCount();
|
||||||
|
if (featureCount <= 0) {
|
||||||
|
return null; // 跳过空图层
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建符合格式的 GeoJSON FeatureCollection
|
||||||
|
Map<String, Object> geoJson = new HashMap<>(3);
|
||||||
|
geoJson.put("type", "FeatureCollection");
|
||||||
|
geoJson.put("name", layer.GetName());
|
||||||
|
|
||||||
|
// 预分配要素列表容量
|
||||||
|
List<Map<String, Object>> features = new ArrayList<>((int) Math.min(featureCount, Integer.MAX_VALUE));
|
||||||
|
|
||||||
// 获取字段定义
|
// 获取字段定义
|
||||||
FeatureDefn featureDefn = layer.GetLayerDefn();
|
FeatureDefn featureDefn = layer.GetLayerDefn();
|
||||||
int fieldCount = featureDefn.GetFieldCount();
|
int fieldCount = featureDefn.GetFieldCount();
|
||||||
|
|
||||||
// 读取所有要素
|
// 缓存字段信息,避免重复获取
|
||||||
|
String[] fieldNames = new String[fieldCount];
|
||||||
|
int[] fieldTypes = new int[fieldCount];
|
||||||
|
for (int j = 0; j < fieldCount; j++) {
|
||||||
|
FieldDefn fieldDef = featureDefn.GetFieldDefn(j);
|
||||||
|
fieldNames[j] = fieldDef.GetName();
|
||||||
|
fieldTypes[j] = fieldDef.GetFieldType();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取图层中的所有要素
|
||||||
layer.ResetReading();
|
layer.ResetReading();
|
||||||
Feature feature;
|
Feature feature;
|
||||||
|
|
||||||
while ((feature = layer.GetNextFeature()) != null) {
|
while ((feature = layer.GetNextFeature()) != null) {
|
||||||
// 创建Feature对象
|
Map<String, Object> featureMap = new HashMap<>(3);
|
||||||
Map<String, Object> featureObj = new HashMap<>();
|
featureMap.put("type", "Feature");
|
||||||
featureObj.put("type", "Feature");
|
|
||||||
|
|
||||||
// 存储属性信息
|
|
||||||
Map<String, Object> properties = new HashMap<>();
|
|
||||||
// 可以添加图层名称作为属性
|
|
||||||
properties.put("layerName", layer.GetName());
|
|
||||||
|
|
||||||
|
// 处理属性
|
||||||
|
Map<String, Object> properties = new HashMap<>(fieldCount);
|
||||||
for (int j = 0; j < fieldCount; j++) {
|
for (int j = 0; j < fieldCount; j++) {
|
||||||
String fieldName = featureDefn.GetFieldDefn(j).GetName();
|
properties.put(fieldNames[j], getFieldValue(feature, j, fieldTypes[j]));
|
||||||
properties.put(fieldName, feature.GetFieldAsString(j));
|
|
||||||
}
|
}
|
||||||
featureObj.put("properties", properties);
|
featureMap.put("properties", properties);
|
||||||
|
|
||||||
// 存储几何信息,转换为GeoJSON格式
|
// 处理几何信息 - 直接构建Map而不是先转JSON再解析
|
||||||
Geometry geometry = feature.GetGeometryRef();
|
Geometry geometry = feature.GetGeometryRef();
|
||||||
if (geometry != null) {
|
featureMap.put("geometry", convertGeometryToMap(geometry));
|
||||||
|
|
||||||
String geometryJson = geometry.ExportToJson();
|
features.add(featureMap);
|
||||||
Map<String, Object> geometryObj = JsonUtil.jsonToMap(geometryJson);
|
feature.delete(); // 及时释放要素资源
|
||||||
featureObj.put("geometry", geometryObj);
|
|
||||||
}
|
|
||||||
|
|
||||||
features.add(featureObj);
|
|
||||||
feature.delete();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
geoJson.put("features", features);
|
||||||
|
return geoJson;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("转换图层 " + layer.GetName() + " 时发生错误: " + e.getMessage() + ", 详细错误: " + gdal.GetLastErrorMsg());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将Geometry直接转换为Map,避免JSON序列化/反序列化开销
|
||||||
|
*/
|
||||||
|
private static Map<String, Object> convertGeometryToMap(Geometry geometry) {
|
||||||
|
if (geometry == null) {
|
||||||
|
return createEmptyMultiPolygon();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭数据源
|
try {
|
||||||
dataSource.delete();
|
String geometryType = geometry.GetGeometryName();
|
||||||
|
Map<String, Object> geometryMap = new HashMap<>(2);
|
||||||
|
|
||||||
// 转换为JSON并返回
|
// 如果不是MultiPolygon,尝试转换
|
||||||
return JsonUtil.toJson(geoJson);
|
if (!"MULTIPOLYGON".equals(geometryType)) {
|
||||||
|
// 转换为MultiPolygon
|
||||||
|
Geometry multiPolygon = geometry.MakeValid();
|
||||||
|
if (!"MULTIPOLYGON".equals(multiPolygon.GetGeometryName())) {
|
||||||
|
multiPolygon = geometry.Buffer(0); // 修复几何问题
|
||||||
|
}
|
||||||
|
if (!"MULTIPOLYGON".equals(multiPolygon.GetGeometryName())) {
|
||||||
|
// 如果仍然不是MultiPolygon,创建空的
|
||||||
|
return createEmptyMultiPolygon();
|
||||||
|
}
|
||||||
|
geometry = multiPolygon;
|
||||||
|
}
|
||||||
|
|
||||||
|
geometryMap.put("type", "MultiPolygon");
|
||||||
|
geometryMap.put("coordinates", getCoordinates(geometry));
|
||||||
|
|
||||||
|
return geometryMap;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return createEmptyMultiPolygon();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 直接获取几何坐标,避免JSON转换
|
||||||
|
*/
|
||||||
|
private static List<?> getCoordinates(Geometry geometry) {
|
||||||
|
List<Object> coordinates = new ArrayList<>();
|
||||||
|
|
||||||
|
if (geometry == null) {
|
||||||
|
return coordinates;
|
||||||
|
}
|
||||||
|
|
||||||
|
int geometryCount = geometry.GetGeometryCount();
|
||||||
|
for (int i = 0; i < geometryCount; i++) {
|
||||||
|
Geometry polygon = geometry.GetGeometryRef(i);
|
||||||
|
if (polygon == null) continue;
|
||||||
|
|
||||||
|
List<Object> polygonCoords = new ArrayList<>();
|
||||||
|
int ringCount = polygon.GetGeometryCount();
|
||||||
|
for (int j = 0; j < ringCount; j++) {
|
||||||
|
Geometry ring = polygon.GetGeometryRef(j);
|
||||||
|
if (ring == null) continue;
|
||||||
|
|
||||||
|
List<List<Double>> ringCoords = new ArrayList<>();
|
||||||
|
int pointCount = ring.GetPointCount();
|
||||||
|
for (int k = 0; k < pointCount; k++) {
|
||||||
|
double[] point = ring.GetPoint(k);
|
||||||
|
List<Double> pointCoords = new ArrayList<>(2);
|
||||||
|
pointCoords.add(point[0]);
|
||||||
|
pointCoords.add(point[1]);
|
||||||
|
ringCoords.add(pointCoords);
|
||||||
|
}
|
||||||
|
polygonCoords.add(ringCoords);
|
||||||
|
}
|
||||||
|
coordinates.add(polygonCoords);
|
||||||
|
}
|
||||||
|
|
||||||
|
return coordinates;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字段值、根据字段类型处理
|
||||||
|
*/
|
||||||
|
private static Object getFieldValue(Feature feature, int fieldIndex, int fieldType) {
|
||||||
|
try {
|
||||||
|
switch (fieldType) {
|
||||||
|
case ogr.OFTInteger:
|
||||||
|
return feature.GetFieldAsInteger(fieldIndex);
|
||||||
|
case ogr.OFTInteger64:
|
||||||
|
return feature.GetFieldAsInteger64(fieldIndex);
|
||||||
|
case ogr.OFTReal:
|
||||||
|
return feature.GetFieldAsDouble(fieldIndex);
|
||||||
|
case ogr.OFTString:
|
||||||
|
case ogr.OFTStringList:
|
||||||
|
case ogr.OFTWideString:
|
||||||
|
return feature.GetFieldAsString(fieldIndex);
|
||||||
|
case ogr.OFTDate:
|
||||||
|
case ogr.OFTTime:
|
||||||
|
case ogr.OFTDateTime:
|
||||||
|
return feature.GetFieldAsString(fieldIndex);
|
||||||
|
default:
|
||||||
|
// 对于其他类型,统一返回字符串表示
|
||||||
|
return feature.GetFieldAsString(fieldIndex);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
return feature.GetFieldAsString(fieldIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建空的 MultiPolygon 几何对象
|
||||||
|
*/
|
||||||
|
private static Map<String, Object> createEmptyMultiPolygon() {
|
||||||
|
Map<String, Object> geometry = new HashMap<>(2);
|
||||||
|
geometry.put("type", "MultiPolygon");
|
||||||
|
geometry.put("coordinates", new ArrayList<>());
|
||||||
|
return geometry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,7 +27,7 @@ public class SQLiteUtil {
|
|||||||
// 统一日期格式(LocalDateTime)
|
// 统一日期格式(LocalDateTime)
|
||||||
private static final DateTimeFormatter LOCAL_DATE_TIME_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
|
private static final DateTimeFormatter LOCAL_DATE_TIME_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
|
||||||
|
|
||||||
// 连接池缓存:key=数据库文件路径,value=DBCP2数据源(支持多连接复用)
|
// 连接池缓存:key=数据库文件路径、value=DBCP2数据源(支持多连接复用)
|
||||||
private static final Map<String, BasicDataSource> DATA_SOURCE_POOL = new ConcurrentHashMap<>();
|
private static final Map<String, BasicDataSource> DATA_SOURCE_POOL = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
// 字段缓存:缓存类的字段映射(避免反射重复开销)
|
// 字段缓存:缓存类的字段映射(避免反射重复开销)
|
||||||
@ -46,7 +46,7 @@ public class SQLiteUtil {
|
|||||||
if (dbFilePath == null || dbFilePath.trim().isEmpty()) {
|
if (dbFilePath == null || dbFilePath.trim().isEmpty()) {
|
||||||
throw new IllegalArgumentException("数据库文件路径不能为空");
|
throw new IllegalArgumentException("数据库文件路径不能为空");
|
||||||
}
|
}
|
||||||
// 不存在则创建数据源,存在则直接从池获取连接
|
// 不存在则创建数据源、存在则直接从池获取连接
|
||||||
BasicDataSource dataSource = DATA_SOURCE_POOL.computeIfAbsent(dbFilePath, SQLiteUtil::createDataSource);
|
BasicDataSource dataSource = DATA_SOURCE_POOL.computeIfAbsent(dbFilePath, SQLiteUtil::createDataSource);
|
||||||
return dataSource.getConnection();
|
return dataSource.getConnection();
|
||||||
}
|
}
|
||||||
@ -63,7 +63,7 @@ public class SQLiteUtil {
|
|||||||
dataSource.setDriverClassName("org.sqlite.JDBC");
|
dataSource.setDriverClassName("org.sqlite.JDBC");
|
||||||
dataSource.setUrl("jdbc:sqlite:" + dbFilePath);
|
dataSource.setUrl("jdbc:sqlite:" + dbFilePath);
|
||||||
|
|
||||||
// 2. 连接池核心参数(根据并发量调整,SQLite不建议过多连接)
|
// 2. 连接池核心参数(根据并发量调整、SQLite不建议过多连接)
|
||||||
dataSource.setMaxTotal(30); // 最大连接数:20-50(根据服务器CPU/内存调整)
|
dataSource.setMaxTotal(30); // 最大连接数:20-50(根据服务器CPU/内存调整)
|
||||||
dataSource.setMaxIdle(15); // 最大空闲连接:保留部分连接避免频繁创建
|
dataSource.setMaxIdle(15); // 最大空闲连接:保留部分连接避免频繁创建
|
||||||
dataSource.setMinIdle(5); // 最小空闲连接:保证基础并发响应速度
|
dataSource.setMinIdle(5); // 最小空闲连接:保证基础并发响应速度
|
||||||
@ -84,7 +84,7 @@ public class SQLiteUtil {
|
|||||||
try (Connection conn = dataSource.getConnection();
|
try (Connection conn = dataSource.getConnection();
|
||||||
Statement stmt = conn.createStatement()) {
|
Statement stmt = conn.createStatement()) {
|
||||||
stmt.execute("PRAGMA journal_mode=WAL;"); // 启用WAL模式:支持多读者+单写者(核心优化)
|
stmt.execute("PRAGMA journal_mode=WAL;"); // 启用WAL模式:支持多读者+单写者(核心优化)
|
||||||
stmt.execute("PRAGMA cache_size=-20000;"); // 页面缓存20MB(负号表示KB单位,内存足可调大)
|
stmt.execute("PRAGMA cache_size=-20000;"); // 页面缓存20MB(负号表示KB单位、内存足可调大)
|
||||||
stmt.execute("PRAGMA synchronous=NORMAL;"); // 同步级别:平衡性能与安全(崩溃最多丢WAL日志)
|
stmt.execute("PRAGMA synchronous=NORMAL;"); // 同步级别:平衡性能与安全(崩溃最多丢WAL日志)
|
||||||
stmt.execute("PRAGMA temp_store=MEMORY;"); // 临时表/索引存内存(减少磁盘IO)
|
stmt.execute("PRAGMA temp_store=MEMORY;"); // 临时表/索引存内存(减少磁盘IO)
|
||||||
stmt.execute("PRAGMA busy_timeout=2000;"); // 忙等待超时:2秒(避免瞬时并发锁等待)
|
stmt.execute("PRAGMA busy_timeout=2000;"); // 忙等待超时:2秒(避免瞬时并发锁等待)
|
||||||
@ -165,7 +165,7 @@ public class SQLiteUtil {
|
|||||||
throw new IllegalArgumentException("查询SQL不能为空");
|
throw new IllegalArgumentException("查询SQL不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 预加载字段映射(缓存生效,避免重复反射)
|
// 预加载字段映射(缓存生效、避免重复反射)
|
||||||
Map<String, Field> fieldMap = getFieldMap(clazz);
|
Map<String, Field> fieldMap = getFieldMap(clazz);
|
||||||
|
|
||||||
// try-with-resources:自动关闭Connection、PreparedStatement、ResultSet
|
// try-with-resources:自动关闭Connection、PreparedStatement、ResultSet
|
||||||
@ -175,7 +175,7 @@ public class SQLiteUtil {
|
|||||||
|
|
||||||
ResultSetMetaData metaData = rs.getMetaData();
|
ResultSetMetaData metaData = rs.getMetaData();
|
||||||
int columnCount = metaData.getColumnCount();
|
int columnCount = metaData.getColumnCount();
|
||||||
// 预构建「列名-字段」映射(一次构建,循环复用)
|
// 预构建「列名-字段」映射(一次构建、循环复用)
|
||||||
ColumnFieldMapping[] mappings = buildColumnFieldMappings(metaData, columnCount, fieldMap);
|
ColumnFieldMapping[] mappings = buildColumnFieldMappings(metaData, columnCount, fieldMap);
|
||||||
|
|
||||||
// 处理结果集(反射赋值)
|
// 处理结果集(反射赋值)
|
||||||
@ -254,7 +254,7 @@ public class SQLiteUtil {
|
|||||||
*
|
*
|
||||||
* @param dbFilePath 数据库路径
|
* @param dbFilePath 数据库路径
|
||||||
* @param sql DDL语句
|
* @param sql DDL语句
|
||||||
* @param params SQL参数(可选,如动态表名)
|
* @param params SQL参数(可选、如动态表名)
|
||||||
*/
|
*/
|
||||||
public static void executeDDL(String dbFilePath, String sql, List<Object> params) throws SQLException {
|
public static void executeDDL(String dbFilePath, String sql, List<Object> params) throws SQLException {
|
||||||
if (dbFilePath == null || dbFilePath.trim().isEmpty()) {
|
if (dbFilePath == null || dbFilePath.trim().isEmpty()) {
|
||||||
@ -269,7 +269,7 @@ public class SQLiteUtil {
|
|||||||
pstmt.execute();
|
pstmt.execute();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
closeDataSource(dbFilePath);
|
closeDataSource(dbFilePath);
|
||||||
throw new SQLException("执行DDL失败(SQL:" + sql + ",路径:" + dbFilePath + ")", e);
|
throw new SQLException("执行DDL失败(SQL:" + sql + "、路径:" + dbFilePath + ")", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,7 +284,7 @@ public class SQLiteUtil {
|
|||||||
// ========================== 工具辅助方法 ==========================
|
// ========================== 工具辅助方法 ==========================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建PreparedStatement(复用Connection,避免重复获取)
|
* 创建PreparedStatement(复用Connection、避免重复获取)
|
||||||
*
|
*
|
||||||
* @param conn 已获取的Connection(从连接池来)
|
* @param conn 已获取的Connection(从连接池来)
|
||||||
* @param sql SQL语句
|
* @param sql SQL语句
|
||||||
@ -362,7 +362,7 @@ public class SQLiteUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取类的字段缓存(含父类字段,一次反射永久缓存)
|
* 获取类的字段缓存(含父类字段、一次反射永久缓存)
|
||||||
*
|
*
|
||||||
* @param clazz 目标类
|
* @param clazz 目标类
|
||||||
* @return 字段名→Field的映射(不可修改)
|
* @return 字段名→Field的映射(不可修改)
|
||||||
@ -385,7 +385,7 @@ public class SQLiteUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 优化的字段赋值(处理类型转换,避免重复异常捕获)
|
* 优化的字段赋值(处理类型转换、避免重复异常捕获)
|
||||||
*
|
*
|
||||||
* @param obj 目标对象
|
* @param obj 目标对象
|
||||||
* @param field 字段
|
* @param field 字段
|
||||||
@ -407,7 +407,7 @@ public class SQLiteUtil {
|
|||||||
field.set(obj, convertedValue);
|
field.set(obj, convertedValue);
|
||||||
}
|
}
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
System.err.println("警告:字段赋值失败(字段:" + field.getName() + ",值类型:" + value.getClass().getName() + "):" + e.getMessage());
|
System.err.println("警告:字段赋值失败(字段:" + field.getName() + "、值类型:" + value.getClass().getName() + "):" + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -451,7 +451,7 @@ public class SQLiteUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 不支持的类型转换(打印警告)
|
// 不支持的类型转换(打印警告)
|
||||||
System.err.println("警告:不支持的类型转换(目标类型:" + targetType.getName() + ",原始值类型:" + value.getClass().getName() + ")");
|
System.err.println("警告:不支持的类型转换(目标类型:" + targetType.getName() + "、原始值类型:" + value.getClass().getName() + ")");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -547,7 +547,7 @@ public class SQLiteUtil {
|
|||||||
// ========================== 内部辅助类 ==========================
|
// ========================== 内部辅助类 ==========================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 列-字段映射模型(一次性构建,减少循环内计算)
|
* 列-字段映射模型(一次性构建、减少循环内计算)
|
||||||
*/
|
*/
|
||||||
private static class ColumnFieldMapping {
|
private static class ColumnFieldMapping {
|
||||||
String columnName; // 数据库列名
|
String columnName; // 数据库列名
|
||||||
|
|||||||
Reference in New Issue
Block a user