大量のSQLを発行して、レコードを追加したり、更新したりするケースは多くの場面で遭遇すると思います。
JDBC と Mybatis、それぞれのバッチ処理のサンプルを書いてみました。
JDBCでの実装
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での実装
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();