Java > SQLiteをJavaから使う方法

更新日 2013-12-21
広告
SQLiteは組み込み型のRDBMSです。 組み込み型のせいか、マルチスレッド/プロセスからアクセスするとエラーが発生することがあります。 以下、https://bitbucket.org/xerial/sqlite-jdbc で公開されているSQLiteのJDBCドライバを使って説明します。

エラーが発生する例

import java.sql.*;
import java.util.*;

public class Sample1 {

    public static void main(String[] args) throws Exception {

        Class.forName("org.sqlite.JDBC");
        
        Connection connection = null;

        // init table
        try {
            connection = DriverManager.getConnection("jdbc:sqlite:sample.db");
            Statement statement = connection.createStatement();
            statement.setQueryTimeout(30);
            statement.executeUpdate("drop table if exists person");
            statement.executeUpdate("create table person (id integer, name string)");
            
        } catch (SQLException e) {
            System.out.println(e);
        } finally {
            try {
                if(connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                System.out.println(e);
            }
        }

        // access from multi threads.
        List<Thread> threads = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            Thread thread = new Thread(new InsertThread());
            thread.start();
            threads.add(thread);
        }

        try {
            for (Thread thread : threads) {
                thread.join();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
    
}

class InsertThread implements Runnable {

    public void run() {

        Connection connection = null;

        try {
            connection = DriverManager.getConnection("jdbc:sqlite:sample.db");
            Statement statement = connection.createStatement();
            statement.executeUpdate("insert into person values(100, 'hoge')");

        } catch (SQLException e) {
            System.out.println(e);
        } finally {
            try {
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                System.out.println(e);
            }
        }

    }

}
このサンプルでは、100個のスレッドから、一斉にインサートを実行します。 各スレッドが、コネクションを作成します。 すると、以下のエラーが発生します。
java.sql.SQLException: database is locked
PostgreSQLやMySQLのようなスタンドアロン型のRDBMSであれば、このようなエラーは発生しません。

エラーを発生させない例

上で示したように、SQLiteにマルチスレッドでアクセスする場合は、注意が必要です。 以下に、上記エラーを回避する方法を紹介します。
import java.sql.*;
import java.util.*;

public class Sample2 {

    public static void main(String[] args) throws Exception {

        Class.forName("org.sqlite.JDBC");
        
        Connection connection = null;

        // init table
        try {
            connection = DriverManager.getConnection("jdbc:sqlite:sample.db");
            Statement statement = connection.createStatement();
            statement.setQueryTimeout(30);
            statement.executeUpdate("drop table if exists person");
            statement.executeUpdate("create table person (id integer, name string)");
            
        } catch (SQLException e) {
            System.out.println(e);
        } finally {
            try {
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                System.out.println(e);
            }
        }

        // access from multi threads
        List<Thread> threads = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            Thread thread = new Thread(new SyncInsertThread());
            thread.start();
            threads.add(thread);
        }

        try {
            for (Thread thread : threads) {
                thread.join();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            connection.close();
        } catch (SQLException e) {
            System.out.println(e);
        }

    }
    
}

class SyncInsertThread implements Runnable {

    private static Connection connection;

    static {
        try {
            connection = DriverManager.getConnection("jdbc:sqlite:sample.db");
            connection.setAutoCommit(false);
        } catch (Exception e) {
            System.out.println(e);
        }
    }

    public void run() {
        insert();
    }

    public static synchronized void insert() {
        try {
            Statement statement = connection.createStatement();
            statement.setQueryTimeout(30);
            statement.executeUpdate("insert into person values(100, 'hoge')");
            connection.commit();
            statement.close();

        } catch (SQLException e) {
            System.out.println(e);
        }
    }

}

この例では、1つしかConnectionオブジェクトを作成しません。 また、DBアクセスを行うメソッドをstatic synchronizedにしています。 つまり、同時に1つのスレッドのみが、DBアクセスを行います。

こうすると、上記のエラーは発生しません。 各スレッドが順番にDBアクセスするので、処理に時間がかかりますが、エラーを回避するほうが重要だと思います。

広告
お問い合わせは sweng.tips@gmail.com まで。
inserted by FC2 system