LoginSignup
17
16

More than 5 years have passed since last update.

JPAで参照先のEntityが複合主キーの場合(はまり)

Last updated at Posted at 2014-09-02

参画しているプロジェクトでMyBatisからJPAへの移行が予定されているので、(個人的に)JPAの勉強中です。
今回は、しばらく調査して諦めた話です。。

対象のテーブルは、以下のようなレイアウトで、マスタが有効開始日、有効終了日を持ち世代管理されています。

common-app-ER.png

ユーザの本日付けの部門名、ロール名を取得したい場合、SQLだとこんな感じ。

join.sql
SELECT 
    U.USER_ID,
    D.DEPT_NM,
    R.ROLE_NM
FROM
   M_USER U
   LEFT OUTER JOIN 
    M_DEPT D ON U.DEPT_ID = D.DEPT_ID
   LEFT OUTER JOIN
   M_ROLE R ON U.ROLE_ID = R.ROLE_ID
WHERE
   TO_CHAR(SYSDATE,'yyyymmdd') BETWEEN U.START_DATE AND U.END_DATE
   AND
   TO_CHAR(SYSDATE,'yyyymmdd') BETWEEN D.START_DATE AND D.END_DATE
   AND
   TO_CHAR(SYSDATE,'yyyymmdd') BETWEEN R.START_DATE AND R.END_DATE

JPAは「単独カラムだけで主キーを構成することをお勧め」とあるので、移行するなら素直にサロゲートキーを使用したいところですが、他システムのマスタなど、こちらが勝手に変更できない場合もあります。

なので、上記SQLと同じようなことができないか?と試したのが以下。
EclipseLinkを使用しています。

MUserPK.java
@Embeddable
public class MUserPK implements Serializable {
    @Basic(optional = false)
    @Column(name = "USER_ID")
    private String userId;
    @Basic(optional = false)
    @Column(name = "START_DATE")
    private String startDate;

    public MUserPK() {
    }

    public MUserPK(String userId, String startDate) {
        this.userId = userId;
        this.startDate = startDate;
    }

//以下略
}


@JoinColumnでM_DEPTとDEPT_IDで結合。
DEPT_IDだけでは複数カラムになるので@ManyToOneを指定(すればうまくいくと思ってました)

MUser.java
@Entity
@Table(name = "M_USER")
public class MUser implements Serializable {

    private static final long serialVersionUID = 1L;
    @EmbeddedId
    protected MUserPK mUserPK;
    @Column(name = "USERNM_SEI")
    private String usernmSei;
    @Column(name = "USERNM_MEI")
    private String usernmMei;
    @Column(name = "PASSWORD")
    private String password;
    @ManyToOne
    @JoinColumns( {
        @JoinColumn(name = "DEPT_ID", referencedColumnName = "DEPT_ID", insertable = false, updatable = false)
    })
    private MDept deptId;

//以下略
}
MDeptPK.java
@Embeddable
public class MDeptPK implements Serializable {
    @Basic(optional = false)
    @Column(name = "DEPT_ID")
    private String deptId;
    @Basic(optional = false)
    @Column(name = "START_DATE")
    private String startDate;

//以下略
}

MDept.java
@Entity
@Table(name = "M_DEPT")
public class MDept implements Serializable {
    private static final long serialVersionUID = 1L;
    @EmbeddedId
    protected MDeptPK mDeptPK;
    @Column(name = "DEPT_NM")
    private String deptNm;
    @Column(name = "P_DEPT_ID")
    private String pDeptId;
    @Column(name = "END_DATE")
    private String endDate;
    @Column(name = "VERSION_NO")
    private Long versionNo;
//以下略
}

上記を実行すると以下のエラーが。。
複合主キーの場合、結合カラムをそれぞれ指定しろとな。
言われてみればそうですね。。

エラー
Internal Exception: Exception [EclipseLink-7220] (Eclipse Persistence Services - 2.5.1.v20130918-f2b9fc5): org.eclipse.persistence.exceptions.ValidationException
Exception Description: The @JoinColumns on the annotated element [field deptId] from the entity class [class testjpa.MUser] is incomplete. 
When the source entity class uses a composite primary key, 
a @JoinColumn must be specified for each join column using the @JoinColumns. 
Both the name and the referencedColumnName elements must be specified in each such @JoinColumn.

デカルト積でいいなら、以下の書き方でリレーションがないEntity同士でもJOINできるようですが、

2014/09/04修正
where句で結合指定すれば、リレーションがないEntityでもINNER JOINはできるのですね。
そしてコンストラクタ式で検索結果を任意のクラスに格納。
既存のテーブルレイアウト縛りなら、もうこれで逃げるしかないんじゃないかなあ。
LEFT OUTERしたければダミーコード入れちゃうとか。。

jpql

    EntityManagerFactory emf = Persistence.createEntityManagerFactory("TestJPAPU");
    EntityManager em = emf.createEntityManager();

    String sql = "SELECT  new testjpa.JoinDto(" +
            "    U.mUserPK.userId, " +
            "    D.deptNm, " +
            "    R.roleNm ) " +
            "FROM " +
            "   MUser U, " +
            "   MDept D, " +
            "   MRole R " +
            "WHERE " +
            "   U.deptId = D.mDeptPK.deptId " +
            "   AND " +
            "   U.roleId = R.mRolePK.roleId " +
            "   AND " +
            "   '20140904' BETWEEN U.mUserPK.startDate AND U.endDate " +
            "   AND " +
            "   '20140904' BETWEEN D.mDeptPK.startDate AND D.endDate " +
            "   AND " +
            "   '20140904' BETWEEN R.mRolePK.startDate AND R.endDate ";

        List<JoinDto> result = em.createQuery(sql, JoinDto.class).getResultList();            
17
16
3

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
17
16