Custom join table makes Spring Data throw IllegalArgumentException: Expected id attribute type

StackOverflow https://stackoverflow.com/questions/23221789

  •  07-07-2023
  •  | 
  •  

Question

I have three tables; Account, Event and AccountEventRelation.

The last one acts as a normal Many-to-Many relation between user and events, with the small but important exception that it allows one to have properties on the relation.
(Imagine an event with many users, but some users can also be an moderator of an event. The boolean property on account-event relation indicates that.)

I can make this work perfectly if I write my own DAO or use plain JDBC and SQL, but with Spring Data it fails terribly. Adding to that, the error message is confusing;

Caused by: java.lang.IllegalArgumentException: Expected id attribute type [class lan.localhost.entity.EventAccountRelationPk] on the existing id attribute [SingularAttributeImpl[EntityTypeImpl@1443927423:Account [ javaType: class lan.localhost.entity.Account descriptor: RelationalDescriptor(lan.localhost.entity.Account --> [DatabaseTable(user_account)]), mappings: 4],org.eclipse.persistence.mappings.ManyToOneMapping[account]]] on the identifiable type [EntityTypeImpl@1204999057:EventAccountRelation [ javaType: class lan.localhost.entity.EventAccountRelation descriptor: RelationalDescriptor(lan.localhost.entity.EventAccountRelation --> [DatabaseTable(event_account_rel)]), mappings: 4]] but found attribute type [class lan.localhost.entity.Account].
  at org.eclipse.persistence.internal.jpa.metamodel.IdentifiableTypeImpl.getId(IdentifiableTypeImpl.java:200)
  at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation$IdMetadata.<init>(JpaMetamodelEntityInformation.java:212)
  ...

And the error message only appears when I have an interface extending JpaRepository which is autowired.

What do I do wrong?
Have I misunderstood the JPA technology or am I just using Spring Data incorrectly?

Note: I have a repository on GitHub with the a test case as a Maven project, but here are snippets from my classes;

@Entity
@Table(name = "user_account")
public class Account {

  @Id
  @Column(name = "id")
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;

  ...

  @OneToMany(mappedBy = "account")
  private List<EventAccountRelation> events;

  ...

}

The Event class;

@Entity
@Table(name = "meeting")

public class Event {

  @Id
  @Column(name = "id")
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;

  ...

  @OneToMany(mappedBy = "event")
  private List<EventAccountRelation> accounts;

  ...

}

The join table;

@Entity
@Table(name = "event_account_rel")
public class EventAccountRelation {

  @EmbeddedId
  private EventAccountRelationPk primaryKey;

  @MapsId("eventId")
  @ManyToOne(optional = false)
  @JoinColumn(name = "event_id", nullable = false, updatable = false)
  private Event event;

  @MapsId("accountId")
  @ManyToOne(optional = false)
  @JoinColumn(name = "account_id", nullable = false, updatable = false)
  private Account account;

  @Column(name = "moderator", nullable = false)
  private Boolean moderator = Boolean.FALSE;

  ...

}

And finally the composite key;

@Embeddable
public class EventAccountRelationPk {

  private Long eventId;

  private Long accountId;

  ...

}

I use Spring 3.1.4, Spring Data 1.4.5 and EclipseLink 2.5.1 on a Java 1.6.
Of course I could try a later version, but unfortunately I am restricted to this environment. :/

Was it helpful?

Solution

There are two ways to solve the problem.

(1) to use IdClass instead of EmbeddedId

@Entity
@Table(name = "event_account_rel")
@IdClass(EventAccountRelationPk.class)
public class EventAccountRelation {

  @Id
  private Long eventId;

  @Id
  private Long accountId;

  @MapsId("eventId")
  @ManyToOne(optional = false)
  @JoinColumn(name = "event_id", nullable = false, updatable = false)
  private Event event;

  @MapsId("accountId")
  @ManyToOne(optional = false)
  @JoinColumn(name = "account_id", nullable = false, updatable = false)
  private Account account;

  @Column(name = "moderator", nullable = false)
  private Boolean moderator = Boolean.FALSE;

  ...

}

(2) to add a primary key field in EventAccountRelation table and an additional unique key.

@Entity
@Table(name = "event_account_rel",
       uniqueConstraints = {@UniqueConstraint={"EVENT_ID","ACCOUNT_ID"}})
public class EventAccountRelation {

  @Id
  private Long id;

  @ManyToOne(optional = false)
  @JoinColumn(name = "event_id")
  private Event event;

  @ManyToOne(optional = false)
  @JoinColumn(name = "account_id")
  private Account account;

  @Column(name = "moderator", nullable = false)
  private Boolean moderator = Boolean.FALSE;

  ...

}

To compare the two solutions, 1st can use the IdClass to CRUD the relation table with the provided method provided in Repository interface, but one point is you should care to set the account_id when account is set...

2nd solution will need you write the query to find out EventAccountRelation by the composition of event_id and account_id.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top