在哪里使用 JDBC 创建准备好的语句?

Posted

技术标签:

【中文标题】在哪里使用 JDBC 创建准备好的语句?【英文标题】:Where to create a prepared statement with JDBC? 【发布时间】:2010-05-12 09:31:46 【问题描述】:

考虑以下方法,从某些数据结构 (InteractionNetwork) 读取数据并使用 SQLite-JDBC dirver 将它们写入 SQLite 数据库中的表:

private void loadAnnotations(InteractionNetwork network) throws SQLException 
    PreparedStatement insertAnnotationsQuery = 
        connection.prepareStatement(
        "INSERT INTO Annotations(GOId, ProteinId, OnthologyId) VALUES(?, ?, ?)");
    PreparedStatement getProteinIdQuery = 
        connection.prepareStatement(
        "SELECT Id FROM Proteins WHERE PrimaryUniProtKBAccessionNumber = ?");
    connection.setAutoCommit(false);
    for(common.Protein protein : network.get_protein_vector()) 
        /* Get ProteinId for the current protein from another table and
           insert the value into the prepared statement. */
        getProteinIdQuery.setString(1, protein.get_primary_id());
        ResultSet result = getProteinIdQuery.executeQuery();
        result.next();
        insertAnnotationsQuery.setLong(2, result.getLong(1));
        /* Extract all the other data and add all the tuples to the batch. */
    
    insertAnnotationsQuery.executeBatch();
    connection.commit();
    connection.setAutoCommit(true);

这段代码运行良好,程序运行时间约为 30 秒,平均占用 80m 堆空间。因为代码看起来很难看,我想重构它。我做的第一件事是将getProteinIdQuery 的声明移到循环中:

private void loadAnnotations(InteractionNetwork network) throws SQLException 
    PreparedStatement insertAnnotationsQuery = 
        connection.prepareStatement(
        "INSERT INTO Annotations(GOId, ProteinId, OnthologyId) VALUES(?, ?, ?)");
    connection.setAutoCommit(false);
    for(common.Protein protein : network.get_protein_vector()) 
        /* Get ProteinId for the current protein from another table and
           insert the value into the prepared statement. */
        PreparedStatement getProteinIdQuery = // <--- moved declaration of statement here
            connection.prepareStatement(
            "SELECT Id FROM Proteins WHERE PrimaryUniProtKBAccessionNumber = ?");
        getProteinIdQuery.setString(1, protein.get_primary_id());
        ResultSet result = getProteinIdQuery.executeQuery();
        result.next();
        insertAnnotationsQuery.setLong(2, result.getLong(1));
        /* Extract all the other data and add all the tuples to the batch. */
    
    insertAnnotationsQuery.executeBatch();
    connection.commit();
    connection.setAutoCommit(true);

当我现在运行代码时会发生什么,它需要大约 130m 的堆空间并且需要很长时间才能运行。谁能解释这种奇怪的行为?

【问题讨论】:

【参考方案1】:

正如您所发现的,准备一份声明需要时间。不管代码丑不丑,速度下降也很丑,所以你需要使用更快的形式。

但你可以做的是使用内部类来保存细节并提供更好的界面:

private class DatabaseInterface 
    private PreparedStatement insertAnnotation, getProteinId;
    public DatabaseInterface() 
        // This is an inner class; 'connection' is variable in outer class
        insertAnnotation = connection.prepareStatement(
            "INSERT INTO Annotations(GOId, ProteinId, OnthologyId) VALUES(?, ?, ?)");
        getProteinId = connection.prepareStatement(
            "SELECT Id FROM Proteins WHERE PrimaryUniProtKBAccessionNumber = ?");
    
    public long getId(Protein protein)  // Exceptions omitted...
        getProteinId.setString(1, protein.get_primary_id());
        ResultSet result = getProteinId.executeQuery();
        try 
            result.next();
            return result.getLong(1);
         finally 
            result.close();
        
    
    public void insertAnnotation(int GOId, long proteinId, String ontologyId) 
        insertAnnotation.setInt(1, GOId);          // type may be wrong
        insertAnnotation.setLong(2, proteinId);
        insertAnnotation.setString(3, ontologyId); // type may be wrong
        insertAnnotation.executeUpdate();
    

private void loadAnnotations(InteractionNetwork network) throws SQLException 
    connection.setAutoCommit(false);
    DatabaseInterface dbi = new DatabaseInterface();
    for(common.Protein protein : network.get_protein_vector()) 
        dbi.insertAnnotation(..., dbi.getId(protein), ...);
    
    connection.commit();
    connection.setAutoCommit(true);

目标是您拥有一段代码知道如何将事物整合到 SQL 中(如果您使用不同的数据库,这很容易适应),而另一段代码知道如何将这些事物协调在一起。

【讨论】:

还请注意,您可以考虑更广泛地共享胶水类;它与连接相关,而不是特定操作。【参考方案2】:

如果第一个片段看起来很难看,我想这是一个品味问题 ;-)...

但是,第二个代码片段需要更长的时间(恕我直言)的原因是,现在对于 for 循环的每次迭代,都会创建一个 PreparedStatement (getProteinIdQuery) 的新实例,而在第一个片段中,您重用了准备好的语句,以应有的方式使用它:实例化,然后提供适当的值。

至少,这是我的看法... 一月

【讨论】:

以上是关于在哪里使用 JDBC 创建准备好的语句?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 jdbc 准备好的语句传递可变数量的参数?

使用准备好的语句查询列表 (JDBC)

JDBC驱动程序为准备好的语句转义参数?

SQLITE JDBC 驱动程序准备好的语句失败(内部指针 0)

在 JDBC 中,为啥准备好的语句的参数索引从 1 而不是 0 开始?

JDBC 准备好的语句不起作用 [关闭]