Fix 'java.lang.NoClassDefFoundError: com/zaxxer/hikari/HikariConfig'

This commit is contained in:
mcraftbbs 2025-10-18 14:23:38 +08:00
parent eca5397805
commit 253dd3ecb1
4 changed files with 119 additions and 100 deletions

View File

@ -4,7 +4,7 @@ plugins {
}
group = 'com'
version = '1.1.5'
version = '1.1.6'
repositories {
mavenCentral()

View File

@ -1,6 +1,5 @@
package com.ollamachat;
import com.ollamachat.core.Ollamachat;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.bukkit.configuration.file.FileConfiguration;
@ -16,14 +15,12 @@ public class DatabaseManager {
private final JavaPlugin plugin;
private final Logger logger;
private String databaseType; // "sqlite" or "mysql"
private Connection sqliteConnection; // For SQLite, maintain a single connection
private HikariDataSource dataSource; // For MySQL connection pooling
private final ClassLoader dependencyClassLoader;
private Connection sqliteConnection; // For SQLite
private HikariDataSource dataSource; // For MySQL (HikariCP)
public DatabaseManager(JavaPlugin plugin, ClassLoader dependencyClassLoader) {
public DatabaseManager(JavaPlugin plugin) {
this.plugin = plugin;
this.logger = plugin.getLogger();
this.dependencyClassLoader = dependencyClassLoader;
initializeDatabase();
}
@ -47,10 +44,10 @@ public class DatabaseManager {
private void initializeSQLite() {
try {
Class.forName("org.sqlite.JDBC", true, dependencyClassLoader);
databaseType = "sqlite";
Class.forName("org.sqlite.JDBC");
sqliteConnection = DriverManager.getConnection("jdbc:sqlite:plugins/OllamaChat/chat_history.db");
sqliteConnection.setAutoCommit(true);
databaseType = "sqlite";
logger.info("SQLite database initialized successfully.");
} catch (ClassNotFoundException | SQLException e) {
logger.severe("Failed to initialize SQLite database: " + e.getMessage());
@ -60,14 +57,15 @@ public class DatabaseManager {
private void initializeMySQL() {
try {
Class.forName("com.mysql.cj.jdbc.Driver", true, dependencyClassLoader);
} catch (ClassNotFoundException e) {
logger.severe("MySQL JDBC driver not found. Falling back to SQLite.");
initializeSQLite();
return;
}
// 确认 MySQL 驱动是否存在
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
logger.severe("MySQL JDBC driver not found! Falling back to SQLite.");
initializeSQLite();
return;
}
try {
FileConfiguration config = plugin.getConfig();
String host = config.getString("database.mysql.host", "localhost");
int port = config.getInt("database.mysql.port", 3306);
@ -75,11 +73,15 @@ public class DatabaseManager {
String username = config.getString("database.mysql.username", "root");
String password = config.getString("database.mysql.password", "");
// 创建 Hikari 配置
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl(String.format("jdbc:mysql://%s:%d/%s?useSSL=false&allowPublicKeyRetrieval=true&autoReconnect=true", host, port, database));
hikariConfig.setJdbcUrl(String.format(
"jdbc:mysql://%s:%d/%s?useSSL=false&allowPublicKeyRetrieval=true&autoReconnect=true",
host, port, database));
hikariConfig.setUsername(username);
hikariConfig.setPassword(password);
// Load HikariCP settings from config
// 从配置文件读取连接池参数
hikariConfig.setMaximumPoolSize(config.getInt("database.mysql.hikari.maximum-pool-size", 10));
hikariConfig.setMinimumIdle(config.getInt("database.mysql.hikari.minimum-idle", 2));
hikariConfig.setConnectionTimeout(config.getLong("database.mysql.hikari.connection-timeout", 30000));
@ -90,7 +92,7 @@ public class DatabaseManager {
hikariConfig.addDataSourceProperty("prepStmtCacheSqlLimit", config.getString("database.mysql.hikari.prep-stmt-cache-sql-limit", "2048"));
dataSource = new HikariDataSource(hikariConfig);
logger.info("MySQL database initialized with HikariCP successfully.");
logger.info("MySQL database initialized successfully using HikariCP.");
} catch (Exception e) {
logger.severe("Failed to initialize MySQL database: " + e.getMessage());
logger.warning("Falling back to SQLite due to MySQL initialization failure.");
@ -99,45 +101,57 @@ public class DatabaseManager {
}
private Connection getConnection() throws SQLException {
if (databaseType.equals("sqlite")) {
if ("sqlite".equals(databaseType)) {
if (sqliteConnection == null || sqliteConnection.isClosed()) {
sqliteConnection = DriverManager.getConnection("jdbc:sqlite:plugins/OllamaChat/chat_history.db");
sqliteConnection.setAutoCommit(true);
}
return sqliteConnection;
} else {
return dataSource.getConnection();
}
return dataSource.getConnection();
}
private void createTables() throws SQLException {
try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {
stmt.execute("CREATE TABLE IF NOT EXISTS players (" +
"uuid TEXT PRIMARY KEY," +
"username TEXT NOT NULL)");
// 根据数据库类型选择不同的列类型
String uuidType = databaseType.equals("mysql") ? "VARCHAR(36)" : "TEXT";
String textType = databaseType.equals("mysql") ? "VARCHAR(255)" : "TEXT";
String aiModelType = databaseType.equals("mysql") ? "VARCHAR(100)" : "TEXT"; // 减少长度
String longTextType = databaseType.equals("mysql") ? "TEXT" : "TEXT";
// Players
stmt.execute("CREATE TABLE IF NOT EXISTS players (" +
"uuid " + uuidType + " PRIMARY KEY," +
"username " + textType + " NOT NULL)");
// Conversations - 减少 ai_model 长度
stmt.execute("CREATE TABLE IF NOT EXISTS conversations (" +
"conversation_id TEXT NOT NULL," +
"player_uuid TEXT NOT NULL," +
"ai_model TEXT NOT NULL," +
"conversation_name TEXT NOT NULL," +
"conversation_id " + uuidType + " NOT NULL," +
"player_uuid " + uuidType + " NOT NULL," +
"ai_model " + aiModelType + " NOT NULL," +
"conversation_name " + textType + " NOT NULL," +
"created_at DATETIME DEFAULT CURRENT_TIMESTAMP," +
"PRIMARY KEY (conversation_id, player_uuid, ai_model)," +
"FOREIGN KEY (player_uuid) REFERENCES players(uuid))");
// Chat history
stmt.execute("CREATE TABLE IF NOT EXISTS chat_history (" +
"id INTEGER PRIMARY KEY " + (databaseType.equals("sqlite") ? "AUTOINCREMENT" : "AUTO_INCREMENT") + "," +
"player_uuid TEXT NOT NULL," +
"ai_model TEXT NOT NULL," +
"conversation_id TEXT," +
"player_uuid " + uuidType + " NOT NULL," +
"ai_model " + aiModelType + " NOT NULL," +
"conversation_id " + uuidType + "," +
"timestamp DATETIME DEFAULT CURRENT_TIMESTAMP," +
"prompt TEXT NOT NULL," +
"response TEXT NOT NULL," +
"prompt " + longTextType + " NOT NULL," +
"response " + longTextType + " NOT NULL," +
"FOREIGN KEY (player_uuid) REFERENCES players(uuid)," +
"FOREIGN KEY (conversation_id, player_uuid, ai_model) REFERENCES conversations(conversation_id, player_uuid, ai_model))");
logger.info("Database tables created successfully for " + databaseType.toUpperCase());
}
}
public void savePlayerInfo(UUID uuid, String username) {
String sql = databaseType.equals("sqlite")
? "INSERT OR REPLACE INTO players (uuid, username) VALUES (?, ?)"

View File

@ -3,19 +3,17 @@ package com.ollamachat;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.*;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.nio.file.*;
import java.util.*;
import java.util.logging.Logger;
public class DependencyLoader {
private final JavaPlugin plugin;
private final Logger logger;
private final File libDir;
private URLClassLoader dependencyClassLoader;
public DependencyLoader(JavaPlugin plugin) {
this.plugin = plugin;
@ -25,44 +23,31 @@ public class DependencyLoader {
public boolean loadDependencies() {
try {
// Create libs directory if it doesn't exist
if (!libDir.exists()) {
libDir.mkdirs();
}
// Define dependencies to download
List<Dependency> dependencies = new ArrayList<>();
dependencies.add(new Dependency("com.mysql", "mysql-connector-j", "8.0.33"));
dependencies.add(new Dependency("org.xerial", "sqlite-jdbc", "3.46.0.0"));
dependencies.add(new Dependency("com.zaxxer", "HikariCP", "5.1.0")); // Add HikariCP
// Download each dependency
List<URL> jarUrls = new ArrayList<>();
for (Dependency dep : dependencies) {
File jarFile = downloadDependency(dep);
if (jarFile != null) {
jarUrls.add(jarFile.toURI().toURL());
logger.info("Found dependency: " + jarFile.getName());
} else {
logger.warning("Failed to download dependency: " + dep.artifactId + "-" + dep.version);
}
}
if (jarUrls.isEmpty()) {
logger.warning("No dependencies were successfully downloaded.");
if (!libDir.exists() && !libDir.mkdirs()) {
logger.severe("Failed to create libs directory: " + libDir.getAbsolutePath());
return false;
}
// Create a new classloader with the dependencies
dependencyClassLoader = new URLClassLoader(
jarUrls.toArray(new URL[0]),
plugin.getClass().getClassLoader()
// 定义依赖
List<Dependency> dependencies = List.of(
new Dependency("com.mysql", "mysql-connector-j", "8.0.33"),
new Dependency("org.xerial", "sqlite-jdbc", "3.46.0.0"),
new Dependency("com.zaxxer", "HikariCP", "5.1.0")
);
// Set this classloader as the thread context classloader
Thread.currentThread().setContextClassLoader(dependencyClassLoader);
logger.info("Successfully loaded " + jarUrls.size() + " dependencies");
int loadedCount = 0;
for (Dependency dep : dependencies) {
File jarFile = downloadDependency(dep);
if (jarFile != null && injectToPluginClassLoader(jarFile)) {
loadedCount++;
logger.info("Loaded dependency: " + jarFile.getName());
} else {
logger.warning("Failed to load dependency: " + dep.artifactId);
}
}
logger.info("Successfully loaded " + loadedCount + " dependencies.");
return true;
} catch (Exception e) {
logger.severe("Failed to load dependencies: " + e.getMessage());
@ -71,14 +56,11 @@ public class DependencyLoader {
}
}
public ClassLoader getDependencyClassLoader() {
return dependencyClassLoader;
}
private File downloadDependency(Dependency dep) {
String fileName = dep.artifactId + "-" + dep.version + ".jar";
File file = new File(libDir, fileName);
// 已存在则直接返回
if (file.exists()) {
return file;
}
@ -87,16 +69,56 @@ public class DependencyLoader {
dep.groupId.replace(".", "/"), dep.artifactId, dep.version, dep.artifactId, dep.version);
String url = "https://repo1.maven.org/maven2/" + mavenPath;
logger.info("Downloading dependency: " + fileName + " from " + url);
try (InputStream in = new URL(url).openStream()) {
Files.copy(in, file.toPath());
Path temp = Files.createTempFile(libDir.toPath(), dep.artifactId, ".tmp");
Files.copy(in, temp, StandardCopyOption.REPLACE_EXISTING);
Files.move(temp, file.toPath(), StandardCopyOption.REPLACE_EXISTING);
logger.info("Downloaded dependency: " + fileName);
return file;
} catch (IOException e) {
logger.severe("Failed to download dependency " + fileName + ": " + e.getMessage());
logger.severe("Failed to download " + fileName + ": " + e.getMessage());
return null;
}
}
/**
* JAR 动态注入到 Bukkit PluginClassLoader
*/
private boolean injectToPluginClassLoader(File jarFile) {
try {
ClassLoader pluginClassLoader = plugin.getClass().getClassLoader();
// 仅在类加载器是 URLClassLoader 或包含 addURL 方法时注入
Method addURLMethod = null;
Class<?> current = pluginClassLoader.getClass();
while (current != null && addURLMethod == null) {
try {
addURLMethod = current.getDeclaredMethod("addURL", URL.class);
} catch (NoSuchMethodException ignored) {
}
current = current.getSuperclass();
}
if (addURLMethod == null) {
logger.severe("Unable to find addURL method in plugin classloader. Injection failed.");
return false;
}
addURLMethod.setAccessible(true);
addURLMethod.invoke(pluginClassLoader, jarFile.toURI().toURL());
logger.info("Injected dependency into classloader: " + jarFile.getName());
return true;
} catch (Exception e) {
logger.severe("Failed to inject dependency " + jarFile.getName() + ": " + e.getMessage());
return false;
}
}
/**
* 内部依赖描述类
*/
private static class Dependency {
String groupId;
String artifactId;
@ -109,7 +131,3 @@ public class DependencyLoader {
}
}
}

View File

@ -28,14 +28,9 @@ public class Ollamachat extends JavaPlugin {
@Override
public void onEnable() {
DependencyLoader dependencyLoader = new DependencyLoader(this);
ClassLoader dependencyClassLoader;
try {
dependencyLoader.loadDependencies();
dependencyClassLoader = dependencyLoader.getClass().getClassLoader();
} catch (Exception e) {
getLogger().severe("Failed to load dependencies: " + e.getMessage());
getServer().getPluginManager().disablePlugin(this);
DependencyLoader loader = new DependencyLoader(this);
if (!loader.loadDependencies()) {
getLogger().severe("Failed to load dependencies. ");
return;
}
@ -43,9 +38,10 @@ public class Ollamachat extends JavaPlugin {
configManager.initialize();
try {
databaseManager = new DatabaseManager(this, dependencyClassLoader);
databaseManager = new DatabaseManager(this);
} catch (Exception e) {
getLogger().severe("Failed to initialize DatabaseManager: " + e.getMessage());
e.printStackTrace();
getServer().getPluginManager().disablePlugin(this);
return;
}
@ -106,13 +102,4 @@ public class Ollamachat extends JavaPlugin {
public Map<UUID, Boolean> getPlayerSuggestionToggles() {
return playerSuggestionToggles;
}
}
}