package quant.rich.emoney.config; import java.io.File; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import javax.sql.DataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; import com.zaxxer.hikari.HikariDataSource; import lombok.extern.slf4j.Slf4j; import quant.rich.emoney.EmoneyAutoApplication; @Slf4j @Configuration @MapperScan(basePackages = "quant.rich.emoney.mapper.sqlite", sqlSessionTemplateRef = "sqliteSqlSessionTemplate") public class SqliteMybatisConfig { private static final String RESOURCE_PATH = "database.db"; public static final String SQLITE_TRANSACTION_MANAGER = "sqliteTransactionManager"; /** * 配置数据库连接 * * @param dataSource */ public void initSQLiteLocation(DataSource dataSource) { // 指定 sqlite 路径 if (dataSource instanceof HikariDataSource hikariDataSource) { String filePath = hikariDataSource.getJdbcUrl(); if (filePath == null || !filePath.startsWith("jdbc:sqlite:")) { log.warn( "无法在 SQLite HikariDataSource 中找到合法 SQLite JDBC url, " + "数据库可能会加载失败。合法的 url 需在 application.yml(properties) " + "中配置,以 jdbc:sqlite: 开头。当前获取到的 jdbc-url: {}", filePath); return; } filePath = filePath.substring("jdbc:sqlite:".length()).trim(); ClassPathResource original = new ClassPathResource(RESOURCE_PATH); if (!original.exists()) { log.warn("未找到 SQLite 资源: {}", RESOURCE_PATH); return; } String protocol = EmoneyAutoApplication.class.getProtectionDomain().getCodeSource().getLocation().getProtocol(); boolean isJar = "jar".equals(protocol); if (isJar) { // 复制到外部 yml 指定路径,已存在则不复制 File dest = new File(filePath), parentDir = dest.getParentFile(); String destAbsolutePath = dest.getAbsolutePath(); if (!parentDir.exists() && !parentDir.mkdirs()) { log.warn("无法创建放置 SQLite 文件的目录: {}", parentDir.getAbsolutePath()); return; } if (dest.exists()) { // 已存在 log.warn("目标资源 {} 已存在,忽略", destAbsolutePath); return; } try (InputStream in = getClass().getClassLoader().getResourceAsStream(RESOURCE_PATH)) { if (in == null) { log.warn("无法读取 SQLite 资源: {}", RESOURCE_PATH); return; } Files.copy(in, dest.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); log.info("SQLite 数据库文件已复制至:{}", destAbsolutePath); } catch (Exception e) { log.warn("复制 SQLite 数据库文件失败", e); } } else { // 使用当前绝对路径 Path path = Path.of("src/main/resources", RESOURCE_PATH); hikariDataSource.setJdbcUrl("jdbc:sqlite:" + path.toAbsolutePath().toString()); } } } @Bean("sqliteSqlSessionFactory") public SqlSessionFactory sqliteSqlSessionFactory( @Qualifier("sqliteDataSource") DataSource dataSource) throws Exception { initSQLiteLocation(dataSource); MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean(); factory.setDataSource(dataSource); MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.SQLITE)); interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); factory.setPlugins(interceptor); return factory.getObject(); } @Bean("sqliteSqlSessionTemplate") public SqlSessionTemplate sqliteSqlSessionTemplate(@Qualifier("sqliteSqlSessionFactory") SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); } @Bean(SQLITE_TRANSACTION_MANAGER) public DataSourceTransactionManager postgreTransacetionManager(@Qualifier("sqliteDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }