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

## はじめに
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

#### シーケンス管理テーブル
```sql:sequence_mng
CREATE TABLE sequence_mng (
	colname VARCHAR(40) NOT NULL,
	nextvalue BIGINT NOT NULL,
	PRIMARY KEY colname
);
```
#### シーケンス付与対象テーブル
```sql: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` でいけそうな気がします。 
こんな風に

```java: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を作ります。

```java: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も下記のように書き換えます。

```java: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 対応というのは他に見つけられなかったので、誰かの役に立てばと思いエントリします。でわ。