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

Java多SQLite连接问题:JUnit测试遇SQLITE_BUSY数据库锁定错误

解决SQLite数据库锁(SQLITE_BUSY)问题的分析与方案

这个问题的核心原因其实和JUnit的测试执行机制、SQLite的锁特性,以及你没有关闭数据库连接的操作都有关系,咱们一步步拆解:

为什么会出现数据库锁?

  1. JUnit的实例创建逻辑:JUnit默认会为每个@Test方法创建一个全新的TestSuite类实例。也就是说,第一个测试方法执行时创建了一个DatabaseHandler实例(持有一个数据库连接),测试结束后这个连接因为没有被关闭,会一直持有数据库的资源;第二个测试方法执行时,又会创建一个新的DatabaseHandler实例(第二个数据库连接),当第二个连接尝试对数据库执行写操作(truncate、insert)时,第一个连接还没释放锁,SQLite的独占写锁机制就会触发SQLITE_BUSY错误。

  2. SQLite的锁机制:SQLite是文件型数据库,写操作需要获取独占锁。如果有一个连接还持有数据库的锁(哪怕没有显式开启事务),后续的写操作就会被阻塞,直到锁被释放。而你的DatabaseHandler没有任何关闭连接的逻辑,连接会一直存活到JVM垃圾回收,这就导致多个连接同时争抢数据库锁。

具体的解决方案

方案1:用JUnit生命周期注解管理连接的创建与关闭

在每个测试方法执行前初始化DatabaseHandler,测试结束后强制关闭连接,确保每个测试的资源都被清理:

import org.junit.AfterEach;
import org.junit.BeforeEach;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class TestSuite {
    private DatabaseHandler dbh;

    @BeforeEach
    public void setUp() {
        dbh = new DatabaseHandler("testdb");
    }

    @AfterEach
    public void tearDown() {
        try {
            if (dbh.statement != null) {
                dbh.statement.close();
            }
            if (dbh.connection != null) {
                dbh.connection.close();
            }
        } catch (SQLException e) {
            System.err.println(e.getMessage());
        }
    }

    @Test
    public void testInsertEntryDuplicateNoChange() {
        Listing listing = new Listing();
        dbh.truncateEntryTable();
        listing.setId("12345");
        listing.setName("John");
        listing.setAge(24);
        dbh.insertEntry(listing);
        assertEquals(3, dbh.insertEntry(listing));
    }

    @Test
    public void testInsertEntryDuplicatePriceChange() {
        Listing listing = new Listing();
        dbh.truncateEntryTable();
        listing.setId("54321");
        listing.setName("John");
        listing.setAge(34); // 这里你原来的代码写了listing.Age(34),应该是setAge,注意修正!
        dbh.insertEntry(listing);
        listing.setAge(55);
        assertEquals(1, dbh.insertEntry(listing));
    }
}

另外注意:你的第二个测试里有个语法错误listing.Age(34),应该是listing.setAge(34),这可能也是测试报错的潜在原因之一。

方案2:使用静态连接确保全局唯一

如果希望整个测试类共享一个数据库连接(减少连接创建开销),可以把dbh改成静态变量,用@BeforeClass和@AfterClass来管理连接的生命周期,但要注意测试之间的数据隔离(比如每个测试前都truncate表):

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class TestSuite {
    private static DatabaseHandler dbh;

    @BeforeClass
    public static void setUpClass() {
        dbh = new DatabaseHandler("testdb");
    }

    @AfterClass
    public static void tearDownClass() {
        try {
            if (dbh.statement != null) {
                dbh.statement.close();
            }
            if (dbh.connection != null) {
                dbh.connection.close();
            }
        } catch (SQLException e) {
            System.err.println(e.getMessage());
        }
    }

    // 测试方法和之前一致,注意每个测试前truncate表保证隔离
    @Test
    public void testInsertEntryDuplicateNoChange() {
        // ... 代码省略
    }

    @Test
    public void testInsertEntryDuplicatePriceChange() {
        // ... 代码省略
    }
}

额外建议:完善DatabaseHandler的资源管理

最好在DatabaseHandler里添加一个close()方法,把关闭连接和语句的逻辑封装起来,这样代码更整洁:

public class DatabaseHandler {
    // 你的现有成员变量:connection、statement等

    public DatabaseHandler(String dbName) {
        // 现有构造函数逻辑
    }

    public void close() {
        try {
            if (statement != null) {
                statement.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            System.err.println(e.getMessage());
        }
    }

    // 其他方法:insertEntry、truncateEntryTable等
}

这样在tearDown里只需要调用dbh.close()即可。

内容的提问来源于stack exchange,提问作者Cornel Verster

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:22:13