11

Two tables like:

CREATE TABLE foo (
 id INT PRIMARY KEY,
 x TEXT
);
CREATE TABLE bar (
 foo_id INT REFERENCES foo (id) ON DELETE CASCADE,
 y TEXT,
 z TEXT
);

...can be mapped like so:

@Table(name = "foo")
@SecondaryTable(name = "bar", pkJoinColumns = @PrimaryKeyJoinColumn(name = "foo_id", referencedColumnName = "id"))
class Foo {
 @Id
 int id;
 @Embedded
 @AttributeOverrides({
 @AttributeOverride(name = "y", column = @Column(table = "bar")),
 @AttributeOverride(name = "z", column = @Column(table = "bar"))
 })
 Bar bar;
}
@Embeddable
class Bar {
 String y;
 String z;
}

Is there a less awkward way to do this mapping, using either just standard JPA annotations, or else Hibernate-specific annotations (and without introducing a parent reference in the embeddable object)?

Compare this to how easily a collection of @Embeddable objects can be referenced using an @ElementCollection and @CollectionTable.

asked Apr 25, 2017 at 0:54
2
  • Can't you simply map it using @ElementCollection but hide that implementation detail i.e. don;t expose the collection but provide set/getBar methods that manipulate the collection? Commented Apr 25, 2017 at 8:22
  • That would work, too, but would be even uglier than the verbose annotations above :-) Commented Apr 25, 2017 at 16:22

1 Answer 1

5

Sorry for the late answer, but the question interested me so I figured I'd take a crack at it.

A more elegant solution is to make the contents of bar an Entity instead of an Embeddable, and to use a OneToOne relationship instead of an Embedded. You also need to remember to use @PrimaryKeyJoinColumn instead of @JoinColumn:

@Entity
@Table(name = "bar")
class Bar {
 @Id
 int fooId;
 String y;
 String z;
}
...
@Entity
@Table(name = "foo")
class Foo {
 @Id
 int id;
 @OneToOne
 @PrimaryKeyJoinColumn
 Bar bar;
}

The biggest advantage is that Foo no longer needs to know about the contents of Bar. Depending on your DB's naming strategy, you may also need to specify the column name of fooId in Bar with something like @Column(name = "foo_id").

answered Jul 19, 2018 at 16:15
Sign up to request clarification or add additional context in comments.

16 Comments

Should have been more clear that the database schema is fixed (no unique identifiers for bar), so this solution wouldn't work.
@ejain in your DDL for Bar you have this: foo_id INT REFERENCES foo (id) ON DELETE CASCADE. That's all the unique identifier you need. If you're saying that there can be multiple rows in that table (bar) that refer to the same Foo entity, then even your original SecondaryTable solution wouldn't work. If the SecondaryTable solution works, so will this one.
Your current solution loses the foreign key relationship, and only works as long as you manually set both primary keys to identical values... Just replace @PrimaryKeyJoinColumn with @MapsId @JoinColumn(name = "id"), and I'll accept your solution!
The original @Embedded solution creates a foreign key that matches the DDL also posted above; looks like using @MapsId allows us to have a foreign key without having to make the relationship bidirectional.
The @MapsId solution is based on your solution, and doesn't use @Embedded or @SecondaryTable. It's possible that this is a blatant misuse of this annotation, but it appears to work, and doesn't require changing the Bar domain object to have a reference back to a Foo object.
|

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.