Top > MyPage
 

hibernateアノテーション

はじめに

HibernateのHibernate Annotationsの使い方について、実際に簡単なテーブルを用意して確認したのでまとめる。

今回は、検証のためのアプリをmavenを用いて作ったが、mavenのプロジェクトの作り方については割愛する。

mavenを使うと、必要な(依存する)ライブラリも導入されるので、Hibernate以外の必要なライブラリについても省略する。

なお、検証にはこの時点のhibernateの最新バージョン(3.3.1.GA)をもちいた。

この検証を行うにあたって、

HIBERNATE Annotations リファレンスガイド

HIBERNATE Annotations リファレンスガイド 日本語訳

Kishida's SITE 〜 Java入門講座 Hibernate Annotations

を参考にした。

セッションファクトリ取得

今までは

   SessionFactory sessionFactory = 
     
     new Configuration().configure("hibernate.cfg.xml").buildSessionFactory();

であったが、アノテーションを使う場合は次のようになる。

   SessionFactory sessionFactory = 
     
     new AnnotationConfiguration().configure("hibernate.cfg.xml").buildSessionFactory();

Configurationではなく、AnnotationConfigurationを使っている点に注意。

Springとの連携方法は、まだ、調べていない。

双方向一対一

テーブル構成

以下に、実際に使用したDBのテーブル構成のER図を示す。hibernate外部キーの双方向一対一関連と同じものを使う。

ER図

図の様に、今回はSTAFFとSTAFF_INFOを用意した。

STAFFとSTAFF_INFOのプライマリキーは、それぞれとである。どちらもシーケンスで生成する。そして、STAFF_INFOは外部キーとしてSTAFF_IDを持つ。

DTO構成

DTOは、STAFFテーブルとSTAFF_INFOテーブルのそれぞれに対応するStaffとStaffInfoを作成した。

それぞれのクラスの持つ変数は次のようにした。(メソッドは省略)お互いが、相手のクラスの変数をもつことがわかる。

public class Staff implements Serializable {

    private Integer staffId;

    private String staffname;

    private String password;
    
    private StaffInfo staffInfo;

}


public class StaffInfo implements Serializable {

    private Integer id;

    private Integer age;

    private Integer sex;
    
    private Staff staff;


}

クラスレベル アノテーション

それぞれのクラスレベルのアノテーションは次のようになる。

まずはStaff

@Entity
@Table(name="STAFF")
public class Staff implements Serializable {
    //中略
}

そして、StaffInfo

@Entity
@Table(name="STAFF_INFO")
public class StaffInfo implements Serializable {
    //中略
}

Entity、Tableともに必須なので、忘れないように。

単純なプロパティのアノテーション

単純なので、例をひとつだけ

    private String staffname;

    @Column(name="STAFFNAME")
    public String getStaffname() {
        return staffname;
    }

    public void setStaffname(String staffname) {
        this.staffname = staffname;
    }

ゲッターにColumnアノテーション。name属性でカラム名。

他、lengthなどの属性があるが、リファレンスガイドを参照してほしい。

IDのアノテーション

Staffの場合

    private Integer staffId;

    @Id @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="staffseq")
    @GenericGenerator(name="staffseq",strategy = "sequence",
      parameters = {
          @Parameter(name="sequence", value="STAFF_STAFF_ID_SEQ")
      }
    )
    @Column(name="STAFF_ID")
    public Integer getStaffId() {
        return staffId;
    }

プライマリキーSTAFF_IDに対応するプロパティstaffIdのゲッタにIdを宣言

Columnで対応するカラム名。

GenericGeneratorのparametersでシーケンスを指定。

GeneratedValueのgeneratorで宣言したGenericGeneratorを指定。

StaffInfoは次のようになる。

    private Integer id;
    
    @Id @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="staffinfoseq")
    @GenericGenerator(name="staffinfoseq",strategy = "sequence",
      parameters = {
          @Parameter(name="sequence", value="STAFF_INFO_ID_SEQ")
      }
    )
    @Column(name="ID")
    public Integer getId() {
        return id;
    }

GeneratedValueアノテーションでstrategy=GenerationType.SEQUENCEを指定して、generatorでDBのシーケンス名を指定すればよいかと思ったが、それでは駄目だった。きちんとIDジェネレイターを設定してやらないといけないようだ。

one-to-one

StaffクラスのstaffInfoのゲッタのところで宣言

    private StaffInfo staffInfo;
    
    @OneToOne(cascade = CascadeType.ALL, mappedBy = "staff")
    public StaffInfo getStaffInfo() {
        return staffInfo;
    }
    

CascadeTypeはALLの他、REMOVE、MERGEなどがあるが、all-delete-orphanはなかった。どうやるのだろうか。とおもっていたら、実はorg.hibernate.annotations.Cascadeというのがあることがわかった。EJBの方ではなく、こちらを使うようにしたらよいようだ。

mappedByで指定しているのは、反対側のクラスStaffInfo内のStaffの変数名である。

many-to-one

StaffInfoクラスのStaffのゲッタのところで宣言

    private Staff staff;

    @ManyToOne( targetEntity=Staff.class )
    @JoinColumn(name="STAFF_ID")
    public Staff getStaff() {
        return staff;
    }

ManyToOneのtargetEntityで対象となるクラスを指定。カスケードを設定したい場合は、ManyToOneの属性で指定する。このときはカスケードなし。

JoinColumnは該当するテーブル(このときはSTAFF_INFOテーブル)内の、targetEntityで指定したエンティティのIDの値を保持する外部キーのカラム名を指定。

プログラムソース

前のxDoclet用のコメントも残している。

Staff.java

package com.chikkun.common.login.db;

import java.io.Serializable;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;

import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;

/**
 * hibernate テスト用 STAFFのDTO
 * 
 * @author meer
 * @hibernate.class table="STAFF"
 */
@Entity
@Table(name="STAFF")
public class Staff implements Serializable {
    
    
    
    /**
     * serialVersionUID 
     * 
     */
    private static final long serialVersionUID = -6709649841292134396L;

    private Integer staffId;

    private String staffname;

    private String password;
    
    private StaffInfo staffInfo;

    /**
     * @return the staffId
     * @hibernate.id name="staffId" column="STAFF_ID" type="java.lang.Integer"
     *               unsaved-value="null" generator-class="sequence"
     * @hibernate.generator-param name="sequence" value="STAFF_STAFF_ID_SEQ"
     */
    @Id @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="staffseq")
    @GenericGenerator(name="staffseq",strategy = "sequence",
      parameters = {
          @Parameter(name="sequence", value="STAFF_STAFF_ID_SEQ")
      }
    )
    @Column(name="STAFF_ID")
    public Integer getStaffId() {
        return staffId;
    }

    /**
     * @param staffId the staffId to set
     */
    public void setStaffId(Integer staffId) {
        this.staffId = staffId;
    }

    /**
     * @return the staffname
     * @hibernate.property column="STAFFNAME"
     */
    @Column(name="STAFFNAME")
    public String getStaffname() {
        return staffname;
    }

    /**
     * @param staffname the staffname to set
     */
    public void setStaffname(String staffname) {
        this.staffname = staffname;
    }

    /**
     * @return the password
     * @hibernate.property column="PASSWORD"
     */
    @Column(name="PASSWORD")
    public String getPassword() {
        return password;
    }

    /**
     * @param password the password to set
     */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * @return the staffInfo
     * @hibernate.one-to-one class="com.chikkun.common.login.db.StaffInfo"  cascade="all"
     */
    @OneToOne(mappedBy = "staff")
    @Cascade({org.hibernate.annotations.CascadeType.ALL})
    public StaffInfo getStaffInfo() {
        return staffInfo;
    }

    /**
     * @param staffInfo the staffInfo to set
     */
    public void setStaffInfo(StaffInfo staffInfo) {
        this.staffInfo = staffInfo;
    }

}

StaffInfo.java

package com.chikkun.common.login.db;

import java.io.Serializable;

import javax.persistence.*;

import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;



/**
 * hibernate テスト用 STAFF_INFOのDTO
 * 
 * @author meer
 * @hibernate.class table="STAFF_INFO"
 */
@Entity
@Table(name="STAFF_INFO")
public class StaffInfo implements Serializable {
    
    

    /**
     * serialVersionUID
     */
    private static final long serialVersionUID = 7482492136283605534L;

    private Integer id;

    private Integer age;

    private Integer sex;
    
    private Staff staff;

    /**
     * @return the id
     * @hibernate.id name="id" column="ID" type="java.lang.Integer"
     *               unsaved-value="null" generator-class="sequence"
     * @hibernate.generator-param name="sequence" value="STAFF_INFO_ID_SEQ"
     */
    @Id @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="staffinfoseq")
    @GenericGenerator(name="staffinfoseq",strategy = "sequence",
      parameters = {
          @Parameter(name="sequence", value="STAFF_INFO_ID_SEQ")
      }
    )
    @Column(name="ID")
    public Integer getId() {
        return id;
    }

    /**
     * @param id the id to set
     */
    public void setId(Integer id) {
        this.id = id;
    }



    /**
     * @return the age
     * @hibernate.property column="AGE"
     */
    @Column(name="AGE")
    public Integer getAge() {
        return age;
    }

    /**
     * @param age the age to set
     */
    public void setAge(Integer age) {
        this.age = age;
    }

    /**
     * @return the sex
     * @hibernate.property column="SEX"
     */
    @Column(name="SEX")
    public Integer getSex() {
        return sex;
    }

    /**
     * @param sex the sex to set
     */
    public void setSex(Integer sex) {
        this.sex = sex;
    }

    /**
     * @return the staff
     * @hibernate.many-to-one column="STAFF_ID" class="com.chikkun.common.login.db.Staff" unique="true"
     */
    @ManyToOne( targetEntity=Staff.class )
    @JoinColumn(name="STAFF_ID")
    public Staff getStaff() {
        return staff;
    }

    /**
     * @param staff the staff to set
     */
    public void setStaff(Staff staff) {
        this.staff = staff;
    }

    

}

双方向多対多

テーブル構成、DTO構成

hibernateの双方向many-to-manyについてとおなじものをつかうので、そちらを参照のこと。

クラスレベル アノテーション

hibernateの双方向many-to-manyについてのときと同じように、関連テーブルを管理する権限をRoles側に持たせる。

まずはRoles側

@Entity
@Table(name="ROLES")
public class Roles implements Serializable {
        //中略
}

そしてUsers側

@Entity
@Table(name="USERS")
public class Users implements Serializable {
        //中略
}

先ほどの一対一のときと変わらない。

単純なプロパティとIDもone-to-oneとやり方は同じなので省略する。

many-to-manyのアノテーション

hbmでは片側にinverseを付けていた。アノテーションでもそれと同様に片側にしか書かないものがある。

まずはRoles側。

    @ManyToMany(
        targetEntity=com.chikkun.common.login.db.Users.class
    )
    @JoinTable(
        name="USER_ROLE",
        joinColumns={@JoinColumn(name="ROLES_ID")},
        inverseJoinColumns={@JoinColumn(name="USERS_ID")}
    )
    public Set<Users> getUsers() {
        return users;
    }

ManyToManyのtargetEntityで、反対側のエンティティとなるクラスを指定する。ManyToManyの属性でcascadeも設定することが出来るが、前の例ではRoles側からはカスケードしないことにしていたので、今回も設定していない。

JoinTableで関連付けに関する設定をする。nameが関連テーブル名、joinColumnsのJoinColumnのnameがRolesのIDを保持するカラム名、inverseJoinColumnsのJoinColumnのnameが反対側のエンティティのIDを保持するカラム名。このJoinTableアノテーションは関連テーブルを管理する側だけに記述する。

Users側は次のようになる。

    @ManyToMany(
            cascade={CascadeType.ALL},
            mappedBy="users",
            targetEntity=Roles.class
    )
    public Set<Roles> getRoles() {
        return roles;
    }

前の例でUsersからはカスケードするようにしたので、今回もCascadeType.ALLを指定している。が、このようにcascade属性で設定するのではなくCascadeアノテーションを使うべき

targetEntityはRoles側の場合と同じように反対側のエンティティを指定しているわけだが、こちらはパッケージを省略している。省略しなくてはいけないのかは不明。

JoinTableアノテーションが無いかわりに、こちらではManyToManyアノテーションにmappedByがある。ここで指定しているのが反対側のエンティティであるRoles内のUsersのコレクションのプロパティ名である。

プログラムソース

xdoclet用のコメントも残している

Roles.java

package com.chikkun.common.login.db;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.*;


import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;

/**
 * hibernate テスト用 ROLESのDTO
 * @author meer
 * @hibernate.class table="ROLES"
 *
 */
@Entity
@Table(name="ROLES")
public class Roles implements Serializable {

    /**
     * serialVersionUID
     */
    private static final long serialVersionUID = -3784602532689050054L;
    
    
    
    private Integer rolesId;
    private String roleName;
    
    private Set<Users> users;
    
    
    /**
     * @return the rolesId
     * @hibernate.id name="rolesId" column="ROLES_ID" type="java.lang.Integer"
     *               unsaved-value="null" generator-class="sequence"
     * @hibernate.generator-param name="sequence" value="ROLES_ROLES_ID_SEQ"
     */
    @Id @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="rolesseq")
    @GenericGenerator(name="rolesseq",strategy = "sequence",
      parameters = {
          @Parameter(name="sequence", value="ROLES_ROLES_ID_SEQ")
      }
    )
    @Column(name="ROLES_ID")
    public Integer getRolesId() {
        return rolesId;
    }
    /**
     * @param rolesId the rolesId to set
     */
    public void setRolesId(Integer rolesId) {
        this.rolesId = rolesId;
    }
    /**
     * @return the roleName
     * @hibernate.property column="ROLE_NAME"
     */
    @Column(name="ROLE_NAME")
    public String getRoleName() {
        return roleName;
    }
    /**
     * @param roleName the roleName to set
     */
    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }
    
    
    
    /**
     * @return the users
     * @hibernate.set table="USER_ROLE"  lazy="false"
     * @hibernate.key column="ROLES_ID"
     * @hibernate.many-to-many class="com.chikkun.common.login.db.Users"
     *                         column="USERS_ID"
     */
    @ManyToMany(
        targetEntity=com.chikkun.common.login.db.Users.class
    )
    @JoinTable(
        name="USER_ROLE",
        joinColumns={@JoinColumn(name="ROLES_ID")},
        inverseJoinColumns={@JoinColumn(name="USERS_ID")}
    )
    public Set<Users> getUsers() {
        return users;
    }
    /**
     * @param users the users to set
     */
    protected void setUsers(Set<Users> users) {
        this.users = users;
    }
    
    public void addToUsers(Users u){
        Set<Users>usersSet = this.getUsers();
        if(usersSet != null){
            usersSet.add(u);
        }else{
            usersSet = new HashSet<Users>();
            usersSet.add(u);
            this.setUsers(usersSet);
        }
        Set<Roles>roles = u.getRoles();
        if(roles != null){
            roles.add(this);
        }else{
            roles = new HashSet<Roles>();
            roles.add(this);
            u.setRoles(roles);
        }
    }
    
    public void removeFromUsers(Users u){
        Set<Users>usersSet = this.getUsers();
        if(usersSet != null){
            usersSet.remove(u);
        }
        Set<Roles>roles = u.getRoles();
        if(roles != null){
            roles.remove(this);
        }
    }
    
    
    
    /**
     * デフォルトコンストラクタ
     */
    public Roles() {
        
    }
    
    

}

Users.java

package com.chikkun.common.login.db;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import javax.persistence.*;

import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;

/**
 * hibernate テスト用 USERSのDTO
 * 
 * @author meer
 * @hibernate.class table="USERS"
 */
@Entity
@Table(name="USERS")
public class Users implements Serializable {

    /**
     * serialVersionUID
     */
    private static final long serialVersionUID = -6292804071602340401L;

    private Integer usersId;

    private String username;

    private String password;

    private Set<Roles> roles;

    /**
     * @return the usersId
     * @hibernate.id name="usersId" column="USERS_ID" type="java.lang.Integer"
     *               unsaved-value="null" generator-class="sequence"
     * @hibernate.generator-param name="sequence" value="USERS_USERS_ID_SEQ"
     */
    @Id @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="usersseq")
    @GenericGenerator(name="usersseq",strategy = "sequence",
      parameters = {
          @Parameter(name="sequence", value="USERS_USERS_ID_SEQ")
      }
    )
    @Column(name="USERS_ID")
    public Integer getUsersId() {
        return usersId;
    }

    /**
     * @param usersId
     *            the usersId to set
     */
    public void setUsersId(Integer usersId) {
        this.usersId = usersId;
    }

    /**
     * @return the username
     * @hibernate.property column="USERNAME"
     */
    @Column(name="USERNAME")
    public String getUsername() {
        return username;
    }

    /**
     * @param username
     *            the username to set
     */
    public void setUsername(String username) {
        this.username = username;
    }

    /**
     * @return the password
     * @hibernate.property column="PASSWORD"
     */
    @Column(name="PASSWORD")
    public String getPassword() {
        return password;
    }

    /**
     * @param password
     *            the password to set
     */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * @return the roles
     * @hibernate.set table="USER_ROLE" inverse="true" lazy="false" cascade="all"
     * @hibernate.key column="USERS_ID"
     * @hibernate.many-to-many class="com.chikkun.common.login.db.Roles"
     *                         column="ROLES_ID"
     */
    @ManyToMany(
            mappedBy="users",
            targetEntity=Roles.class
    )
    @Cascade({org.hibernate.annotations.CascadeType.ALL})
    public Set<Roles> getRoles() {
        return roles;
    }

    /**
     * @param roles
     *            the roles to set
     */
    protected void setRoles(Set<Roles> roles) {
        this.roles = roles;
    }
    
    
    public void addToRoles(Roles r){
        Set<Roles>roles = this.getRoles();
        if(roles != null){
            roles.add(r);
        }else{
            roles = new HashSet<Roles>();
            roles.add(r);
            this.setRoles(roles);
        }
        Set<Users>users = r.getUsers();
        if(users != null){
            users.add(this);
        }else{
            users = new HashSet<Users>();
            users.add(this);
            r.setUsers(users);
        }
    }
    
    public void removeFromRoles(Roles r){
        Set<Roles>roles = this.getRoles();
        if(roles != null){
            //Usersインスタンス内のRolesのSetから
            //引数のRolesインスタンスと同一のものを除去
            roles.remove(r);
        }
        Set<Users>users = r.getUsers();
        if(users != null){
            //引数のRolesインスタンス内のUsersのSetから
            //このUsersインスタンスと同一のものを除去
            users.remove(this);
        }
    }
    
    

    /**
     * デフォルトコンストラクタ
     */
    public Users() {

    }

}

メモ  アノテーションの属性

Columnアノテーション

    name="columnName";                                (1)
    boolean unique() default false;                   (2)
    boolean nullable() default true;                  (3)
    boolean insertable() default true;                (4)
    boolean updatable() default true;                 (5)
    String columnDefinition() default "";             (6)
    String table() default "";                        (7)
    int length() default 255;                         (8)
    int precision() default 0; // decimal precision   (9)
    int scale() default 0; // decimal scale           (10)

(1) name (オプション):カラムの名前(デフォルトでプロパティ名)

(2) unique (オプション):このカラムに特有の制約をセットするか否か(デフォルトはfalse)

(3) nullable (オプション):このカラムをnull可能としてセット(デフォルトはfalse)

(4) insertable (オプション):このカラムが挿入ステートメントの一部であるか否か(デフォルトはtrue)

(5) updatable (オプション):このカラムが更新ステートメントの一部であるか否か(デフォルトはtrue)

(6) columnDefinition (オプション):この特定のカラムの為にsql DDL部分を書き換える(ポータブルでない)

(7) table (オプション):ターゲットテーブルを定義(デフォルトはプライマリテーブル)

(8) length (オプション):カラムの長さ(デフォルト255)

(9) precision (オプション):カラムの小数点精度(デフォルト0)

(10) scale (オプション):役に立つ場合、カラムの小数点範囲(デフォルト0)

Cascadeアノテーション

    @Cascade(
              {
                  org.hibernate.annotations.CascadeType.ALL,
                  org.hibernate.annotations.CascadeType.DELETE,
                  org.hibernate.annotations.CascadeType.DELETE_ORPHAN,
                  org.hibernate.annotations.CascadeType.EVICT,
                  org.hibernate.annotations.CascadeType.LOCK,
                  org.hibernate.annotations.CascadeType.MERGE,
                  org.hibernate.annotations.CascadeType.PERSIST,
                  org.hibernate.annotations.CascadeType.REFRESH,
                  org.hibernate.annotations.CascadeType.REMOVE,
                  org.hibernate.annotations.CascadeType.SAVE_UPDATE
              }
    )
    

ManyToMany、OneToMany、ManyToOne、OneToOneのfetch属性

fetch属性は

  • FetchType.LAZY
  • FetchType.EAGER

の二つをとりうる。LAZYがデフォルトである。Lazyが以前のlazy=true、EAGERがfalseと考えればよい。