Two almost identical OneToOne in Entity, custom join annotation - java

I've got Parent - Child relationship OneToOne, but two of them. Annotations are not good but it produces good DB schema, yet code is not working. If I try to save Parent instance, Hibernate at first tries to save child1 and child2 - but it breaks FK defined in Child -> because owner doesn't exist yet in DB...So I need to save Parent and then child1, and child2.
If I could do that it doesn't help, because when I try to load Parent, Hibernate will not know which record in Child table belongs to child1 or child2...So in this case I would need to specify one condition in join for child1 something like "where type = 1" and for child2 "where type = 2"...
Just to clarify: in Child table there will be zero or one child for one Parent with ChildType.A (always child1) and zero or one child with ChildType.B (always child2).
I need to save xml which looks like this:
<parent id="" oid="">
<child1 (and other attributes)>
<child2 (and other attributes)>
<parent>
Both child1 and child2 elements are the same type therefore are type of Child in java classes. Only difference is element name (in java I differentiate them with ChildType). Only identification for children are id and oid attributes from parent. They points to another Parent hence target in Child.
I need to change annotations somehow to get this working...Do you guys have some ideas, because I'm really stuck???
Parent.java
public class Parent {
private String oid
private Long id;
private Child child1;
private Child child2;
#Id
#GeneratedValue(generator = "IdGenerator")
#GenericGenerator(name = "IdGenerator", strategy = "com.example.IdGenerator")
#Column(name = "id")
public Long getId() {
return id;
}
#Id
#GeneratedValue(generator = "OidGenerator")
#GenericGenerator(name = "OidGenerator", strategy = "com.example.OidGenerator")
#Column(name = "oid", nullable = false, updatable = false, length = 36)
public String getOid() {
return oid;
}
#OneToOne(optional = true, fetch = FetchType.EAGER)
#Cascade({org.hibernate.annotations.CascadeType.ALL})
public Child getChild1() {
return child1;
}
#OneToOne(optional = true, fetch = FetchType.EAGER)
#Cascade({org.hibernate.annotations.CascadeType.ALL})
public Child getChild2() {
return child2;
}
}
Child.java
public class Child {
private Parent owner;
private String ownerOid;
private Long ownerId;
private ChildType type;
private Parent target;
#MapsId("owner")
#ManyToOne(fetch = FetchType.LAZY)
#PrimaryKeyJoinColumns({
#PrimaryKeyJoinColumn(name = "owner_oid", referencedColumnName = "oid"),
#PrimaryKeyJoinColumn(name = "owner_id", referencedColumnName = "id")
})
public Parent getOwner() {
return owner;
}
#MapsId("target")
#ManyToOne(fetch = FetchType.LAZY)
#PrimaryKeyJoinColumns({
#PrimaryKeyJoinColumn(name = "target_oid", referencedColumnName = "oid"),
#PrimaryKeyJoinColumn(name = "target_id", referencedColumnName = "id")
})
public Parent getTarget() {
return target;
}
#Id
#Column(name = "owner_id")
public Long getOwnerId() {
if (ownerId == null && owner != null) {
ownerId = owner.getId();
}
return ownerId;
}
#Id
#Column(name = "owner_oid", length = 36)
public String getOwnerOid() {
if (ownerOid == null && owner != null) {
ownerOid = owner.getOid();
}
return ownerOid;
}
#Id
#Column(name = "target_id")
public Long getTargetId() {
if (targetId == null && target != null) {
targetId = target.getId();
}
return targetId;
}
#Id
#Column(name = "target_oid", length = 36)
public String getTargetOid() {
if (targetOid == null && target != null) {
targetOid = target.getOid();
}
if (targetOid == null) {
targetOid = "";
}
return targetOid;
}
#Id
#Enumerated(EnumType.ORDINAL)
public ChildType getType() {
if (type == null) {
return ChildType.A;
}
return type;
}
}
ChildType.java
public enum ChildType {
A, B;
}
I also tried to use mappedBy approach mappedBy approach but there are still problems with loading - I can't tell hibernate which child record belogs to which child class member variable.

there are too many things I don't quite get in your solution to give a good answer but just some thoughts:
Consider using inheritance instead of ChildType enum. So you would have ChildA and ChildB extending Child.
That way you Parent can have:
private ChildA child1;
private ChildB child2;
Instead of having a composite primary key, I would consider using a unique auto generated key and then add a unique constraint on on the other id and oid fields. It should make the child parent relationships easier and you can have different parent implementation for ChildA and ChildB:
In ChildA:
#OneToOne(mappedBy="child1")
public Parent getParent() {
return parent;
}
And
In ChildB:
#OneToOne(mappedBy="child2")
public Parent getParent() {
return parent;
}
And in Child just:
public abstract Parent getParent();
Now the whole Parent/Owner/Target thing I still didn't quite grasp.

I see two problems with using one-to-one references:
Parent has zero or one child1, and zero or one child2. My understanding is that you use one to one when it's ALWAYS one.
Your IDs are getting really complicated. The idea with one-to-one is that the ID's are the same between the two entities, and you've got Parent with a two-part primary key, and Child with a different four part primary key.
The other addendum to 2. is that if you're using 1-1, then the ID of parent and child should be the same. But if the parent has two children, they can't both have the same ID! So there's a real data modeling problem. Also, the fact that child type is part of the key is also a bad smell, cause I expect child type is part of your business logic,
I think what you want is what Hibernate calls a 'unidirectional one-to-one association on a foreign key'. The Hibernate ORM manual shows how to do this in XML, here's an example with Annotations, although without the unique property set.
I'm going to assume there's no outside business reason why your primary keys have to be the way that they are, and suggest that you
Change Parent and Child to each have to have a single field primary key, using #Id and #GeneratedValue(strategy=GenerationType.AUTO).
Change Parent to have the references to child1 and child2 be #ManyToOne(unique=true)
If you want, change Child as detailed in the Hibernate manual so that it has a reference to the Parent.
You will lose the 'feature' that parents and children have the same ID, but gain massive simplicity in your object ID scheme, making database operations faster and the code simpler to read.
If there is an outside reason that you haven't specified as to why Parent and/or Child need multi-part primary keys, this answer will need some modification.
I also second the point that #barsju made about using inheritance, or even entirely independent classes, to do the two Child types. If you use ID's the way I lay out here, you can make Parent have references to the concrete subtypes, and the query will work fine.

Use #JoinColumns and define a filter
In Parent.java:
#OneToOne(optional = true, fetch = FetchType.EAGER)
#JoinColumns ({
#JoinColumn(name="id", referencedColumnName = "owner_id"),
#JoinColumn(name="oid", referencedColumnName = "owner_oid"),
})
#FilterJoinTable(name="type", condition="type = 'A'")
#Cascade({org.hibernate.annotations.CascadeType.ALL})
public Child getChild1() {
return child1;
}
#OneToOne(optional = true, fetch = FetchType.EAGER)
#JoinColumns ({
#JoinColumn(name="id", referencedColumnName = "owner_id"),
#JoinColumn(name="oid", referencedColumnName = "owner_oid"),
})
#FilterJoinTable(name="type", condition="type = 'B'")
#Cascade({org.hibernate.annotations.CascadeType.ALL})
public Child getChild2() {
return child2;
}
I never used filters, so the exact syntax may be a little bit different, but that should give you the idea how it should work.
By the way, you can make the design easier if you introduce a single id column (not a composite id as you do it now). In that case the type don't need to be part of the id. Hibernate does not really get happy with composite ids. Nevertheless composite ids work and they don't have anything to do with your problem. The filter should work in the same way then.

Related

Hibernate composite pattern with many-to-many composition

I want to create a tree structure where each node can have multiple parents and children. (So actually it is not really a tree but more of a network).
For example, we have an interface to implement the composition, a User class which is the leaf node and a Group class which builds the structure. There would be some check against recursion (adding a group to a group that had the first group as a parent somewhere).
interface GroupMember {
boolean isLeaf();
}
class User implements GroupMember {
private int id;
private String name;
boolean isLeaf() { return true; }
}
class Group implements GroupMember {
private int id;
private Set<GroupMember> members;
boolean isLeaf() { return false; }
public addMember(GroupMember newMember) {
// Some check against recursion
members.add(newMember);
}
}
I see the most efficient way of implementing this in the database would be to have a link table (though this is just a suggestion and not required):
TABLE GROUP_MEMBER
-------------------
PARENT_ID NUMBER
CHILD_TYPE CHAR(1)
CHILD_ID NUMBER
However, I am not sure if Hibernate supports this design. It seems to me that in loading the members set in Group Hibernate would have to consider the discriminator in the GROUP_MEMBER table to decide which class to instantiate.
I have considered having group containing two sets to separately fetch the groups and users, but this seems less than ideal.
May be I'm wrong, but I don't agree with having CHILD_TYPE to be part part of GROUP_MEMBER. I's a CHILD implementation detail and should stay with it. By moving it to the CHILD table, you can use standard ManyToMany JPA mapping, which should make the life simpler.
If desired, CHILD_TYPE can be a discriminator inside the CHILD table.
I always recommend to have a FK. Bugs happen, and orphans in the database are always a huge headache.
Entities:
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "CHILD_TYPE", length = 1)
#Table(name = "MEMBERS", schema = "mtm")
#Data //lombok
#EqualsAndHashCode(onlyExplicitlyIncluded = true) //lombok
public abstract class GroupMember {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#ManyToMany
#JoinTable(name = "GROUP_MEMBER", schema = "mtm",
joinColumns = #JoinColumn(name = "MEMBER_ID", referencedColumnName = "ID"),
inverseJoinColumns = #JoinColumn(name = "PARENT_ID", referencedColumnName = "ID"))
private Set<Group> parents = new HashSet<>();
public abstract boolean isLeaf();
}
#Entity
#DiscriminatorValue("G")
#Data
#EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true)
class Group extends GroupMember {
#ManyToMany(mappedBy = "parents")
private Set<GroupMember> members = new HashSet<>();
public boolean isLeaf() {
return false;
}
}
#Entity
#DiscriminatorValue("U")
#SecondaryTable(name = "USERS", schema = "mtm")
#Data
#EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true)
class User extends GroupMember {
#EqualsAndHashCode.Include
#Column(table = "USERS")
private String name;
public boolean isLeaf() {
return true;
}
}
Schema:
create schema if not exists MTM;
CREATE TABLE MTM.MEMBERS (
id INT GENERATED BY DEFAULT AS IDENTITY,
CHILD_TYPE CHAR(1)
);
CREATE TABLE MTM.GROUP_MEMBER (
member_id INT,
parent_id INT
);
CREATE TABLE MTM.users (
id INT,
name varchar(255)
);
Notes:
Standard Hibernate MTM and inheritance strategies are implemented
Common data is stored in the MEMBERS table and User specific inside USERS table (implemented using #SecondaryTable)
Group data is stored entirely inside MEMBERS for efficiency (eliminates JOIN), but can be extended in the same way as User
If required, an additional interface can be introduced for the isLeaf() property.
I think you could use a #NamedQuery looking like select g from Group g left join fetch g.members on top of your Group class and use this query with the Hibernate session. Then you would use a query like select g from Group g left join fetch g.members where g.id = :id and get the result then.

JPA: 'CascadeType.REMOVE' or 'orphanRemoval = true', which use in a n:n relation that generate new table/class with EmbeddeId class?

I am developing an REST API to a pizzeria store. And here i'm trying to delete a Flavor and all data related to it. Further explained below:
Classes:
Flavor have at least one Filling, each one taking a position on it.
i.e: Souce (at pos. 1), mozzarela (at pos. 2) tomato (at pos. 3)
Flavors must have a price to each Size
With that in mind, we can conclude that exist two many-to-many relationships:
Flavor to many Filling
Flavor to many Size
Class diagram of actual implementation
The requirement is to: delete a Flavor, and automatically delete all the FillingPositionFlavor and FlavorPriceSize.
But,I'm confused on use of CascadeType.REMOVE and orphanRemoval = true:
When I use Cascade and OrphanRemoval on Flavor.sizePrices, get a HibernateException when trying to edit a Flavor, exclusion works fine:
A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: com.pkg.Flavor.sizePrices
When I use Cascade on Flavor.sizePrices, get a PSQLException when excluding a Flavor, editing works fine:
ERROR: update or delete on table "tb_flavor" violates foreign key constraint "fk9orw0yhtc0e06ka84dbcd2c82" on table "tb_flavor_size_price"
I'm doing unit testing of services in Spring Boot to test all the CRUD operations.
Below is the actual code, I hid properties like id and others to facilitate the read.
#Entity
#Table(name = "tb_flavor")
class Flavor {
#OneToMany(cascade = {CascadeType.PERSIST,CascadeType.REMOVE},orphanRemoval = true)
private Set<FlavorPositionFilling> flavors = new HashSet<FlavorPositionFilling>();
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.REMOVE},orphanRemoval = true)
private Set<FlavorPriceSize> priceSizes;
// other properties and methods
}
#Entity
#Table(name = "tb_flavor_price_size")
class FlavorPriceSize {
#EmbeddedId
private FlavorPriceSizeEmbeddeId id;
private float price;
// other properties and methods
}
#Embeddable
class FlavorPriceSizeEmbeddeId implements Serializable {
#ManyToOne(cascade = { CascadeType.ALL })
#JoinColumn(name = "ID_FLAVOR_FK", referencedColumnName = "id_flavor")
private Flavor flavor;
#ManyToOne(cascade = { CascadeType.ALL })
#JoinColumn(name = "ID_SIZE_FK", referencedColumnName = "id_size")
private Size size;
}
#Entity
#Table(name = "tb_flabor_position_filling")
class FlaborPositionFilling {
#EmbeddedId
private FlaborPositionFillingEmbeddedId id;
private Integer position;
}
#Embeddable
class FlaborPositionFillingEmbeddedId implements Serializable {
#ManyToOne(cascade = CascadeType.REMOVE)
#JoinColumn(name="ID_FLAVOR_FK", referencedColumnName="id_flavor")
private Flavor sabor;
#ManyToOne()
#JoinColumn(name="ID_FILLING_FK", referencedColumnName="id_filling")
private Filling filling;
}
I've read a lot about both, but still not understand the right use of each and their effect on operations. Can anyone explain it to me? Show videos, images, code...
Let's assume that you have a parent -> child relationship.
If you set CacadeType.REMOVE on the relationship every EntityManager.remove call on the parent will also remove the children.
orphanRemoval = true is used to delete orphan children.
So if remove a child from the parent reference or collection and save the parent the child will be deleted because its no longer attached to the parent.

how to select child in one to many relation in jpa

I want to select parent with child that i want.
But when I select my parent I have to show all the childs
How can i do that?
Example:
public class parent{
private Integer id;
#OnetoMany
#JoinColumn(name="parentId")
private List<child> children;
}
public class child{
private Integer id;
private Integer parentId;
}
findByIdAndchildType(Integer id, String type)
I want to see : parent(id) - > child (type)
But i can see parent(id) - > child(othertype), child(othertype1), child(type)
It sounds to me that you're trying to get a bi-directional relation. This is possible by adding the mapping to both sides of the relation.
For example, add a #ManyToOne mapping to the Child entity. Be aware that you should probably remove your parentId field since now you can access it by using child.getParent().getId().
#Entity
public class Child {
#Id
private Integer id;
#ManyToOne
#JoinColumn(name = "parentId")
private Parent parent;
// Remove parentId field
// Getters + Setters ...
}
NOTE: If you want to keep the parentId field, you'll have to choose which two of the mappings (getParentId() or getParent().getId()) you want to use for inserting and updating entities. The other field should have both insertable = false and updatable = false.
The next step is to change the #OneToMany mapping to use mappedBy:
#Entity
public class Parent {
#Id
private Integer id;
#OneToMany(mappedBy = "parent") // Change this
private List<Child> children;
// Getters + Setters ...
}
If you want to retrieve a specific child with its parent, you can now create a repository for Child entities:
public interface ChildRepository extends JpaRepository<Child, Integer> {
}
After that, you can get a specific child by using:
Optional<Child> child = repository.findById(123); // 123 is the ID of the child in this case
Optional<Parent> parent = child.map(Child::getParent);
With Spring boot 1.x that would be:
Child child = repository.findOne(123);
Parent parent = null;
if (child != null) {
parent = child.getParent();
}

Hibernate. Keep reference to last child

I have simple Parent-Child relationship with #OneToMany and #ManyToOne annotations.
#Entity
public class Parent {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "parent", fetch = FetchType.LAZY)
private List<Child> children = new ArrayList<>();
}
public class Child {
#ManyToOne( fetch = FetchType.LAZY, optional = false)
#JoinColumn( name = "parent_id" )
#ForeignKey( name = "fk_child_parent" )
private Parent parent;
}
But I also want to keep reference to current(last) child inside Parent entity.
How to do it in right way? Should I introduce new undirectional #OneToOnerelationship in Parent? Like this:
#OneToOne( optional = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn( name = "current_child_id", nullable = false )
#ForeignKey( name = "FK_parent_child" )
private Child currentChild;
So you are always gonna have the issue of determining the insertion order of child elements for existing parent's and children on application start. You should therefore have this data preserved in your persistence layer.
Add a created (or updated) timestamp to your child class, you can determine for yourself how you want to handle the initialization of this field (i.e. with column defaults or triggers etc.). Now the required information should be available to you in your code, just add a 'compareTo' method to your Child (to sort) and add the following a getMostRecentChild method to your Parent class.
#Entity
public class Parent {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "parent", fetch = FetchType.LAZY)
public List<Child> children = new ArrayList<>();
public Child getMostRecentChild() {
if(children == null || children.isEmpty()) {
return null;
}
return Collections.sort(children).get(0);
}
}
public class Child {
#ManyToOne( fetch = FetchType.LAZY, optional = false)
#JoinColumn( name = "parent_id" )
#ForeignKey( name = "fk_child_parent" )
public Parent parent;
#Column( name = "created_ts" )
public createdTs;
public int compareTo(Child other) {
return created_ts.compareTo(other);
}
}
The implementation details for how you find the currentChild are irrelevant. You could also use a TreeSet or just a loop to find the currentChild.
EDIT: Argument why this is better than a currentChild column.
If you don't like the above answer consider the following
Adding a currentChild field provides two references to the same object. It is therefore not normalized, whereas, adding a timestamp adds a normalized column to your schema from which the information you require (and more) can easily be derived in a way that is directly coupled to the logic you are attempting to encapsulate in your currentChild column.
In order to ensure that your currentChild doesn't mismatch from it's intended value, you must encapsulate it's logic in either the Hibernate object, which you can then override on a database level, or with triggers on a database level, which can be ugly and hard to trace (whereas a default value and 'ON UPDATE' for created_ts field is fairly common place)
Since you have a List, you can just look at the last element of the list.
You will want Hibernate to maintain the list order by adding #OrderColumn(name="INDEX_COL") below the #OneToMany

Handling creation of ORM objects prior to persistence/generation of primary keys?

Bear with me as I try to simplify my issue as much as possible.
I am creating a new ORM object. This object has an auto generated primary key which is created on the database using as an identity. Within this object, is a child object with a many to one relationship with the parent object. One of the attributes I need to set to create the child object is primary key of the parent object, which has not been generated yet. It is important to note that the primary key of the child object is a composite key that includes the primary key of the parent object.
Diagram http://xs941.xs.to/xs941/09291/fieldrule.1degree221.png
In this diagram FieldRule is the child table and SearchRule is the parent table. The problem is that SearchRuleId has not been generated when I am creating FieldRule objects. So there is no way to link them.
How do I solve this problem?
Here is are some relevant snippets from the entity classes, which use annotation based mappings.
From SearchRule.java (Parent Class):
public class SearchRule implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = true)
#Column(name = "ID")
private Integer id;
#Basic(optional = false)
#Column(name = "Name", unique = true)
private String name;
#Basic(optional = false)
#Column(name = "Threshold")
private int threshold;
#Basic(optional = false)
#Column(name = "LastTouched", insertable = false, updatable = false)
#Temporal(TemporalType.TIMESTAMP)
private Date lastTouched;
#Column(name = "TouchedBy")
private String touchedBy;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "searchRule", fetch = FetchType.LAZY)
private Collection<FieldRule> fieldRuleCollection;
#JoinColumn(name = "IndexTemplateId", referencedColumnName = "ID")
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private IndexTemplate indexTemplateId;
From FieldRule.java (Child Class):
public class FieldRule implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected FieldRulePK fieldRulePK;
#Basic(optional = false)
#Column(name = "RuleValue")
private String ruleValue;
#JoinColumns({#JoinColumn(name = "IndexTemplateId", referencedColumnName = "IndexTemplateId", insertable = false, updatable = false), #JoinColumn(name = "FieldNumber", referencedColumnName = "FieldNumber", insertable = false, updatable = false)})
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private Field field;
#JoinColumn(name = "SearchRuleId", referencedColumnName = "ID", insertable = false, updatable = false)
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private SearchRule searchRule;
From FieldRulePK.java (Child PK Class):
#Embeddable
public class FieldRulePK implements Serializable {
#Basic(optional = false)
#Column(name = "IndexTemplateId")
private Integer indexTemplateId;
#Basic(optional = false)
#Column(name = "FieldNumber")
private Integer fieldNumber;
#Basic(optional = false)
#Column(name = "SearchRuleId")
private Integer searchRuleId;
Why do you have to set the primary key of the initial object in the sub-objects? With a proper mapping the reference will get set by the JPA application automatically.
So the answer is: do a correct mapping.
If you need a more detailed answer provide a more detailed question. Including:
source code of the involved classes
source code used to create and persist the instances
exceptions experienced
information on which jpa implementation you use
Edit, after more details where provided in the question:
I think your embeddable PK should look something like this:
#Embeddable
public class FieldRulePK implements Serializable {
#Basic(optional = false)
#Column(name = "IndexTemplateId")
private Integer indexTemplateId;
#Basic(optional = false)
#Column(name = "FieldNumber")
private Integer fieldNumber;
#ManyToOne( ... some not so trivial details here ..)
private SearchRule searchRule;
}
And the searchRule property of your FieldRule should be dropped. The entity reference in the embeddable should result in an id field in the database.
This is a database design issue, I think. If the FieldRule can be created independently of the SearchRule (in other words, SearchRuleId is not a "not null" field) then you need to not include it in your composite primary key. If SearchRuleId cannot be null, then you just have to save the objects in the right order, which your ORM should handle for you if your mapping is correct.
I think the problem is with the way you're doing your mapping, where you're trying to pull too many database concepts into your OO model. ORM was a little confusing to me as well, when I started doing it. What you need to understand is that the concept of a primary key field is a database concept and not an OO concept. In OO, each object reference is unique, and that's what you use to identify instances.
Object references do not really map well to the database world, and that's why we have primary key properties. With that said, the use of primary key properties should be kept to a minimal. What I find helpful is to minimize the type of primary key properties that map directly to the primary key columns (usually, integer properties that map to a primary key column).
Anyway, based on that, here's how I think you should do your mapping (changes highlighted with horizontal separators):
From FieldRule.java (Child Class):
public class FieldRule implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected FieldRulePK fieldRulePK;
#Basic(optional = false)
#Column(name = "RuleValue")
private String ruleValue;
// Removed field and searchRule mapping as those are already in the
// primary key object, updated setters/getters to pull properties from
// primary key object
public Field getField() {
return fieldRulePK != null ? fieldRulePK.getField() : null;
}
public void getField(Field field) {
// ... parameter validation ...
if (fieldRulePK == null) fieldRulePK = new FieldRulePK();
fieldRulePK.setField(field);
}
public SearchRule getSearchRule() {
return fieldRulePK != null ? fieldRulePK.getSearchRule() : null;
}
public void setSearchRule(SearchRule searchRule) {
// ... parameter validation ...
if (fieldRulePK == null) fieldRulePK = new FieldRulePK();
fieldRulePK.setSearchRule(searchRule);
}
From FieldRulePK.java (Child PK Class):
#Embeddable
public class FieldRulePK implements Serializable {
// Map relationships directly to objects instead of using integer primary keys
#JoinColumns({#JoinColumn(name = "IndexTemplateId", referencedColumnName = "IndexTemplateId", insertable = false, updatable = false), #JoinColumn(name = "FieldNumber", referencedColumnName = "FieldNumber", insertable = false, updatable = false)})
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private Field field;
#JoinColumn(name = "SearchRuleId", referencedColumnName = "ID", insertable = false, updatable = false)
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private SearchRule searchRule;
SearchRule.java should be fine as it is.
I hope this all makes sense.
Note that this is untested, it would take too much time for me to set up a test database and create all the necessary test code, but I hope it gives you an idea on how to proceed.
Posting this mostly because I can't leave this complicated of comment... but anyway...
Normally when I look at EmbeddedId type things I see things like from this example of Embeddable keys. Normally I'd expect something like
From ChildPK.java:
#Basic(optional = false)
#Column(name = "ParentId")
private Parent parent;
But here I guess we've got 2 other FKs being made into a composite PK, IndexTemplateId and FieldNumber... and this Parent object's ID is auto-generated using a sequence.
Now I suppose that you must already be persisting the Parent object prior to trying to persist the child object or you must mark the Parent object in child as cascading, that should ensure the id gets populated, the composite keys seem to greatly complicate the problem.
Since this is a new ORM I would suggest that you use a single PK on each table instead of composite ids and simply have FK relations between the tables.
Apologies if I'm not grasping something here, but I'm not quite sure there is enough information here - I would ask for the entire Entity field declarations just to see how you're trying to put this together each of your 3 classes...
Something is a bit fishy here. Generally speaking if you have parent entity A and child entity B and you are persisting A with some children the correct order of operations is first inserting A into the database and then inserting children (I am assuming proper cascade from A to B). So in this general case the ids will be properly generated and everything should OK.
However it appears that in your case children (FieldRules) are saved first. The only reasonable explanation for this I can think of is that if you have an additional entity C (in your case probably Field entity) which is already saved when your code is running and it has a cascade to FieldRules. In this case you have two conflicting cascades: one SearchRule -> FieldRule and another Field -> FieldRule. Since JPA doesn't perform smart analysis of this it is a matter of chance (and loading order) which one will get invoked first. And in your case the Field->FieldRules is probably invoked which causes the children to be inserted before parent.
So I would try to search for any additional cascades TO FieldRules in your code and try to remove those. If you can remove them all it will probably solve your problem
Bottom line, your searchRule MUST be saved before your fieldRules can be.
However, rather than having the column definition on the field, you could try having it on a getter...
#Embeddable
public class FieldRulePK implements Serializable {
//snip other columns
#Basic(optional = false)
#Column(name = "SearchRuleId")
private Integer getSearchRuleId()
{
return this.fieldRule.searchRule.getId();
}
private void setSearchRuleId(Integer id)
{
this.fieldRule.searchRule = new SearchRule(id);
}
This would mean that when the saveSearchRule(searchRule) cascades into the FieldRuleCollection to save that, the searchRuleId is automatically retrieved from the searchRule after it is saved, rather than having to hackily be added in.
It means whatever creates your FieldRulePK object has to pass a reference to it's parent, but otherwise means your hacky setSearchRuleId() loop is unnecessary.
Why does the "sub-object" (I think you mean "child") need to have the key to the parent object? If you have a OneToMany on the Parent object and a ManyToOne on the Child object with mappedBy, your child object will already have a foreign key (and a reference to the parent object).
Also, you need to check you cascade in your Parent object OneToMany annotation.
Simple answer: don't rely on your persistence layer generating the IDs at the time of persistence. Create the entity IDs at the time you create the objects.
Unless you are coding some specific meaning into your keys (a database anti-pattern), they can be any random, unique value such as a UUID (GUID for the Microsofties).
And here's something to think about when you use your persistence layer to generate the ID/primary key: do you use the entity's primary key in the hashcode or equals method?
If you do use the ID/primary key in the hashcode/equals method then you will break the contract expected of objects when stored in a Java collection. See this Hibernate page for more details.
Right now my work around is doing something like,
Collection<FieldRule> fieldRules = searchRule.getFieldRuleCollection();
if (searchRule.getId() == null)
{
//null out the collection so it doesn't cascade on persist
searchRule.setFieldRuleCollection(null);
//save to get id
dao.saveSearchRule(searchRule);
for (FieldRule fr : fieldRules) {
fr.getFieldRulePK().setSearchRuleId(searchRule.getId());
}
}
//re set collection
searchRule.setFieldRuleCollection(fieldRules);
//remove double refrence, which jpa doesn't like, to FieldRuleCollection
fieldRules = null;
//save again, this time for real
dao.saveSearchRule(searchRule);
That seems really hackey to me, but it does work (maybe, I'm hitting some other issues but they may be unrelated).
There must be a better way to turn off casacade for a single persist.

Categories

Resources