开发者

JPA OneToOne association where 2 entities use composite primary keys but use different column names?

开发者 https://www.devze.com 2023-02-03 04:13 出处:网络
We are trying to use Hibernate with a database that uses a lot of composite keys and it\'s been causing us a lot of headaches.

We are trying to use Hibernate with a database that uses a lot of composite keys and it's been causing us a lot of headaches. Unfortunately, we can't change the schema so we have to do a lot of additional mapping betwen our fields. We are restricted to using JPA 1.0 and Hibernate 3.3.

The biggest problem we've had so far is to do with a one-to-one association between two entities using a composite key of 2 values, where the tables have different names for these columns (the DB has a naming convention of having a table-specific prefix on each column.)

Whenever we perform our query, we get this exception:

Caused by: org.hibernate.TypeMismatchException Provided id of the wrong type for class com.business.entity.InvestorIssuerEmailEntity.  
Expected: class com.business.entity.InvestorIssuerEmailEntityPK, got class com.business.entity.InvestorIssuerEntityPK; 

The two classes for these tables, InvestorIssuerEntity and InvestorIssuerEmailEntity, have an optional @OneToOne assocation (in some cases InvestorIssuer has no matching record in InvestorIssuerEmail):

@IdClass(InvestorIssuerEntityPK.class)
@Table(name = "T090_INVESTOR_ISSUER")
@Entity
InvestorIssuerEntity
    @Column(name = "T090_091_INVESTOR_ID", nullable = false, insertable = true, 
updatable = true, length = 18, precision = 0)
    @Id
    private Long investorId;

    @Column(name = "T090_102_ISSUER_ID", nullable = false, insertable = true, 
updatable = true, length = 18, precision = 0)
    @Id
    private Long issuerId;

(other fields omitted)

    @OneToOne(optional = true)
    @JoinColumns(value = {
            @JoinColumn(name="T090_091_INVESTOR_ID", referencedColumnName = "T284_INVESTOR_ID", nullable = false, insertable = false, updatable = false),
            @JoinColumn(name = "T090_102_ISSUER_ID", referencedColumnName = "T284_ISSUER_ID", nullable = false, insertable = false, updatable = false)
    })
    @NotFound(action = NotFoundAction.IGNORE)
    private InvestorIssuerEmailEntity investorIssuerEmail;

... 

InvestorIssuerEntityPK 
    @Id
    @Column(name = "T090_091_INVESTOR_ID", nullable = false, insertable = true, 
updatable = true, length = 18, precision = 0)
    private Long investorId;

    @Id
    @Column(name = "T090_102_ISSUER_ID", nullable = false, insertable = true, 
updatable = true, length = 18, precision = 0)
    private Long issuerId;

...

@IdClass(InvestorIssuerEmailEntityPK.class)
@Table(name = "T284_INVESTOR_ISSUER_EMAIL")
@Entity
InvestorIssuerEmailEntity
    @Column(name = "T284_INVESTOR_ID", nullable = false, insertable = true, 
updatable = true, length = 18, precision = 0)
    @Id
    private Long investorId;

    @Column(name = "T284_ISSUER_ID", nullable = false, insertable = true, 
updatable = true, length = 18, precision = 0)
    @Id
    private Long issuerId;

...

InvestorIssuerEmailEntityPK 

    @Column(name = "T284_INVESTOR_ID", nullable = false, insertable = true, 
updatable = true, length = 18, precision = 0)
    @Id
    private Long investorId;

    @Column(name = "T284_ISSUER_ID", nullable = false, insertable = true, 
updatable = true, length = 18, precision = 0)
    @Id
    private Long issuerId;  

I've tried to get around the Type Mismatch problem by using the same class as the @EmbeddableId for the two entities, and then using @AttributeOverrides, like this:

@Id
@EmbeddedId
@AttributeOverrides({
        @AttributeOverride(name = "investorId",
                column = @Column(name = "T284_INVESTOR_ID", nullable = false, insertable = true, updatable = true, length = 18, precision = 0)),
        @AttributeOverride(name = "issuerId",
                column = @Column(name = "T284_ISSUER_ID", nullable = false, insertable = true, updatable = true, length = 18, precision = 0))
})
private InvestorIss开发者_C百科uerId investorIssuerId;

I only did the change for these two entities though, still using the @IdClass approach for the other entities (Is it a case of only using @IdClass OR @EmbeddableId for your entities, not both?)

We ended up getting other issues like "Repeated column in mapping for entity", so we've reverted to this approach to see if there's other workarounds to this.

Does anyone have any solutions to get around this problem? I've looked around StackOverflow but haven't come across any cases where the composite keys used in the association have different names.

Note: Even after trying the suggestion below, we still get this error: org.hibernate.MappingException: Repeated column in mapping for entity: com.business.entity.InvestorIssuerEntity column: T090_091_INVESTOR_ID (should be mapped with insert="false" update="false")

I even removed ALL associations from InvestorIssuerEntity and still got the same problem. The error only went away when I removed the @Column annotation in the composite key class. Of course, the queries didn't work because the investorId wasn't mapped! I don't understand where Hibernate was finding the "Repeated column in mapping", since I'd already removed every mention of T090_091_INVESTOR_ID else except for the composite key.

We have other associations in InvestorIssuerEntity that do a join on the same primary keys, but the associated entities also have additional columns in their composite keys. Once you use @EmbeddedId, are you supposed to use them for all entities? We still use @IdClass for the others classes. But then how does that cause a "repeated column" anywhere?


It seems I got a working solution for your case:

@Entity
public class InvestorIssuerEntity {
    @EmbeddedId 
    private InvestorIssuerEntityPK investorIssuerEntityPK;

    @OneToOne(optional=true, mappedBy="investorIssuerEntity")
    private InvestorIssuerEmailEntity investorIssuerEmailEntity;
}

@Entity
public class InvestorIssuerEmailEntity {
    @EmbeddedId @AttributeOverrides({
        @AttributeOverride(name="investorId", column=@Column(name="T02_INV_ID")),
        @AttributeOverride(name="issuerId", column=@Column(name="T02_ISS_ID"))
    })
    private InvestorIssuerEntityPK investorIssuerEntityPK;

    @OneToOne(optional=true) @PrimaryKeyJoinColumns({
        @PrimaryKeyJoinColumn(name="T02_ISS_ID", referencedColumnName="T01_ISS_ID"), 
        @PrimaryKeyJoinColumn(name="T02_INV_ID", referencedColumnName="T01_INV_ID")
    })
    private InvestorIssuerEntity investorIssuerEntity;
}

@Embeddable
public class InvestorIssuerEntityPK implements Serializable {
    private static final long serialVersionUID = -1176248537673293674L;

    @Column(name="T01_INV_ID")
    private Long investorId;

    @Column(name="T01_ISS_ID")
    private Long issuerId;
}

It generates the following DDL, which seems to be what you are looking for:

create table InvestorIssuerEmailEntity (
    T02_INV_ID bigint not null,
    T02_ISS_ID bigint not null,
    primary key (T02_INV_ID, T02_ISS_ID)
)

create table InvestorIssuerEntity (
    T01_INV_ID bigint not null,
    T01_ISS_ID bigint not null,
    primary key (T01_INV_ID, T01_ISS_ID)
)

alter table InvestorIssuerEmailEntity 
    add constraint FKC2FBCC4E1E26612E 
    foreign key (T02_INV_ID, T02_ISS_ID) 
    references InvestorIssuerEntity


I want to mention an alternative approach:

  • pro: Both entities can use their own PK class
  • con: The 1:1 relation manifests in a Set with 1 entry

You can switch to a @OneToMany Relation and store the related entity in a Set. The Set will have 1 entry, which can be unpacked in a post-processing step.

@OneToMany
@JoinColumns(value = {
        @JoinColumn(name="T090_091_INVESTOR_ID", referencedColumnName = "T284_INVESTOR_ID", nullable = false, insertable = false, updatable = false),
        @JoinColumn(name = "T090_102_ISSUER_ID", referencedColumnName = "T284_ISSUER_ID", nullable = false, insertable = false, updatable = false)
})
@NotFound(action = NotFoundAction.IGNORE)
private Set<InvestorIssuerEmailEntity> investorIssuerEmails;
0

精彩评论

暂无评论...
验证码 换一张
取 消