タイガー!タイガー!じれったいぞー!(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();

【SQL】ORDER BY句でオリジナルの順番でソートする方法

完全に自分用の備忘録です。

先日、SQL の ORDER BY句で、オリジナルの順序で並べたい機会がありました。

そもそも、そういう機会があることがイレギュラーだとわかってはいるのですが、やりたいものは仕方ありません。

すると、チーム・メンバーが、下記のサイトを教えてくれました。

qiita.com

これはありがたいです! ありがとうございます!!

やはり、CASE文を制する者が SQLを制するのでしょうか!?

最高です。

SELECT
  SECTION_CODE, SUM(AMOUNT1), SUM(AMOUNT2)
FROM
    TABLE1
WHERE
    DATE_FIELD = 20201231
GROUP BY
    SECTION_CODE
ORDER BY             
    CASE SECTION_CODE
        WHEN 'A1' THEN 10
        WHEN 'A2' THEN 20
        WHEN 'AB' THEN 30
        WHEN 'B1' THEN 40
        WHEN 'BA' THEN 50
        ELSE 999
    END

2021年は「○○力」で!

f:id:no14141:20210103215057j:plain

新年、明けましておめでとうございます!

コロナ禍で遠出はできず、気がついたら、あっという間に年末年始休暇も今日で終了となります。

明日から仕事になりますので、ここは新年の目標を書いておこうと思います。

2020年までの目標

昔は、新年の目標をダラダラと箇条書きで何個も立てていたのですが、3年前からは止めました。

その代わりとして、1年に1つの「力」をアップさせる目標設定に切り替えました。

つまり、「○○力の向上」というシンプルな形です。

2018年は「習慣力」。

2019年は「睡眠力」。

2020年は「人間力」。

ズバリ、いまく目標達成できたのは、2018年の「習慣力」のみ。睡眠力も上がっておらず(むしろ下がっている)、人間力もまったく上がった感じがしません。去年は、ガンガン色々な場所に打ってでて、経験を増やして見える景色を変えて、人間力を上げたかったのですが・・・。

しかし、習慣力がアップしたことだけは間違いなく、その後の「読書習慣」「RUN習慣」「筋トレ習慣」「学習習慣」など、3年以上も継続できています。これは本当に良かった!
(その結果、マラソンでは「サブ4」をギリギリ達成することができました)

3年前に「習慣力」を上げるぞ!!と自分自身に誓って、本当に良かったと思っています。

2021年の目標

さて、今年の目標です。

元日時点ではまだ決めていなかったのですが、昨日、ふと頭の中に湧き上がってきたのです。

「技術力!!」

これで決めました!

コロナ禍もしばらく続きますし、地方の我々でも勉強できる環境は一気に加速している今だからこそです。

逆に、今やらないで、いつやるというのでしょうか!?

「やるなら、今しかねぇ~♪」と「北の国から」での1シーンを思い浮かべてしまいました。

具体的には、下記のことをレベルアップします。

*データベース周辺(DB試験合格)
クラウド関連(GCP
*フロントエンド開発関連(vue3.0)
*インフラ関連(K8S、Terraformなど)
Java強化(Springなど)
*多言語(Python
*統計関連(BIマスター)

そのためには、毎日、オンライン学習を再開します。去年も秋くらいでストップしていたので、こちらを毎日コツコツアップします。

ただ時間をかければいいわけでもないので、良書と呼ばれる「技術書」もうまく活用していく予定です。

まとめ

目標が決まりました。

2021年終了時点で、現時点より上記のスキルが上がっていて、人に説明できるレベルになっていること。

それができれば、本業に生かされること間違いなし!

やったります!!! 上だけを見つづけて上昇あるのみです!!!

12月は習慣の見直しチャンス!

f:id:no14141:20201228183617j:plain

いよいよ今年も残すところ、今日を入れてあと3日となりました。

毎年、年の初めには、新しい目標を立て、行動計画を立てるチャンスではあります。
しかしながら、私も同様にそれを繰り返してきたのですが、途中で挫折する機会が多くありました。

つまり、年の初めでは「時すでに遅し!」であるということがわかったのです。
その理由は、正月気分の中、始めることにあるのではないかと推測しました。

2017年12月、薄々そのことに気が付きまして、来年の目標を12月上旬に計画を立てて、さらに行動を前倒しで始めてみたところ、意外とそれからうまく「習慣」が回り始めたのでした!

習慣づくりのポイント

「習慣」について分解してみると、組み立てやすくなります。

まずは、「続ける習慣」と「やめる習慣」。今の生活に何か新しい行動をプラスするのが「続ける習慣」で、今の生活でやっていることをマイナスしていくのが「やめる習慣」です。

行動を増やす、または止める(減らす)のは、比較的簡単に計画できるのですが、もっとも重要なのは「思考」の習慣。

これについては、普段の行動を変えることで、習慣になり、その結果、「思考」が良い方向に(あるべき形に)向かっていくので、今の段階では、まずは「行動」の習慣を増やすか止めることの計画を立てた方が良いでしょう。

行動計画を12月早々に決めてしまい、それを来年からと言わずに、12月から前倒しで実践していく!

これが今回のブログの主題となります。

2021年に向けての新習慣

今年は、出遅れてしまいました。。。

現時点で日次ベースの習慣を数えてみたら、40近くもありました。

その中の習慣リストをパッと見た感じ、止めてもいい習慣は、「1日1回の感謝ツイート」くらいかな。

この習慣は2年間1日も休まずに続けて来れましたが、パブリックに投稿してきたので、どうしても詳細に踏み込めずに、ぼんやりとした表現が多くて、最近はもやもや感がありました。

こちらを、「3行感謝日記」や「4行日記」に移行しようかと計画中です(12月上旬からやりたかった・・・)。

この日記のOUTPUT先は、Googleカレンダーを検討しています。

検索もできるし、自分だけの記録先なので、本音でその日その日の気持ちを吐き出すことができそうです。

以上は習慣の置換計画ですが、もう1つ、加える習慣として今週から始めているのが、「就寝2時間前からブルーライトカット用のメガネを装着する」です。

奮発してJINSでメガネを買ったのでした(こちらは、近日中にそのエピソードについてブログに書こうとは思っております)。

まとめ

習慣について、今の気持ちをツラツラと書いてみました。

まずは良い習慣プランを決め、それを黙々と続けること。その結果、思考の習慣も変化し、良いことをたくさん引き寄せることができるようになると、私は信じています。

こんな時代だからこそ、ヤケにならずに「習慣」にフォーカスしてみてはいかがでしょうか。

元旦からではなく、思い立ったら「吉日」ですから。

【Netbeans8.2】Mavenリポジトリが参照できない

既存の Mavenプロジェクト を久しぶりにメンテしようとしたときの話です。

そのプロジェクトはMavenで管理で、Gitから取得したソースでは「pom.xml」が改変されていました(jacocoの追加)。

自分が使っているIDENetBeans はVer8.2でありまして、かなり古いVersionなのですが(Apacheでは無いVersion)、「消去」や「ビルド」など、すべてのmaven操作ができなくなってしまいました。

原因調査

原因は、mavenセントラルへのアクセスに失敗してしまうことです。

jacocoが入っていると、どうやら毎回mavenセントラルにアクセスに行くようです(詳細は不明)。

------------------------------------------------------------------------
Building ProjectName 1.0
------------------------------------------------------------------------
Downloading: http://repo.maven.apache.org/maven2/org/jacoco/jacoco-maven-plugin/0.8.6/jacoco-maven-plugin-0.8.6.pom

------------------------------------------------------------------------
BUILD FAILURE
------------------------------------------------------------------------
Total time: 4.259 s
Finished at: 2020-12-25T17:58:03+09:00
Final Memory: 9M/245M
------------------------------------------------------------------------
Plugin org.jacoco:jacoco-maven-plugin:0.8.6 or one of its dependencies could not be resolved: Failed to read artifact descriptor for org.jacoco:jacoco-maven-plugin:jar:0.8.6: Could not transfer artifact org.jacoco:jacoco-maven-plugin:pom:0.8.6 from/to central (http://repo.maven.apache.org/maven2): Failed to transfer file: http://repo.maven.apache.org/maven2/org/jacoco/jacoco-maven-plugin/0.8.6/jacoco-maven-plugin-0.8.6.pom. Return code is: 501, ReasonPhrase: HTTPS Required. -> [Help 1]

To see the full stack trace of the errors, re-run Maven with the -e switch.
Re-run Maven using the -X switch to enable full debug logging.

For more information about the errors and possible solutions, please read the following articles:
[Help 1] http://cwiki.apache.org/confluence/display/MAVEN/PluginResolutionException

ブラウザから直接 http://repo.maven.apache.org/maven2/ にアクセスしてみたところ・・・

f:id:no14141:20201225174223j:plain

「501 HTTPS Required」と返ってきましたので、どこかのタイミングでHTTPS強制になったのですね。

その情報をキャッチアップできていなかったので、「情弱」な自分を恥じてしまいそう。。。

support.sonatype.com

上記のサイトを拝見したところ、2020年の1月にルールが変更になった様子。

原因がわかれば、ルールに従うのみですね。

解決方法

私のmavenは、「C:\apache-maven-3.2.2」に設置していましたので、その中にある設定ファイル(conf\settings.xml)にセントラル向けのURLを追記しました。

mirrorsタグ内に書きを追記します。 (なお、IDEを再起動しなくても、追記し上書き保存しただけで、エラーが発生しなくなりました)

  <mirrors>
    <mirror>
          <id>mirror1</id>
          <mirrorOf>central</mirrorOf>
          <name>mirror1</name>
          <url>https://repo1.maven.org/maven2/</url>
    </mirror>
  </mirrors>

まとめ

以上で問題解決できました。

課題は次の2つ。

(1) いつまでも古い道具を使っていてはいけないよ
(2) 今使っている道具の情報収集を怠ってはいけないよ

という気づきを得ましたが、まずは最新版のApache NetBeans12で、対象のプロジェクトがコントロールできるかどうかを検証してみます。

前進あるのみですね!

iPhone 7 が永遠に圏外!?

妻のスマホの話です。。。

先週末のことでした。「突然、iPhone7が圏外になってしまった」と相談を受けました(キャリアはLINEモバイルWifiでのアクセスは問題なし)。

最初は、電源の再起動で復活したのですが、数時間後、再発・・・。

どうやら、iOS14.2にアップデートしてからこの現象が起きている様子。

さて、本格的に調査開始です。

最近、職場では、PCなどの端末にとことん向き合う機会がほとんど無くなってしまいましたが、本来は過剰なまでに問題解決に向き合う男なんです(誰も聞いてないって?)。

検索を開始して数秒で、次のサイトにたどりつきました!

support.apple.com

原因がわかれば、後はプロに任せるしかありません。

幸いにして、購入から4年以内だったので、無償で機種交換をしてもらえそうです。

Apple正規サービスプロバイダの「カメラのキタムラ 福島店」さんを指名し、持ち込み修理をWeb予約しました。

aspblog.kitamura.jp

修理対応

で、今週の水曜日です。

予定時間通りに受付にiPhone7を提出すると、想定通り、新品と無償交換となったわけです。

この結果により、俺の「iPhone12 mini」を購入する計画が無くなりました。
修理代金がかかるようならば、俺のスマホを妻に渡して、俺は新品を購入する予定だったので・・・。

まあでも、どうしても欲しいわけではなかったので、新品になるのであれば家計にも優しいのでOKとしましょう!!(iPhone13を待つとします。もしくはOPPOか?)

2,000円の画面フィルムを購入し、それを新iPhone7に貼ってもらってお店を後にしました。

スマホの復旧作業

こっからが本格的に俺の出番です。

事前に「iクラウド」にバックアップが取れていたのは確認できていたので、復旧は簡単です。

まずは、家のWikiネットワークに接続と!

しかし、家のWifiMACアドレス制限をしていたので、iPhoneの初期設定時の画面ではまったくMACアドレスが確認できません!!(ユーザーに優しくないぞ!)

仕方なく、WifiMACアドレス制限を一旦オフにして、接続し、バックアップから復元開始。

約10分程度で、復元できました!

めでたし、めでたし・・・

と思ったら、今度は、外出先でインターネットがつながらないという。

そうか、APN設定をする必要があったのね。すっかり忘れていました(基本やろ!)。

電話は普通にできたので、安心しきっていました。機種変更の方は、忘れずにやりましょう。

Wifiネットワークにつないだ状態で、下記のサイトの「APN設定」を行って、無事に終了です。

mobile.line.me

おっと、Wifiに新しい機器のMACアドレスを登録して、再度MACアドレス制限をオンに戻すことを忘れるところでした(汗)。

まとめ

今回、iPhoneの機種交換を2年ぶりに行ったので、時系列にまとめてみました。

以前より手軽に復旧できるようになっていて、感動しました。

以前苦労した「google authenticator」の移行もアプリで簡単にできるようになったようですし、日々進化していて嬉しい限りです。

k-tai.watch.impress.co.jp

最後に一言。定番かもしれませんが、「バックアップがきちんと取れているか、たまに確認しましょう!!」

support.apple.com

アンガーマネジメントは「try~catch文」でうまくいく

タイトルに「うまくいく」と書きましたが、これは願望です。

正直、まだまだマネジメントできていません。

毎日、生きていると、思うようになることばかりでは無いはずです。
コントロールできることならいいのですが、人間関係なら必ず「相手」があることですし、天気であれば自然が相手、当然ながらコントロール不可能なわけです。

いいことも、悪いことも、すべて受入ていくしかないのですが、どうしても人間ですので、イラっとしてしまったり、 同僚や家族に愛の無い言葉を吐いてしまったりしてしまいます。

あー、本当に情けない。

そこで、苦肉の策ではありますが、Javaなどのプログラム言語にある「例外処理」をアンガーマネジメントで使えないかと考えました。

www.sejuku.net

そもそも例外処理とは、「実行停止してはいけないプログラムに何らかのエラーが発生して落ちた途中で問題が発生した場合、その後の処理を自分でコントロールしたい」のような状態を指します。この例外処理を使うことで、エラーが発生して落ちてしまう場合や問題が発生した場合の処理を自分でコントロールできるようになります。(Samurai Blogより引用)

普段の生活の中で発生する例外のひとつは、「怒りを感じたとき」だと思います。

次のコードの通り行動できると最高ですね!

try {
    出来事();

} catch (AngerException ex) {
    怒りを感じたときに取る行動(6秒待つ、深呼吸する、その場を離れるなど);

} finally {
    笑顔();
}

書籍『はじめてのアンガーマネジメント実践ブック』によれば、下記のように書かれていました。

  • 目指すのは、怒るべきことには怒り、どうでもいいことには怒らないようになる状態。

  • アンガーマネジメントとは、 怒らなくなることが目的ではなく、怒る必要のあることは上手に怒れるようになる一方で、怒る必要のないことは怒らなくてすむようになることです。

まさに、「AngerException(実際には存在しません)」をキャッチしたときに、どう行動するか?だと思うんです。

その時にはどんな行動を取ればいいのか? 一般的には「6 秒待つ」というのが有名ですよね。

個人的には、まだ具体的には取るべき行動は確立されていないのですが、いろいろと試しながら、腑に落ちる行動を見つけていければと思っています。

そして最後に「finally」です。

正常時でも例外時でも、最後は必ず実行される部分です。

それは、すなわち「笑顔」というアクションでしょう。

どうでしょうか。

普段の生活での行動パターンも、こうしてコード化することで、より具体的に実行しやすくなるのではないでしょうか!?

すべてが同じパターンで物事が進むわけではございませんが、アンガーマネジメントを「try~catch文」で考えてみたら、何とかなりそうと思ったので、このような記事を書かせていただきました。

「If-Thenプランニング」の考え方に通じるものがありますね。

とにかくまぁ、肩の力を抜いて気楽やっていきましょうか。