You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过单例类检查嵌入式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);
    }
}

关键逻辑说明

  1. 文件锁机制

    • 在共享文件夹创建专属锁文件,应用启动时尝试获取排他锁:
      • 成功获取锁:说明无其他实例运行,正常启动。
      • 获取失败:直接弹出提示并终止应用。
    • 应用正常退出时自动释放锁,确保其他用户可以启动。
  2. Derby异常捕获

    • 作为双重保障,当文件锁因权限等问题失效时,Derby会抛出特定SqlException(SQLState为XJ040,或消息包含database is already in use),捕获该异常后同样阻止启动。
  3. 注意事项

    • 必须替换LOCK_FILE_PATH为实际的共享文件夹路径,确保所有用户拥有该路径的读写权限。
    • 如果是后台应用,可将JOptionPane替换为日志输出或其他通知方式。
    • 若应用意外崩溃,锁文件可能残留,可手动删除;复杂场景下可在锁文件中写入进程ID,启动时校验进程是否存活。

内容的提问来源于stack exchange,提问作者nik ravan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 16:36:18