如何通过单例类检查嵌入式Derby数据库是否已被其他JVM启动
嵌入式Derby数据库多实例冲突检测实现方案
针对你的需求,我们可以通过文件锁机制+Derby专属异常捕获的双重方案,实现多JVM实例的冲突检测,阻止应用重复启动。以下是修改后的PersistenceManager类代码及关键说明:
修改后的完整代码
import org.apache.derby.client.am.SqlException; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import java.io.File; import java.io.FileOutputStream; import java.nio.channels.FileLock; import javax.swing.JOptionPane; public class PersistenceManager { public static final boolean DEBUG = true; private static final PersistenceManager singleton = new PersistenceManager(); protected EntityManagerFactory emf; // 替换为共享文件夹中的实际锁文件路径(如Windows:\\server\\share\\app.lock,Linux:/mnt/share/app.lock) private static final String LOCK_FILE_PATH = "/path/to/shared/folder/derby-app.lock"; private static FileLock appLock; private static FileOutputStream lockOutputStream; public static PersistenceManager getInstance() { return singleton; } private PersistenceManager() { } public EntityManagerFactory getEntityManagerFactory() { if (emf == null) { // 第一步:检查共享锁文件,判断是否已有实例运行 if (!acquireAppLock()) { JOptionPane.showMessageDialog(null, "应用已被其他用户启动,无法重复打开!", "启动失败", JOptionPane.ERROR_MESSAGE); System.exit(1); } // 第二步:尝试创建EntityManagerFactory,捕获Derby占用异常 try { createEntityManagerFactory(); } catch (Exception e) { handleDerbyConflict(e); } } return emf; } public void closeEntityManagerFactory() { if (emf != null) { emf.close(); emf = null; if (DEBUG) System.out.println("\n*** Persistence finished at " + new java.util.Date()); } // 退出时释放锁文件 releaseAppLock(); } protected void createEntityManagerFactory() { this.emf = Persistence.createEntityManagerFactory("NewPersistenceUnit"); if (DEBUG) System.out.println("\n*** Persistence started at " + new java.util.Date()); } /** * 获取共享文件夹的排他锁,判断是否已有实例运行 * @return true=获取锁成功(无其他实例);false=锁被占用(已有实例) */ private boolean acquireAppLock() { try { File lockFile = new File(LOCK_FILE_PATH); lockFile.getParentFile().mkdirs(); // 确保父目录存在 lockOutputStream = new FileOutputStream(lockFile, false); // 尝试获取排他锁,失败则直接返回false appLock = lockOutputStream.getChannel().tryLock(); return appLock != null; } catch (Exception e) { if (DEBUG) e.printStackTrace(); return false; } } /** * 释放锁文件资源 */ private void releaseAppLock() { try { if (appLock != null && appLock.isValid()) appLock.release(); if (lockOutputStream != null) lockOutputStream.close(); // 可选:删除锁文件,避免异常退出后残留 // new File(LOCK_FILE_PATH).delete(); } catch (Exception e) { if (DEBUG) e.printStackTrace(); } } /** * 处理Derby数据库被占用的异常 */ private void handleDerbyConflict(Exception e) { if (e instanceof SqlException) { SqlException sqlEx = (SqlException) e; // Derby嵌入式模式被占用的特征:SQLState为XJ040,或消息包含"database is already in use" if ("XJ040".equals(sqlEx.getSQLState()) || sqlEx.getMessage().contains("database is already in use")) { JOptionPane.showMessageDialog(null, "数据库已被其他实例占用,无法启动应用!", "启动失败", JOptionPane.ERROR_MESSAGE); releaseAppLock(); System.exit(1); } } // 非冲突异常正常抛出 throw new RuntimeException(e); } }
关键逻辑说明
文件锁机制
- 在共享文件夹创建专属锁文件,应用启动时尝试获取排他锁:
- 成功获取锁:说明无其他实例运行,正常启动。
- 获取失败:直接弹出提示并终止应用。
- 应用正常退出时自动释放锁,确保其他用户可以启动。
- 在共享文件夹创建专属锁文件,应用启动时尝试获取排他锁:
Derby异常捕获
- 作为双重保障,当文件锁因权限等问题失效时,Derby会抛出特定
SqlException(SQLState为XJ040,或消息包含database is already in use),捕获该异常后同样阻止启动。
- 作为双重保障,当文件锁因权限等问题失效时,Derby会抛出特定
注意事项
- 必须替换
LOCK_FILE_PATH为实际的共享文件夹路径,确保所有用户拥有该路径的读写权限。 - 如果是后台应用,可将
JOptionPane替换为日志输出或其他通知方式。 - 若应用意外崩溃,锁文件可能残留,可手动删除;复杂场景下可在锁文件中写入进程ID,启动时校验进程是否存活。
- 必须替换
内容的提问来源于stack exchange,提问作者nik ravan
相关产品推荐
相关产品推荐

