はじめに
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
シーケンス管理テーブル
CREATE TABLE sequence_mng (
colname VARCHAR(40) NOT NULL,
nextvalue BIGINT NOT NULL,
PRIMARY KEY colname
);
シーケンス付与対象テーブル
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
でいけそうな気がします。
こんな風に
@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を作ります。
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も下記のように書き換えます。
@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 対応というのは他に見つけられなかったので、誰かの役に立てばと思いエントリします。でわ。