I'm trying to do a JPA mapping for an existing database. I can't really change the existing structure.
I managed to make it works, but Intellij is telling me that some column doesn't exist even if it works. So I don't know if there's a better way to do this or if it's Intellij that doesn't support all the use cases.
I simplified my mapping and table for the question.
The 2 tables with primary composite keys are:
Table_A
some_id_a
some_seq_a
Table B
some_id_a
some_seq_a
some_seq_b
And my mapping is:
#Data
#Entity(name="Table_A")
public class TableA {
#EmbeddedId
private Key key;
#OneToMany
#JoinColumn(name = "someIdA")
#JoinColumn(name = "someSeqA")
private List<TableB> tableBs;
#Data
#Embeddable
public static final class Key implements Serializable {
private String someIdA;
private long someSeqA;
}
}
#Data
#Entity(name="Table_B")
public class TableB {
#EmbeddedId
private Key key;
#Data
#Embeddable
public static final class Key implements Serializable {
private String someIdA;
private long someSeqA;
private long someSeqB;
}
}
So like I said it works but I have an error in Intellij saying that the #JoinColumn(name ="someIdA") #JoinColumn(name = "someSeqA") don't exist and is expecting something like #JoinColumn(name ="some_id_a") #JoinColumn(name = "some_seq_a").
Using it the way Intellij is telling me, JPA has en error that says: Table [table_b] contains physical column name [some_id_a] referred to by multiple logical column names: [some_id_a], [someIdA].
My mapping is ok despite Intellij but is there's a better alternative ?
Thanks
You can use a "derived identity" and map your classes like this:
#Data
#Entity(name="Table_A")
public class TableA {
#EmbeddedId
private Key key;
#OneToMany(mappedBy = "tableA")
private List<TableB> tableBs;
#Data
#Embeddable
public static final class Key implements Serializable {
private String someIdA;
private long someSeqA;
}
}
#Data
#Entity(name="Table_B")
public class TableB {
#EmbeddedId
private Key key;
#MapsId("tableAKey") // maps tableAKey attribute of embedded id
#JoinColumns({
#JoinColumn(name="some_id_a", referencedColumnName="some_id_a"),
#JoinColumn(name="some_seq_a", referencedColumnName="some_seq_a")
})
#ManyToOne
private TableA tableA;
#Data
#Embeddable
public static final class Key implements Serializable {
private TableA.Key tableAKey; // corresponds to PK type of TableA
private long someSeqB;
}
}
Derived identities are discussed (with examples) in the JPA 2.2 spec in section 2.4.1.
Related
I have the following tables in my DB:
statement:
id | created_date | message
and
statement_configuration
id | name | currency
and
statement_balances
statement_id | statement_configuration_id | balance
Where the statement_balances table has a composite primary key on statement_id and statement_configuration_id.
My Statement entity looks like this:
public class Statement implements Serializable {
#Id
private long id;
#Column
private String message
//I'm not sure of which annotations I need here
#OneToMany
private Map<Long, StatementBalance> statementBalancesByConfigId;
....
}
The StatementBalances entity looks like this:
public class Statement implements Serializable {
#Id
private long statmentId;
#Id
private long statementConfigurationId;
#Column
private long balance;
....
}
My goal is to build a Map of type Map<Long, StatementBalances> inside my Statement entity. The map will map the statement_configuration_id to a balance; allowing me to get all the StatementBalances that are linked to this Statement (keyed by statement_configuration_id).
Is it possible to build this map using JPA annotations?
Yes this is possible. An example solution:
#Entity
public class Statement implements Serializable {
#Id
private long id;
private String message;
#OneToMany(mappedBy = "statementId")
#MapKey(name = "statementConfigurationId")
private Map<Long, StatementBalances> statementBalancesByConfigId;
}
#Entity
#Table(name = "statement_configuration")
public class StatementConfiguration implements Serializable {
#Id
private long id;
#OneToMany(mappedBy = "statementConfigurationId")
private Collection<StatementBalances> statementBalances;
private String name;
private String currency;
}
The StatementBalancesId composite primary key class and StatementBalances entity class allow modeling a ternary association by creating of two bidirectional relationships between them:
public class StatementBalancesId implements Serializable {
long statementId;
long statementConfigurationId;
// requires no-arg constructor, equals, hashCode
}
#Entity
#Table(name = "statement_balances")
#IdClass(StatementBalancesId.class)
public class StatementBalances implements Serializable {
#Id
#ManyToOne
#JoinColumn(name="statement_configuration_id")
private StatementConfiguration statementConfigurationId;
#Id
#ManyToOne
#JoinColumn(name="statement_id")
private Statement statementId;
#Column
private long balance;
}
The database tables created this way are identical as those in the question.
I've ran into problem with composite primary key handling by Hibernate as a JPA provider.
My entities look like below
// Entity class
#Entity
#IdClass(ExternalMatchPK.class)
#Table(name = "external_match")
public class ExternalMatch {
#Id
#Column(name = "place_id")
private Integer placeId;
#Id
#Column(name = "external_object_id")
private Integer externalObjectId;
// ... Other stuff here
}
// Key class
public class ExternalMatchPK implements Serializable {
private Integer placeId;
private Integer externalObjectId;
}
Looks pretty simple yet no matter what I do I keep getting the following exception (lines are splitted for readability):
org.hibernate.MappingException:
Repeated column in mapping for entity: ExternalMatch
column: external_object_id (should be mapped with insert="false" update="false")
I've tried placing annotation on entity class fields and key class fields together as well as separately, moving all annotations from fields to getters on each one of the classes, using key calss as #Embeddable and putting it into the entity class with #EmbeddedId. Nothing seems to work.
This case seems trivial so maybe it's something wrong with our setup but I can't even imagine where to look for the issue.
Any advice is much appreciated.
It appears that I shot myself in the foot with this.
The issue was that I had a biderectional mapping between ExternalMatch and ExternalObject I forgot about trying to replace the actual entity with its integer id.
So changing
// Entity class
#Entity
#IdClass(ExternalMatchPK.class)
#Table(name = "external_match")
public class ExternalMatch {
#Id
#Column(name = "place_id")
private Integer placeId;
#Id
#Column(name = "external_object_id")
private Integer externalObjectId;
// ... Other stuff here
}
// Key class
public class ExternalMatchPK implements Serializable {
private Integer placeId;
private Integer externalObjectId;
}
// Related entity class
#Entity
#Table(name = "external_object")
public class ExternalObject extends AbstractNameableEntity {
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "external_object_id", nullable = false)
private List<ExternalMatch> matches;
// ...
}
to reprsent actual mappings like this
// Entity class
#Entity
#IdClass(ExternalMatchPK.class)
#Table(name = "external_match")
public class ExternalMatch {
#Id
#ManyToOne
#JoinColumn(name = "external_object_id", referencedColumnName = "id")
private ExternalObject externalObject;
#Id
#ManyToOne
#JoinColumn(name = "place_id")
private Poi place;
// ... Other stuff here
}
// Key class
public class ExternalMatchPK implements Serializable {
private Poi place;
private ExternalObject externalObject;
}
// Related entity class
#Entity
#Table(name = "external_object")
public class ExternalObject extends AbstractNameableEntity {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "externalObject")
private List<ExternalMatch> matches;
// ...
}
resolved the repeated mapping issue yet leaving us with all the familiar troubles a biderectional mapping creates :)
It seems to me that there is virtually no difference between the below two ways of mapping. Here is an example base on #MapsId javadoc:
// parent entity has simple primary key
#Entity
public class Employee {
#Id long empId;
...
}
// dependent entity uses EmbeddedId for composite key
#Embeddable
public class DependentId {
String name;
long empid; // corresponds to primary key type of Employee
}
#Entity
public class Dependent {
#EmbeddedId DependentId id;
...
#MapsId("empid") // maps the empid attribute of embedded id
#ManyToOne Employee emp;
}
What if I change Dependent's mapping to:
#Entity
public class Dependent {
#EmbeddedId DependentId id;
#ManyToOne
#JoinColumn("empid", insertable=false, updatable=false)
Employee emp;
}
What is the difference of the above two approach?
So, I tested #MapsId for my usage when in the table I have only one foregin key it was no different. But for tables where I have two foregin keys to one table like ...
UserTable, and EmailTable-> #MapsId(owner)UserTable owner, #MapsId(receiver) UserTable receiver i have problems with that. Hibernate throws exceptions. So i have to back to old #JoinColumn way of doing that. That was a one differemce that I met with that adnotations.
I am using combination of both #MapsId and #JoinColumn together to avoid getting extra field getting created in DB for associating the entities. IF I ignore #JoinColumn, an extra field is getting created in DB.
#Entity
public class BookingsModel implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private SlotDateModel slotDateModelObj;
#JsonProperty
String slotnumber;
#MapsId("memberid")
#JsonBackReference
#ManyToOne
#JoinColumn(name="memberid",referencedColumnName = "memberid")
#NotNull
MemberModel memberModel;
.
.
.
}
#Entity
public class MemberModel implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#JsonProperty
#Id
String memberid;
#JsonProperty
String name;
#JsonIgnore
String phoneno;
#JsonManagedReference
#OneToMany
Set<BookingsModel> bookings;
.
.
.
}
#Embeddable
public class SlotDateModel implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
String memberid;
String slotdate;
.
.
.
}
Tables generated with #JoinColumn
Table generated when #JoinColumn is commented Can notice that the extra field "member_model_memberid" is getting added.
I got these 2 entities:
#javax.persistence.Entity
public class Book {
#javax.persistence.EmbeddedId
private BookPK id;
private String title;
#javax.persistence.ManyToOne(fetch = javax.persistence.FetchType.LAZY)
#javax.persistence.JoinColumns({
#javax.persistence.JoinColumn(name = "LNGCOD", referencedColumnName = "LNGCOD"),
#javax.persistence.JoinColumn(name = "LIBCOD", referencedColumnName = "LIBCOD") })
private Language language;
}
#javax.persistence.Entity
public class Language {
#javax.persistence.EmbeddedId
private LanguagePK id;
private String name;
}
with composed PK's:
#Embeddable
public class BookPK implements Serializable {
private Integer bookcod;
private Integer libcod;
}
#Embeddable
public class LanguagePK implements Serializable {
private Integer lngcod;
private Integer libcod;
}
If I try to create a new Book and persist it, I get an exception telling me libcod is found twice in the insert statement ("Column 'libcod' specified twice"). But I can't use "insertable = false" when defining the JoinColumn ("Mixing insertable and non insertable columns in a property is not allowed").
Is there any way to define these objects + relationship so the columns are managed automatically by Hibernate ? (I am especially thinking of libcod).
Thank you.
Create a third property "Integer libcod;" on the Book. Have that property manage the db state of libcod. Use insertable=false,updatable=false for both properties in the join to Language. in your "setLanguage" set the private libcod = language.libcod. don't expose a getter/setter for the private libcod.
Are any of the values generated at insert time? This could complicate things further, I suppose.
Maybe somebody can clarify what is wrong with the code below. When I create one-to-one association within embedded class (it is composite primary key) like in the code below:
#Entity
public class Test {
#EmbeddedId
private TestId id;
#Embeddable
public static class TestId implements Serializable {
private static final long serialVersionUID = 1950072763330622759L;
#OneToOne(optional = false)
#JoinColumn(name = "linkedTable_id")
private LinkedTable linkedTable;
}
..........
}
I get the following stack trace:
--------------------------------------------
Caused by: java.lang.NullPointerException
at org.hibernate.cfg.AnnotationBinder.bindOneToOne(AnnotationBinder.java:1867)
at org.hibernate.cfg.AnnotationBinder.processElementAnnotations(AnnotationBinder.java:1286)
at org.hibernate.cfg.AnnotationBinder.fillComponent(AnnotationBinder.java:1662)
at org.hibernate.cfg.AnnotationBinder.bindId(AnnotationBinder.java:1695)
at org.hibernate.cfg.AnnotationBinder.processElementAnnotations(AnnotationBinder.java:1171)
at org.hibernate.cfg.AnnotationBinder.bindClass(AnnotationBinder.java:706)
at org.hibernate.cfg.AnnotationConfiguration.processArtifactsOfType(AnnotationConfiguration.java:452)
at org.hibernate.cfg.AnnotationConfiguration.secondPassCompile(AnnotationConfiguration.java:268)
at org.hibernate.cfg.Configuration.buildMappings(Configuration.java:1121)
at org.hibernate.ejb.Ejb3Configuration.buildMappings(Ejb3Configuration.java:1211)
at org.hibernate.ejb.EventListenerConfigurator.configure(EventListenerConfigurator.java:154)
at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:847)
at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:178)
at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:235)
... 26 more
What is interesting why the sample above works if I change association type to many-to-one and doesn't work with one-to-one?
I wasn't aware this was possible but, according to the Hibernate Annotation reference documentation, it is (this is Hibernate specific though):
2.2.3.2.1. #EmbeddedId property
(...)
While not supported in JPA, Hibernate
lets you place your association
directly in the embedded id component
(instead of having to use the
#MapsId annotation).
#Entity
class Customer {
#EmbeddedId CustomerId id;
boolean preferredCustomer;
}
#Embeddable
class CustomerId implements Serializable {
#OneToOne
#JoinColumns({
#JoinColumn(name="userfirstname_fk", referencedColumnName="firstName"),
#JoinColumn(name="userlastname_fk", referencedColumnName="lastName")
})
User user;
String customerNumber;
}
#Entity
class User {
#EmbeddedId UserId id;
Integer age;
}
#Embeddable
class UserId implements Serializable {
String firstName;
String lastName;
}
And with the code you provided, the following snippet just works for me:
LinkedTable linkedTable = new LinkedTable();
linkedTable.setId(1l);
session.persist(linkedTable);
session.flush();
Test.TestId testId = new Test.TestId();
testId.setLinkedTable(linkedTable);
Test test = new Test();
test.setId(testId);
session.persist(test);
session.flush();
Tested with Hibernate EM 3.4.0.GA, Hibernate Annotations 3.4.0.GA and Hibernate Core 3.3.0.SP1.
If it doesn't work for you, can you provide a bit more code allowing to reproduce the problem?