Nablarchの日付管理機能を使用する

TISでは、豊富な基幹システム構築経験から得られたナレッジを集約したJavaアプリケーション開発/実行基盤として Nablrch を提供しています。 SpringからNablarchの機能を利用することで、Springに不足している機能を補うことができます。

この例では、Nablarchの機能である 日付管理 を使用して、システム日時と業務日付を管理する実装方法を説明します。

日付管理機能では、データベース上で区分ごとに業務日付を管理することができます。また、システム日時の取得では、テスト実行時などに任意の日時に切り替えることができます。

サンプルコードの動作確認環境については、 動作確認環境と依存ライブラリについて を参照してください。

サンプル全体は nablarch-date を参照してください。

Nablarchを使用するための準備

pom.xml

Nablarchのモジュールバージョンを管理するために、dependencyManagementにnablarch-bomを追加します。

  <dependency>
    <groupId>com.nablarch.profile</groupId>
    <artifactId>nablarch-bom</artifactId>
    <version>6</version>
    <type>pom</type>
    <scope>import</scope>
  </dependency>

日付管理機能を使うため、依存ライブラリにnablarch-coreとnablarch-common-code-jdbcを追加します。

    <!-- Nablarch:日付管理 -->
    <dependency>
        <groupId>com.nablarch.framework</groupId>
        <artifactId>nablarch-core</artifactId>
    </dependency>
    <dependency>
        <groupId>com.nablarch.framework</groupId>
        <artifactId>nablarch-common-code-jdbc</artifactId>
    </dependency>

Tip

Nablarchでは独自のログ出力機能を提供していますが、別のログライブラリを使用するためのアダプタが提供されています。この例では、SLF4Jを使用するためのアダプタである nablarch-slf4j-adaptor を使用しています。 詳細は logアダプタ を参照してください。

業務日付管理で使用するテーブルの作成

業務日付管理はデータベースで行うため、テーブルを用意します。

この例では、Flywayによるマイグレーションで起動時にデータベースを構築しているため、マイグレーション用のSQLファイルを作成します。 区分ごとに異なる業務日付を取得できるように、区分を2つ用意しています。

テーブル構造の詳細については 業務日付管理機能を使うための設定を行う を参照してください。

Tip

テーブル名やカラム名はあくまで一例であり、スキーマ情報を保持するBeanを定義する際に任意のカラム名を設定することができます。

V001__create_code_table.sql
create table business_date
(
    segment_id varchar(2) not null,
    biz_date   varchar(8) not null,
    primary key (segment_id)
);

insert into business_date (segment_id, biz_date) values ('00', '20220101');
insert into business_date (segment_id, biz_date) values ('01', '20220102');

データアクセス機能を動作させるための設定

Nablarchの業務日付管理機能は、NablarchのDIコンテナ機能とデータアクセス機能を使用して動作するようになっています。Nablarchにはそれらを動作させるための設定が組み込まれていますが、Springからそのまま使用することは出来ないため、Nablarchに組み込まれている設定と同等の設定をSpring側で行います。

データアクセス機能を使用できるようにするため、Springのトランザクション管理機能と統合させるためのクラスを作成します。

UnmanagedSimpleDbTransactionManager
/**
 * Springのトランザクション管理機能と統合するSimpleDbTransactionManagerサブクラス。
 */
public class UnmanagedSimpleDbTransactionManager extends SimpleDbTransactionManager {

    /**
     * トランザクション名
     */
    private static final String DB_TRANSACTION_NAME = TransactionContext.DEFAULT_TRANSACTION_CONTEXT_KEY;

    /**
     * ConnectionFactory
     */
    private final ConnectionFactory connectionFactory;

    /**
     * Springのトランザクションマネージャ
     */
    private final PlatformTransactionManager transactionManager;

    /**
     * トランザクションの状態を保持するスレッドローカル
     */
    private final ThreadLocal<TransactionStatus> transactionStatusHolder = new ThreadLocal<>();

    /**
     * コンストラクタ。
     * 
     * @param connectionFactory ConnectionFactory
     * @param transactionManager Springのトランザクションマネージャ
     */
    public UnmanagedSimpleDbTransactionManager(ConnectionFactory connectionFactory,
            PlatformTransactionManager transactionManager) {
        this.connectionFactory = connectionFactory;
        this.transactionManager = transactionManager;
    }

    @Override
    public void beginTransaction() {
        // トランザクションを開始、または既存トランザクションへ参加し、状態をスレッドローカルへ保存する
        TransactionStatus status = transactionManager.getTransaction(null);
        transactionStatusHolder.set(status);

        // Nablarchで使用するためAppDbConnectionを作成してDbConnectionContextへセット
        AppDbConnection dbConnection = connectionFactory.getConnection(DB_TRANSACTION_NAME);
        DbConnectionContext.setConnection(DB_TRANSACTION_NAME, dbConnection);
    }

    @Override
    public void commitTransaction() {
        // トランザクションを開始した場合はコミットを行う。
        // 既存トランザクションへ参加した場合は何も行わない。
        TransactionStatus status = transactionStatusHolder.get();
        if (status != null && status.isNewTransaction()) {
            transactionManager.commit(status);
        }
    }

    @Override
    public void rollbackTransaction() {
        // トランザクションを開始した場合はロールバックを行う。
        // 既存トランザクションへ参加した場合は何も行わない。
        TransactionStatus status = transactionStatusHolder.get();
        if (status != null && status.isNewTransaction()) {
            transactionManager.rollback(status);
        }
    }

    @Override
    public void endTransaction() {
        transactionStatusHolder.remove();
        DbConnectionContext.removeConnection(DB_TRANSACTION_NAME);
    }
}

作成したトランザクション管理のクラスや、その他に必要となるクラスをBean定義します。

DbAccessConfiguration
/**
 * Nablarchのデータアクセス機能を使用するための設定。
 */
public class DbAccessConfiguration {

    /**
     * SimpleDbTransactionManagerを構築する。
     * 
     * @param connectionFactory ConnectionFactory
     * @param transactionManager PlatformTransactionManager
     * @return 構築されたインスタンス
     */
    @Bean
    public SimpleDbTransactionManager dbManager(ConnectionFactory connectionFactory,
                                                PlatformTransactionManager transactionManager) {
        return new UnmanagedSimpleDbTransactionManager(connectionFactory, transactionManager);
    }

    /**
     * BasicSqlStatementExceptionFactoryを構築する。
     * 
     * @return 構築されたインスタンス
     */
    @Bean
    public BasicSqlStatementExceptionFactory sqlStatementExceptionFactory() {
        return new BasicSqlStatementExceptionFactory();
    }

    /**
     * BasicSqlParameterParserFactoryを構築する。
     * 
     * @param sqlConvertors SqlConvertorのリスト
     * @return 構築されたインスタンス
     */
    @Bean
    public BasicSqlParameterParserFactory sqlParameterParserFactory(List<SqlConvertor> sqlConvertors) {
        BasicSqlParameterParserFactory sqlParameterParserFactory = new BasicSqlParameterParserFactory();
        sqlParameterParserFactory.setSqlConvertors(sqlConvertors);
        return sqlParameterParserFactory;
    }

    /**
     * BasicStatementFactoryを構築する。
     *
     * @param sqlStatementExceptionFactory SqlStatementExceptionFactory
     * @param sqlParameterParserFactory SqlParameterParserFactory
     * @return 構築されたインスタンス
     */
    @Bean
    @ConfigurationProperties(prefix = "nablarch.db.statement-factory")
    public BasicStatementFactory statementFactory(SqlStatementExceptionFactory sqlStatementExceptionFactory,
                                                  SqlParameterParserFactory sqlParameterParserFactory) {
        BasicStatementFactory statementFactory = new BasicStatementFactory();
        statementFactory.setSqlStatementExceptionFactory(sqlStatementExceptionFactory);
        statementFactory.setUpdatePreHookObjectHandlerList(List.of());
        statementFactory.setSqlParameterParserFactory(sqlParameterParserFactory);
        return statementFactory;
    }

    /**
     * BasicDbAccessExceptionFactoryを構築する。
     * 
     * @return 構築されたインスタンス
     */
    @Bean
    public BasicDbAccessExceptionFactory dbAccessExceptionFactory() {
        return new BasicDbAccessExceptionFactory();
    }

    /**
     * H2のDialectを構築する。
     *
     * @return 構築されたインスタンス
     */
    @Bean
    public H2Dialect dialect() {
        return new H2Dialect();
    }

    /**
     * BasicDbConnectionFactoryForDataSourceを構築する。
     *
     * @param dataSource データソース
     * @param statementFactory StatementFactory
     * @param dbAccessExceptionFactory DbAccessExceptionFactory
     * @param dialect Dialect
     * @return 構築されたインスタンス
     */
    @Bean
    @ConfigurationProperties(prefix = "nablarch.db.connection-factory")
    public BasicDbConnectionFactoryForDataSource connectionFactory(
            DataSource dataSource,
            StatementFactory statementFactory,
            DbAccessExceptionFactory dbAccessExceptionFactory,
            Dialect dialect) {
        BasicDbConnectionFactoryForDataSource connectionFactory = new BasicDbConnectionFactoryForDataSource();
        connectionFactory.setDataSource(new TransactionAwareDataSourceProxy(dataSource));
        connectionFactory.setStatementFactory(statementFactory);
        connectionFactory.setDbAccessExceptionFactory(dbAccessExceptionFactory);
        connectionFactory.setDialect(dialect);
        return connectionFactory;
    }
}

Tip

Nablarchのデータアクセス機能では、データベースによるSQLの違いを吸収するためにダイアレクトを設定する必要があります。 この例ではデータベースにH2を使用するため、H2用のダイアレクトを設定しています。

業務日付管理機能を動作させるための設定

業務日付管理では、業務日付をシステムプロパティや環境変数で上書きするための機能が提供されていますが、Springからそのまま使用することができません。 そのため、Spring側で同等のプロパティを用意し、プロパティ値をNablarchのDIコンテナであるシステムリポジトリに登録することで上書きできるように設定します。

プロパティ値をバインドするためのBeanを定義します。

FixedBusinessDateProperties
@Component
@ConfigurationProperties(prefix = "business-date")
public class FixedBusinessDateProperties {

    private Map<String, String> fixed = new HashMap<>();

    public Map<String, String> getFixed() {
        return Collections.unmodifiableMap(fixed);
    }

    public void setFixed(Map<String, String> fixed) {
        this.fixed = fixed;
    }
}

スキーマ情報等の設定を保持する BasicBusinessDateProvider のBeanを定義します。 スキーマ情報の他、デフォルト区分やキャッシュ使用有無など、使用する環境に合わせて設定します。 また、BasicBusinessDateProviderには初期化用のメソッドがあるため、Bean初期化時に呼び出すように設定しておきます。

BusinessDateConfiguration
    /**
     * BasicBusinessDateProviderを構築する。
     * 
     * @param transactionManager SimpleDbTransactionManager
     * @return 構築されたインスタンス
     */
    @Bean(initMethod = "initialize")
    @ConfigurationProperties(prefix = "nablarch.business-date")
    public BasicBusinessDateProvider businessDateProvider(SimpleDbTransactionManager transactionManager) {
        BasicBusinessDateProvider businessDateProvider = new BasicBusinessDateProvider();
        businessDateProvider.setTableName("business_date");
        businessDateProvider.setSegmentColumnName("segment_id");
        businessDateProvider.setDateColumnName("biz_date");
        businessDateProvider.setDefaultSegment("00");
        businessDateProvider.setDbTransactionManager(transactionManager);
        // サンプルでは取得前に更新を行うため、キャッシュを無効にする
        businessDateProvider.setCacheEnabled(false);

        return businessDateProvider;
    }

Bean定義したBasicBusinessDateProviderはNablarchの内部で使用するため、NablarchのDIコンテナであるシステムリポジトリに登録します。 また、業務日付を上書きするためのプロパティが設定されている場合、区分ごとの業務日付をシステムリポジトリに登録します。

BusinessDateConfiguration
    /**
     * BusinessDateSystemRepositoryLoaderを構築する。
     * 
     * @param businessDateProvider BusinessDateProvider
     * @param properties 業務日付を上書きするためのProperties
     * @return 構築されたインスタンス
     */
    @Bean
    public BusinessDateSystemRepositoryLoader businessDateSystemRepositoryLoader(
            BusinessDateProvider businessDateProvider, FixedBusinessDateProperties properties) {
        return new BusinessDateSystemRepositoryLoader(businessDateProvider, properties);
    }
BusinessDateSystemRepositoryLoader
/**
 * 業務日付管理機能に必要なインスタンスを{@link SystemRepository}へ登録するクラス。
 */
public class BusinessDateSystemRepositoryLoader implements InitializingBean {

    /**
     * BusinessDateProvider
     */
    private final BusinessDateProvider businessDateProvider;

    /**
     * 業務日付を上書きするためのProperties
     */
    private final FixedBusinessDateProperties properties;

    /**
     * コンストラクタ。
     * 
     * @param businessDateProvider BusinessDateProvider
     * @param properties 業務日付を上書きするためのProperties
     *
     */
    public BusinessDateSystemRepositoryLoader(BusinessDateProvider businessDateProvider,
                                              FixedBusinessDateProperties properties) {
        this.businessDateProvider = businessDateProvider;
        this.properties = properties;
    }

    @Override
    public void afterPropertiesSet() {
        Map<String, Object> objectMap = new HashMap<>();
        objectMap.put("businessDateProvider", businessDateProvider);
        properties.getFixed().forEach((key, value) ->
                objectMap.put(BasicBusinessDateProvider.class.getSimpleName() + "." + key, value));
        SystemRepository.load(() -> objectMap);
    }
}

システム日付管理機能を動作させるための設定

BasicSystemTimeProvider のBeanを定義します。

SystemTimeConfiguration
    /**
     * BasicSystemTimeProviderを構築する。
     * 
     * @return 構築されたインスタンス
     */
    @Bean
    public BasicSystemTimeProvider systemTimeProvider() {
        return new BasicSystemTimeProvider();
    }

Bean定義したBasicSystemTimeProviderはNablarchの内部で使用するため、NablarchのDIコンテナであるシステムリポジトリに登録します。

SystemTimeConfiguration
    /**
     * SystemTimeSystemRepositoryLoaderを構築する。
     * 
     * @param systemTimeProvider SystemTimeProvider
     * @return 構築されたインスタンス
     */
    @Bean
    public SystemTimeSystemRepositoryLoader systemTimeSystemRepositoryLoader(SystemTimeProvider systemTimeProvider) {
        return new SystemTimeSystemRepositoryLoader(systemTimeProvider);
    }
SystemTimeSystemRepositoryLoader
/**
 * システム日時管理機能に必要なインスタンスを{@link SystemRepository}へ登録するクラス。
 */
public class SystemTimeSystemRepositoryLoader implements InitializingBean {

    /**
     * SystemTimeProvider
     */
    private final SystemTimeProvider systemTimeProvider;

    /**
     * コンストラクタ。
     * 
     * @param systemTimeProvider SystemTimeProvider
     */
    public SystemTimeSystemRepositoryLoader(SystemTimeProvider systemTimeProvider) {
        this.systemTimeProvider = systemTimeProvider;
    }

    @Override
    public void afterPropertiesSet() {
        SystemRepository.load(() -> Map.of("systemTimeProvider", systemTimeProvider));
    }
}

システム日付の使用例

システム日付を取得するには、 SystemTimeUtil を使用します。

DateService
    /**
     * システム日付を取得します。
     *
     * @return システム日付(yyyyMMdd形式)
     */
    public String getSystemDate() {
        String systemDate = SystemTimeUtil.getDateString();
        return systemDate;
    }

Tip

テスト等でシステム日付を切り替えたい場合は、SystemTimeProvider を実装したクラスを作成し、Bean定義した BasicSystemTimeProvider を使用しないように差し替えます。

業務日付の使用例

業務日付情報にアクセスするには、Bean定義した BasicBusinessDateProvider を使用します。

デフォルト区分の業務日付は、区分を指定せずに取得することができます。

DateService
    /**
     * デフォルト区分の業務日付を取得します。
     *
     * @return 業務日付(yyyyMMdd形式)
     */
    public String getBusinessDate() {
        String businessDate = businessDateProvider.getDate();
        return businessDate;
    }

区分を指定することで、指定した区分の業務日付を取得することができます。

DateService
    /**
     * 指定された区分の業務日付を取得します。
     *
     * @param segment 区分
     * @return 業務日付(yyyyMMdd形式)
     */
    public String getBusinessDateWithSegment(String segment) {
        String businessDate = businessDateProvider.getDate(segment);
        return businessDate;
    }

業務日付を更新するには、対象の区分と日付を指定します。

DateService
    /**
     * 指定された区分の業務日付を更新します。
     *
     * @param segment 区分
     * @param date 日付
     */
    public void updateBusinessDate(String segment, LocalDate date) {
        // 更新時の日付はyyyyMMdd形式の文字列で指定する
        String dateString = date.format(DateTimeFormatter.ofPattern("uuuuMMdd"));
        businessDateProvider.setDate(segment, dateString);
    }

業務日付を上書きする場合は、business-date.fixed.[区分] プロパティを設定します。 例えば、バッチ実行時に業務区分 00 の業務日付を上書きしたいといった場合、以下のように起動します。

java -jar nablarch-date-0.0.1-SNAPSHOT.jar --businessDate.fixed.00=20220131

Tip

BasicBusinessDateProvider では業務日付を String 型で扱います。 LocalDate 型等で日付を扱いたいような場合には、 BasicBusinessDateProviderをラップして相互変換を実装したクラスを用意して、業務日付へのアクセスにはそのクラスを使用するといった方法があります。