views:

114

answers:

1

I have four entities to map together, "Association", "Account", "Transaction" and "TransactionEvent". The id of Association is a simple integer id. Account and Transaction each have embedded id's consisting of a mapping to an Association and a number.

TransactionEvent should have an embedded id consisting of one Account and one Association. Now, each of those are mapped to an Association, and I want it to be the same Association for one TransactionEvent.

JPA Annotations is used for the Hibernate mapping, but I cannot make this work. I have tried forcing the same column name for the Association key, but Hibernate complains about repeated columns.

Is this possible to solve, or am I not thinking straight?

Here are the annotated classes, but I trimmed away getters/setters and non-id columns, annotations from the javax.persistence namespace:


@Entity
public class Association implements Serializable {
    @Id @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
}

@Embeddable
public class AccountPK implements Serializable {
    @ManyToOne(optional=false)
    private Association association;

    @Column(nullable=false)
    private int number;
}

@Embeddable
public class TransactionPK implements Serializable {
    @ManyToOne
    private Association association;

    @GeneratedValue(strategy=GenerationType.AUTO)
    private long number;
}

@Embeddable
public class AccountEventPK implements Serializable {
    @ManyToOne(optional=false)
    @JoinColumns({
        @JoinColumn(name="association_id", referencedColumnName="association_id"),
        @JoinColumn(name="account_number", referencedColumnName="number")
    })
    private Account account;

    @ManyToOne(optional=false)
    @JoinColumns({
        @JoinColumn(name="association_id", referencedColumnName="association_id"),
        @JoinColumn(name="transaction_number", referencedColumnName="number")
    })
    private Transaction transaction;
}

Actual Account, Transaction and AccountEvent entities are on the form


@Entity
public class Account implements Serializable {
        @EmbeddedId
        private AccountPK id;
}
A: 

I don't have much experience with placing associations directly in the embedded id component since this is not supported by JPA but is Hibernate specific.

As an alternative my suggestion would be to use the approach described in the Composite Primary Keys section of the JPA wikibook:

(...) JPA 1.0 requires that all @Id mappings be Basic mappings, so if your Id comes from a foreign key column through a OneToOne or ManyToOne mapping, you must also define a Basic @Id mapping for the foreign key column. The reason for this is in part that the Id must be a simple object for identity and caching purposes, and for use in the IdClass or the EntityManager find() API.

Because you now have two mappings for the same foreign key column you must define which one will be written to the database (it must be the Basic one), so the OneToOne or ManyToOne foreign key must be defined to be read-only. This is done through setting the JoinColumn attributes insertable and updatable to false, or by using the @PrimaryKeyJoinColumn instead of the @JoinColumn.

A side effect of having two mappings for the same column is that you now have to keep the two in synch. This is typically done through having the set method for the OneToOne attribute also set the Basic attribute value to the target object's id. This can become very complicated if the target object's primary key is a GeneratedValue, in this case you must ensure that the target object's id has been assigned before relating the two objects.

(...)

Example ManyToOne id annotation

...
@Entity
@IdClass(PhonePK.class)
public class Phone {
    @Id
    @Column(name="OWNER_ID")
    private long ownerId;

    @Id
    private String type;

    @ManyToOne
    @PrimaryKeyJoinColumn(name="OWNER_ID", referencedColumnName="EMP_ID")
    private Employee owner;
    ...

    public void setOwner(Employee owner) {
        this.owner = owner;
        this.ownerId = owner.getId();
    }
    ...
}

This looks like to be what you're looking for (and maybe less complicated). I'd try to implement this solution (incrementally).

Pascal Thivent
Thank you, I will try this out as soon as possible.
Jon