タイガー!タイガー!じれったいぞー!(SE編)

AS400, Java, JavaEE, JSF等の開発、習慣など。日々の気づきをまとめたブログ(備忘録)

JDBC と Mybatis でバッチ処理

大量のSQLを発行して、レコードを追加したり、更新したりするケースは多くの場面で遭遇すると思います。

JDBC と Mybatis、それぞれのバッチ処理のサンプルを書いてみました。

JDBCでの実装

RDBAS400で、jt400を使ってアクセスします。

compile 'net.sf.jt400:jt400:9.1'

下記の例では、2人の社員モデルを生成し、PreparedStatementで追加処理を行っています。
addBatchメソッドでバッチ登録し、executeBatchメソッドでSQLを実行。
コミットメント制御は手動で行う必要があります。

import models.Employee;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class MainJDBC {

    public static void main(String[] args) {
        List<Employee> employees = new ArrayList<>();
        Employee emp1 = new Employee("0001", "山田", "太郎", "03-1234-5678", "30");
        Employee emp2 = new Employee("0002", "山田", "次郎", "03-2345-6789", "20");
        employees.add(emp1);
        employees.add(emp2);

        String url;
        String u1 = "jdbc:as400://";
        String u2 = ";libraries=TIGERDB";
        url = u1 + System.getenv("AS400_SERVER") + u2;
        String user = System.getenv("AS400_USER");
        String pass = System.getenv("AS400_PASSWORD");
        String sql = "insert into EMPLOYEES (EMP_CD, LAST_NAME, FIRST_NAME, PHONE_NUM, SEC_CD) values (?, ?, ?, ?, ?)";
        Connection conn = null;
        try {
            Class.forName("com.ibm.as400.access.AS400JDBCDriver");
            conn = DriverManager.getConnection(url, user, pass);
            conn.setAutoCommit(false);
            try (PreparedStatement ps = conn.prepareStatement(sql)) {
                for (Employee emp : employees) {
                    ps.setString(1, emp.getEmployeeCode());
                    ps.setString(2, emp.getLastName());
                    ps.setString(3, emp.getFirstName());
                    ps.setString(4, emp.getPhoneNum());
                    ps.setString(5, emp.getSectionCode());
                    ps.addBatch();
                }
                int[] cnt = ps.executeBatch();
                conn.commit();
                System.out.println("SQLの更新件数: " + Arrays.stream(cnt).sum());

            } catch (Exception ex) {
                conn.rollback();
                System.out.println("rollback");
                throw ex;
            }
        } catch (Exception ex) {
            ex.printStackTrace();

        } finally {
            try {
                conn.close();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }
    }
}

Mybatisでの実装

RDBは同じくAS400になります。

compile 'net.sf.jt400:jt400:9.1'
compile group: 'org.mybatis', name: 'mybatis', version: '3.5.6'

バッチ処理の指定は、configで初期設定する方法と、sqlSession生成時に個別設定する方法があります。

mybatis用configで初期設定

  • defaultExecutorTypeを指定できました。SqlSessionFactoryでsessionを open する際に、ExecutorType.BATCH の引数指定は不要です。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="defaultExecutorType" value="BATCH"/>
    </settings>
    <environments default="MAIN">
        <environment id="MAIN">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
                <!-- Isolation TRANSACTION_READ_COMMITTED = 2 -->
                <property name="defaultTransactionIsolationLevel" value="2"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="EmployeeMapper.xml"/>
    </mappers>
</configuration>
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(
    Resources.getResourceAsStream("mybatis-config.xml"), 
    getOptionParam());
session = factory.openSession();   // BATCH設定は configで実施済み

sqlSession生成時に個別設定する方法

import models.Employee;
import models.mappers.EmployeeMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.session.TransactionIsolationLevel;

import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.Properties;

public class MainMybatis {

    public static void main(String[] args) {
        SqlSession session = null;
        try {
            SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(
                    Resources.getResourceAsStream("mybatis-config.xml"), getOptionParam());
            // バッチモードでSQL Sessionを生成
            session = factory.openSession(ExecutorType.BATCH, TransactionIsolationLevel.READ_COMMITTED); 
        } catch (IOException ex) {
            ex.printStackTrace();
        }

        if (Objects.nonNull(session)) {
            try {
                EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
                //全社員表示
                List<Employee> employees = mapper.getAll();
                for (Employee e : employees) {
                    System.out.println(e.toString());
                }

                //追加処理
                Employee emp1 = new Employee("0003", "山田", "太郎", "03-1234-5678", "30");
                Employee emp2 = new Employee("0004", "山田", "次郎", "03-2345-6789", "20");
                System.out.println(mapper.insert(emp1));  //結果:  -2147482646 となる
                mapper.insert(emp2);
                session.flushStatements(); //データベースへフラッシュ(実行)
                session.clearCache(); //ローカルキャッシュをクリア

                //更新処理(BATCHは、更新処理も可能)
                Employee emp3 = new Employee("0002", "山下", "次郎", "03-2345-6789", "21");
                mapper.update(emp3);
                session.flushStatements();
                session.clearCache();

                //トランザクション確定
                session.commit();

            } finally {
                session.close();
            }
        }
    }

    private static Properties getOptionParam() {
        //mybatis Props作成
        Properties mybatisProps = new Properties();
        mybatisProps.put("driver", "com.ibm.as400.access.AS400JDBCDriver");
        String url;
        String u1 = "jdbc:as400://";
        String u2 = ";libraries=TIGERDB";
        url = u1 + System.getenv("AS400_SERVER") + u2;
        mybatisProps.put("url", url);
        mybatisProps.put("username", System.getenv("AS400_USER"));
        mybatisProps.put("password", System.getenv("AS400_PASSWORD"));
        return mybatisProps;
    }
}

バッチ処理の注意点

実際にバッチ処理を行う場合、更新対象が不確定であるため、場合によっては大量の追加・更新処理が行われてしまう可能性があります。

そのため、フラッシュするレコード数を事前に決めておいて、そのレコード数ごとに更新させたほうが良さそうです。

int batchFlushCount = 500;
int counter = 0;
int size = smList.size();
for (StockModel sm : smList) {
    counter++;
    stockDao.updateStockByPk(session, sm);
    if (counter % batchFlushCount == 0 || counter == size) {
        session.flushStatements();
        session.clearCache();
    }
}
session.commit();