3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Hibernate(JPA) の TableGenerator で通番を生成し、StringのIdに格納する。

Last updated at Posted at 2017-05-25

はじめに

HibernateのEntityを永続化する際、@Idアノテートされているフィールドに@GeneratedValueアノテーションを使用し、かつCHAR型のカラムに設定したい。

ただし採用している業務FWの仕様により、DBMS(SQL Server)のSEQUENCEではなく別途シーケンス通番取得用テーブル(sequence_mng)が存在し、レコード新規作成の際は整合性を保つため sequence_mng を使用しなくてはならないのです。

カラムもBIGINTでいいじゃん!と思うけど、「ゼロパディングで固定桁」信仰みたいなのもあるので仕方ない。

前提条件

実行環境

・JDK1.8
・Hibernate(5.2.10.FINAL)
・SQL Server 2016

シーケンス管理テーブル

sequence_mng
CREATE TABLE sequence_mng (
	colname VARCHAR(40) NOT NULL,
	nextvalue BIGINT NOT NULL,
	PRIMARY KEY colname
);

シーケンス付与対象テーブル

partner
CREATE TABLE m_partner (
	partner_cd CHAR(6) NOT NULL,
	partner_nm NVARCHAR(60) NOT NULL,
        -- OMITTED
        PRIMARY KEY partner_cd
);

こんな感じで、save()などを呼んで永続化させようとしたとき、
・sequence_mngのcolnameが"partner_cd"のレコードを取得
・Partner.class の partnerCdにnextvalueを格納
・nextvalue++
というのをAnnotationベースで動くようにしたい。

TableGenerator試行

そして、このパターンだとGenerationType.TABLE でいけそうな気がします。
こんな風に

Partner.java(Failure)
@Entity
@Table(name = "partner")
public class Partner {

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "SEQ_TBL_PARTNER")
    @TableGenerator(name = "SEQ_TBL_PARTNER", 
      table = "sequence_mng",
      pkColumnName = "colname",
      valueColumnName = "nextvalue",
      pkColumnValue = "partner_cd")
    @Column(name = "partner_cd", unique = true, nullable = false, length = 6)
    private Integer partnerCd;

    @Column(name = "partner_nm", nullable = false)
    private String partnerNm;
// 以下略
}

ところが、@TableGeneratorの実体
org.hibernate.id.enhanced.TableGenerator
実際には'org.hibernate.id.IdentifierGeneratorHelper#getIntegralDataTypeHolder'で
@Idフィールドがlong(Long),BigDecimal以外は許可してくれず
IdentifierGenerationException("Unknown integral data type for ids : StringType())
となり動きません。
前述のとおりカラム属性は変えられないのでまいりました。

カスタマイズ

そこで、TableGeneratorを継承したStringTableGeneratorを作って動くようにしてあげます。

StringTableGenerator.java

TableGeneratorを継承したStringTableGeneratorを作ります。

StringTableGenerator.java
package sample.hibernate.id;

import org.hibernate.MappingException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentifierGeneratorHelper;
import org.hibernate.id.IntegralDataTypeHolder;
import org.hibernate.id.enhanced.TableGenerator;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.BigDecimalType;
import org.hibernate.type.Type;

import java.io.Serializable;
import java.util.Properties;

/**
 * TableGenerator (文字列カラム対応)
 */
public class StringTableGenerator extends TableGenerator {

  @Override
  public void configure(Type type, Properties params, ServiceRegistry serviceRegistry)
    throws MappingException {
    super.configure(new BigDecimalType() , params, serviceRegistry);
  }

  @Override
  public Serializable generate(SharedSessionContractImplementor session, Object obj) {
    return super.generate(session, obj).toString();
  }

}

configure()メソッド

第一引数にはStringTypeがやってくるので、親クラスに渡す前にBigDecimalType()にすり替えます(^^)

generate()メソッド

親クラスから帰ってきた値はBigDecimalなので、返す前にStringにすり替えます。
以上です。

アノテーション設定

そして、実際にシーケンスを生成するEntityも下記のように書き換えます。

Partner.java
@Entity
@Table(name = "partner")
public class Partner implements Serializable {
  @Id
  @GeneratedValue(strategy = GenerationType.TABLE, generator = "CUSTOM_SEQ")
  @GenericGenerator(strategy = "sample.hibernate.id.StringTableGenerator", 
    name = "CUSTOM_SEQ", parameters = {
    @Parameter(name = "segment_value", value = "partner_cd"),
    @Parameter(name = "table_name", value = "sequence_mng"),
    @Parameter(name = "segment_column_name", value = "colname"),
    @Parameter(name = "value_column_name", value = "nextvalue")
  })
  @Column(name = "partner_cd", unique = true, nullable = false, length = 6)
  private String partnerCd;

  @Column(name = "partner_nm", nullable = false)
  private String partnerNm;
 
(以下略)

クラスが固定されている@TableGeneratorから@GenericGeneratorにして、先ほど作ったStringTableGeneratorを使用するように指定します。
パラメータも名前が変わるので、注意です。

上記の変更で、Stringのフィールドにも連番を生成して設定されるようになります。

##最後に
SequenceGeneratorの場合はこちら(ほぼ元ネタ)
https://stackoverflow.com/questions/12517421/how-to-map-a-string-to-db-sequence-in-hibernate

TableGeneratorのわかりやすい説明
http://d.hatena.ne.jp/taedium/20070627/p1

こちらもわかりやすいTableGenerator
http://qiita.com/KevinFQ/items/a6d92ec7b32911e50ffe

余談

Hibernate はちょこちょことパッケージ構成変わったりクラス名変わったりするので、
世に出ている資料はすぐに陳腐化してしまいます。

おかしいなーうごかないなーと思ったらソース見た方が早いです。
今回だと、@Parameterのname属性はそうしました。

TableGenerator かつ StringType 対応というのは他に見つけられなかったので、誰かの役に立てばと思いエントリします。でわ。

3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?