How do I join tables on non-primary key columns? - java

I have an issue with join tables on an object in an ORM class hierarchy where the join column is NOT the primary key of the base class due a lagacy database structure.
Here is an example of the table design:
CREATE TABLE "SCH"."FOO"
(
"OWNERID" NUMBER(10,0) NOT NULL ENABLE,
"FOOID" NUMBER(10,0) NOT NULL ENABLE,
CONSTRAINT "FOO_PK" PRIMARY KEY ("OWNERID", "FOOID")
CONSTRAINT "FOO_FK1" FOREIGN KEY ("OWNERID") REFERENCES "SCH"."OWNERS" ("OWNERID") ENABLE
)
CREATE TABLE "SCH"."BAR"
(
"BARID" NUMBER(10,0) NOT NULL ENABLE,
"FOOID" NUMBER(10,0)
CONSTRAINT "BAR_PK" PRIMARY KEY ("BARID")
)
And here are the mappings (unesessary infomation removed)
#Entity
#IdClass(FooId.class)
#Table(name = "FOO")
public class Foo implements java.io.Serializable
{
#Id
#Column(name = "OWNERID")
private BigInteger ownerId;
#Id
#SequenceGenerator(name = "FOO_GENERATOR", sequenceName = "SEQ_FOO")
#GeneratedValue(generator = "FOO_GENERATOR")
#Column(name = "FOOID")
private BigInteger id;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "FOOID", referencedColumnName = "FOOID")
#Fetch(value = FetchMode.SUBSELECT)
#Cascade(value = {CascadeType.ALL})
private Set<Bar> bar = new LinkedHashSet<Bar>(0);
}
#Entity
#Table(name = "BAR")
public class Bar implements java.io.Serializable
{
#Id
#Column(name = "BARID")
private BigInteger id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "FOOID", referencedColumnName = "FOOID")
private Foo foo;
}
This fails with an exception:
Caused by: org.hibernate.AnnotationException: referencedColumnNames(FOOID) of com.package.Bar.foo referencing com.package.Foo not mapped to a single property
at org.hibernate.cfg.BinderHelper.createSyntheticPropertyReference(BinderHelper.java:204)
at org.hibernate.cfg.ToOneFkSecondPass.doSecondPass(ToOneFkSecondPass.java:114)
at org.hibernate.cfg.Configuration.processEndOfQueue(Configuration.java:1580)
at org.hibernate.cfg.Configuration.processFkSecondPassInOrder(Configuration.java:1503)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1419)
at org.hibernate.cfg.Configuration.buildMappings(Configuration.java:1375)
Could you please help with a solution?

You must not map the bidirectional association twice. The One side must be marked as the inverse of the Many side, using the mappedBy attribute:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "foo")
#Fetch(value = FetchMode.SUBSELECT)
#Cascade(value = {CascadeType.ALL})
private Set<Bar> bar = new LinkedHashSet<Bar>(0);
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "FOOID", referencedColumnName = "FOOID")
private Foo foo;
There is no reason to tell Hibernate twice that the association is mapped by the join column FOOID. And doing it is actually an error, because it defines two different unidirectional associations rather than one bidirectional association.
EDIT
The above should work, but doesn't due to the following Hibernate bug: It's a Hibernate bug. See HHH-4284.
To circumvent this problem, since the FOOID is enough to ensure uniqueness, a solution would be to remove the #Id annotation from the owner ID and the #IdClass annotation.

U can do as follows.... it should work -
#Entity
#IdClass(FooId.class)
#Table(name = "FOO")
public class Foo implements java.io.Serializable
{
#Id
#Column(name = "OWNERID")
private BigInteger ownerId;
#Id
#SequenceGenerator(name = "FOO_GENERATOR", sequenceName = "SEQ_FOO")
#GeneratedValue(generator = "FOO_GENERATOR")
#Column(name = "FOOID")
private BigInteger id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "FOOID",nullable=false)
#ForeignKey(name = "fk")
private Set<Bar> bar = new LinkedHashSet<Bar>(0);
}
#Entity
#Table(name = "BAR")
public class Bar implements java.io.Serializable
{
#Id
#Column(name = "BARID")
private BigInteger id;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "FOOID", updatable = false, insertable = false, nullable=false)
private Foo foo;
}

You used composite primary key in FOO table. So you should try #EmbeddedId property and you should need two columns "OWNER_ID" and "FOO_ID" in BAR entity that join with FOO entity.

Related

Invalid Identifier While Joining Tables in JPA

I am trying to join 3 tables in JPA. When i am trying to associate CRL_IC_IMPORT_TRANS table i am getting error as Invalid Identifier as shown below
from
crl_ic investorco0_
left outer join
crl_ic_import_trans icimporttr1_
on investorco0_.icimport_trans_event_id=icimporttr1_.event_id
left outer join
crl_ic_order investorco2_
on investorco0_.current_order_id=investorco2_.id
"INVESTORCO0_"."ICIMPORT_TRANS_EVENT_ID": invalid identifier
Edit
from
crl_ic investorco0_
left outer join
crl_ic_order investorco1_
on investorco0_.current_order_id=investorco1_.id
Error:
Provided id of the wrong type for class ICImportTrans. Expected: class java.lang.Long, got class java.lang.String
Why its going to primary key in ICImportTrans class even after i mapped to a non PK ?
Below are my 3 tables and its keys. What is the mistake i am doing .
#Table(name = "CRL_IC")
public class ICLoanOrder {
#Id
#Column(name="EVENT_ID")
String id;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "EVENT_ID",referencedColumnName = "eventId" )
ICImportTrans iCImportTrans;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
ICOrder currentOrder;
}
#Table(name = "CRL_IC_ORDER")
public class ICOrder implements Serializable {
#Id
#GenericGenerator(name = "UUIDGenerator", strategy = "uuid2")
#GeneratedValue(generator = "UUIDGenerator")
#Type(type = "uuid-char")
UUID id;
#ManyToOne(fetch = FetchType.LAZY)
#JsonIgnore
#JoinColumn(name="EVENT_ID")
ICLoanOrder iCLoanOrder;
}
#Table(name = "CRL_IC_IMPORT_TRANS")
public class ICImportTrans implements Serializable {
#Id
#GeneratedValue(strategy= GenerationType.SEQUENCE, generator="SEQ1")
#SequenceGenerator(name="SEQ1",sequenceName = "SEQ_IC_IMPORT",allocationSize = 1)
#Column(name="PROCESS_ID")
private Long processId;
private String eventId;
}
The default name of the column should be iCImportTrans_id.
If you want to change it to icimport_trans_event_id you need to specify it in the #JoinColumn
#Table(name = "CRL_IC")
public class ICLoanOrder {
...
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "icimport_trans_event_id", referencedColumnName = "eventId")
ICImportTrans iCImportTrans;
...
}
Provided id of the wrong type for class ICImportTrans. Expected: class java.lang.Long, got class java.lang.String
What kind of mapping are you trying to achieve?
In ICLoanOrder you mapped the id column and the association column to the same name EVENT_ID. I guess you want to use ICImportTrans.eventId as id of ICLoanOrder.
This willwork if you don't care about having an id field:
#Entity(name="LoanOrder")
#Table(name = "CRL_IC")
public static class ICLoanOrder implements Serializable{
#Id
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "EVENT_ID", referencedColumnName = "eventId")
ICImportTrans iCImportTrans;
}
Normally, you would use #MapsId, but it seems to ignore the referencedColumn attribute and gives the same error.

JPA/Hibernate/SpringBoot: Inserting #OneToMany relationship entities

I'm struggling with inserting #OneToMany entities in the JPA-Hibernate setup.
There are two associated tables with one of the table having the foreign key as the primary key of the source table.
employee
- id (PK)
employee_location
- employee_id (FK to employee)
Here are my entities:
Employee
#Entity(name = "employee")
class Employee {
#Id
#Column(name = "id")
#GeneratedValue()
private Long id;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "employee_id", referencedColumnName = "id")
private List<EmployeeLocation> employeeLocations;
}
Employee Location
#Entity(name = "employee_location")
class EmployeeLocation {
#Id
#Column(name = "employee_id")
private Long employeeId;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "employee_id", referencedColumnName = "id", insertable = false, updatable = false)
private Employee employee;
}
Saving the entities:
List<EmployeeLocation> locations = Arrays.asList(new EmployeeLocation(), new EmployeeLocation());
Employee employee = new Employee();
employee.setLocations(locations);
employee.save(); // Throws exceptions
Which throws me this error:
org.springframework.orm.jpa.JpaSystemException: ids for this class must be manually assigned before calling save():
I tried changing #Entity to #Embeddable and removed the #Id on EmployeeLocation, but it gave me other Unmapped entity exceptions.
How do I handle inserting/updating #OneToMany entities? Is this possible?
How do I handle inserting/updating #OneToMany entities? Is this possible?
If you want the DB to generate the primary key values for you, you need to ask for it by using the #GeneratedValue annotation
#Entity(name = "employee")
class Employee {
#Id
#Column(name = "id")
#GeneratedValue // mean -> "Hey, DB, give me an ID"!
private Long id;
Same applies for EmployeeLocation
More details can be found here
If this does not fully solve your problem, leave a comment.
In your EmployeeLocation entity (detail) you cannot have as primary key the master's primary key, it needs its own. As follows:
#Entity(name = "employee_location")
class EmployeeLocation {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "employee_location_id")
private Integer id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "employee_id", referencedColumnName = "id", insertable = false, updatable = false)
private Employee employee;
}
In the case of Employee entity having a OneToOne relationship with EmployeeLocation entity you can use just #MapsId. This way, the EmployeeLocation id property is populated with the identifier of the post association.
class EmployeeLocation {
#Id
#Column(name = "id")
private Long id;
#OneToOne
#MapsId
private Employee employee;
}
but since your Employee entity has an OneToMany relationship with EmployeeLocation, Employee id property value can't be used as EmployeeLocation id property value because two or more EmployeeLocation entities asociated to the same Employee entity will have the same id value.
You'll need something like this:
#Entity
public class EmployeeLocation {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
#ManyToOne Employee employee;
}
In both cases you need to bind the EmployeeLocation entity to Employee entity, for example:
class Employee {
....
public void addLocation(EmployeeLocation location) {
location.setEmployee(this);
this.employeeLocations.add(location);
}
public void setLocations(List<EmployeeLocation> locations) {
for (EmployeeLocation location : locations) {
location.setEmployee(this);
}
this.employeeLocations = locations;
}
....
}
ANOTHER OPTION: Using ElementCollection
#Entity(name = "employee")
class Employee {
#Id
#Column(name = "id")
#GeneratedValue
private Long id;
#ElementCollection
#CollectionTable(
name="employee_location",
joinColumns=#JoinColumn(name="EMPLOYEE_ID"))
private Set<EmployeeLocation> employeeLocations;
}
#Embeddable
class EmployeeLocation {
// properties
}

Why does Spring data throw SQLIntegrityConstraintViolationException when saving an entity with a relation to itself?

So im developing a project in which i have an entity that holds a relation to itself. Now for some reason when i try to save an entity (made in a react app sent through rest) it throws:
java.sql.SQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`post`.`post_sections`, CONSTRAINT `FKdg8t1aa12xxpo5bwa6ef634x2` FOREIGN KEY (`sections_id`) REFERENCES `sections` (`id`))
Now this is my entity:
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Table(name = SQL.SECTION_TABLE)
#ToString(doNotUseGetters = true)
#Entity(name = SQL.SECTION_ENTITY)
#EqualsAndHashCode(doNotUseGetters = true)
public class SectionDAO implements Serializable {
#Id
#GeneratedValue(generator = "uuid2", strategy = GenerationType.AUTO)
#Column(columnDefinition = "BINARY(16)", unique = true)
#GenericGenerator(name = "uuid2", strategy = "org.hibernate.id.UUIDGenerator")
private UUID id;
#ToString.Exclude
#EqualsAndHashCode.Exclude
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<SectionDAO> children = new HashSet<>();
#ToString.Exclude
#EqualsAndHashCode.Exclude
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private SectionDAO parent;
#ToString.Exclude
#ElementCollection
#EqualsAndHashCode.Exclude
private List<Translation> localizations;
}
All the others work, because i have the spring data hibernate logs and it only blows up when inserting the so called "children". Is there any reason i don't see here to justify it not working?

Hibernate / JPA: How to map two #Immutable entities (views of same table) with another entity by same parameter

There is a table ORDERS from which were created views ORDER_VIEW_A and ORDER_VIEW_B.
I have created entity classes OrderViewA and OrderViewB where in each of them is mapping on entity named 'TransactionRecord'.
It is #OneToOne relationship.
There is column ORDER_ID in TRANSACTION_RECORD table and field orderId in TransactionRecord entity.
Field orderId is same for OrderViewA.id and for OrderViewB.id, cause views are selected from the same table.
My question is, how to map in Hibernate two views in OneToOne relationship with another entity by same field.
My code looks like this and it doesn't work in any way, Hibernate always end up with:
org.hibernate.AnnotationException: Referenced property not a
(One|Many)ToOne: com.example.app.model.TransactionRecord.orderId in
mappedBy of com.example.app.model.views.OrderViewA.orderViewA
#Entity
#Immutable
#Table(name = "ORDER_VIEW_A")
public class OrderViewA {
#Id
#Column(name = "id")
private Long id;
...
#OneToOne(mappedBy = "orderId", fetch = FetchType.LAZY)
private IntegrationRecord orderARecord;
...
}
#Entity
#Immutable
#Table(name = "ORDER_VIEW_B")
public class OrderViewB {
#Id
#Column(name = "id")
private Long id;
...
#OneToOne(mappedBy = "orderId", fetch = FetchType.LAZY)
private IntegrationRecord orderBRecord;
...
}
#Entity
#Table(name = "TRANSACTION_RECORD")
public class TransactionRecord {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "order_id")
private Long orderId;
...
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "order_id")
private OrderViewA orderViewA;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "order_id")
private OrderViewB orderViewB;
...
}

JPA. Stackoverflow on cascade merge

Here is my JPA structure:
Movie (look at cascade types):
#Entity
#Table(name = "movie")
public class Movie {
#Id
#Column(name = "movie_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
//#OneToMany(cascade = CascadeType.ALL, mappedBy = "primaryKey.movie") //stack overflow
#OneToMany(mappedBy = "primaryKey.movie") //works fine
private List<Rating> ratings;
....
}
Rating:
#Entity
#Table(name = "rating")
#AssociationOverrides({#AssociationOverride(name = "primaryKey.movie", joinColumns = #JoinColumn(name = "movie_id")),
#AssociationOverride(name = "primaryKey.user", joinColumns = #JoinColumn(name = "imdb_user_id"))})
public class Rating {
#EmbeddedId
private RatingId primaryKey = new RatingId();
#Column(name = "rating_value")
private Integer ratingValue;
.....
}
RatingId:
#Embeddable
public class RatingId implements Serializable{
#ManyToOne
private Movie movie;
#ManyToOne
private User user;
}
When I call entityManager.merge(Movie movie) with CascadeType.ALL I get the StackOverflowError. If remove cascading, merge call doesn't throw the error. Where may be a problem?
I think this problem related to composite primary key. There is no error when merge performed on another entities with the same one-to-many relationship, but without composite id.
StackOverflow was caused by cyclic relations. To avoid exception I marked keys in many-to-many table as #ManyToOne(fetch = FetchType.LAZY).
That's how my tables look after modifications: https://stackoverflow.com/a/32544519/2089491

Categories

Resources