3

I would like to have in my entity composite primary key consisting of 2 columns (attributes), and have one of them to be foreign key simultaneously.

I write something like this, but don't know whether it works cause foreign key is marked as generated value in IntelliJ data source

@Entity
@Table(name = "service_point")
@Access(AccessType.PROPERTY)
@IdClass(ServicePointId.class)
public class ServicePoint {

    private Long providerId;
    private Integer servicePointNumber;

    private Provider provider;

    @Id
    @Basic(optional = false)
    @Column(name = "provider_id", nullable = false, insertable = false,
            updatable = false, columnDefinition = "BIGINT UNSIGNED")
    public Long getProviderId() {
        return providerId;
    }

    public void setProviderId(Long providerId) {
        this.providerId = providerId;
    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "service_point_no", nullable = false, columnDefinition = "BIGINT UNSIGNED")
    public Integer getServicePointNumber() {
        return servicePointNumber;
    }

    public void setServicePointNumber(Integer servicePointNumber) {
        this.servicePointNumber = servicePointNumber;
    }

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "provider_id")
    public Provider getProvider() {
        return provider;
    }

    public void setProvider(Provider provider) {
        this.provider = provider;
    }
}

UPDATED:

I have tested Brian Vosburgh and it works:

transaction.begin();

em.persist(provider);

ServicePoint servicePoint = new ServicePoint(provider, 1);

em.persist(servicePoint);

transaction.commit();

ServicePoint servicePoint2 = em.find(ServicePoint.class,
        new ServicePointId(provider.getUserId(), servicePoint.getServicePointNumber()));

assertTrue("Service point provider id and Provider provider id should be the same.",
        servicePoint2.getProvider().getUserId() == provider.getUserId());
assertNotNull("Service point number can not be null", servicePoint2.getServicePointNumber());
assertEquals(servicePoint2.getProvider(), provider);

transaction.begin();
em.remove(servicePoint);
em.remove(provider);
transaction.commit();

UPDATE 2 - new problem in next relationship composite PK (3 columns) and 2 of them are composite FK I have been trying to resemble the below solution and couldn't get through how to write ServicePointPhotoId @IdClass

extentions of previous example

Michał Ziobro
  • 10,759
  • 11
  • 88
  • 143

2 Answers2

4

Get rid of the providerId field and its corresponding getter and setter. Add an @Id annotation to getProvider(). Define the IdClass like this:

public class ServicePointId {
    private Long provider;
    private Integer servicePointNumber;
    public Integer getProvider() {
        return provider;
    }
    public void setProvider(Integer provider) {
        this.provider = provider;
    }
    public Integer getServicePointNumber() {
        return servicePointNumber;
    }
    public void setServicePointNumber(Integer servicePointNumber) {
        this.servicePointNumber = servicePointNumber;
    }
}

Note that the property name in the IdClass matches the property name in the Entity (i.e. provider), but the properties' types are different. In the IdClass the property type must match the type of the Id property of Provider.

This is discussed in the JPA 2.1 spec, section 2.4.1.

Suggestion for UPDATE 2:

public class ServicePointPhotoId {
    public ServicePointId servicePoint;
    public Long photoId;
}

@Entity
@IdClass(ServicePointPhotoId.class)
@Table(name="service_point_photo")
public class ServicePointPhoto {
    @Id
    @ManyToOne
    @JoinColumns({
        @JoinColumn(name="provider_id", referencedColumnName="provider_id"),
        @JoinColumn(name="service_point_no", referencedColumnName="service_point_no")
    })
    private ServicePoint servicePoint;

    @Id
    @Column(name="photo_id")
    private Long photoId;
}

Note the attribute name must match (i.e. servicePoint); but the IdClass attribute's type must match the referenced Entity's IdClass (i.e. ServicePointId).

I have used field annotations, but you can convert those to property annotations.

Again: the JPA 2.1 spec has an example of just this sort of relationship in section 2.4.1.3.

Brian Vosburgh
  • 3,146
  • 1
  • 18
  • 18
  • I'm testing this solution and consider if there is possibility to do something like: 1) em.persist(provider); ServicePoint servicePoint = new ServicePoint(provider); em.persist(servicePoint); and auto generated only servicePointNumber. 2) Could i remain in IdClass providerId and use @MapsId("provider_id") on provider in ServicePoint? – Michał Ziobro Jun 09 '15 at 09:03
  • Ok this solution seems to work correctly could you tell me if such approach will be ok or better will be to use .@EmbeddedId instead of @IdClass. If I say about auto generated servicePointNumber I mean if I add new ServicePoint(provider) than for provider id ex. 5 i get generated next for example point 3 -> (5,3), but this isn't just autoicremented so (5,3), (4,3), (1,3) = (providerId, servicePointNo) will be allowed – Michał Ziobro Jun 09 '15 at 09:39
  • I prefer using `@EmbeddedID` as it reduces the duplication of all the attributes in two classes and the chance of them getting out of sync. I have always thought `@IdClass` was in the spec to be consistent with the technique used by the pre-JPA spec Container Managed Persistence. – Brian Vosburgh Jun 09 '15 at 15:05
  • Edited answer to add suggestion for UPDATE 2 – Brian Vosburgh Jun 09 '15 at 15:27
  • ok thx, actually I do it also myself based on this reference JPA 2.1 spec, section 2.4.1.3 which you recommended :) – Michał Ziobro Jun 09 '15 at 18:10
0

The best way to do composite primary keys is to use the @EmbeddedId with a corresponding @Embeddable class.

Of our 400-odd database Entities, about 135 used Embedded ID classes to implement composite (multi-field) primary keys.

There are lots of questions and answers here on SO with examples of these.

DuncanKinnear
  • 4,563
  • 2
  • 34
  • 65
  • Is there any crucial difference in using .@EmbeddedId instead of .@IdClass ? – Michał Ziobro Jun 09 '15 at 09:08
  • @Michael See [this question](http://stackoverflow.com/questions/212350/which-annotation-should-i-use-idclass-or-embeddedid) and [this question](http://stackoverflow.com/questions/4024800/jpa-hibernate-whats-better-for-composite-primary-keys-idclass-or-embeddedid) here at SO for some discussion about the difference. We were taught the `@EmbeddedId` way by our SUN Microsystems mentor back in 2006 when we first started our EJB3 stuff. I believe that `@EmbeddedId` is the newer, more OO way to do composite keys. – DuncanKinnear Jun 09 '15 at 19:42