Using hibernate, insert to child fails with "Referential integrity constraint violation" on child. Parent Id is incremented for each child.
// Parent: Composite primary key, one auto generated
#IdClass(PlanId.class)
public class PlanEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "PlanIdGenerator")
#SequenceGenerator(name = "PlanIdGenerator", sequenceName = "PLAN_ID_SEQUENCE", allocationSize = 1)
private Long id;
#Id
private Long version;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "planEntity", fetch = FetchType.LAZY, orphanRemoval = true) //
private Collection<PlanGoalBucketEntity> goalBuckets = new ArrayList<>();
public void addPlanGoalBucketEntity(PlanGoalBucketEntity goalBucket) {
goalBuckets.add(goalBucket);
goalBucket.setPlanEntity(this);
}
public void removePosition(PlanGoalBucketEntity goalBucket) {
goalBuckets.remove(goalBucket);
goalBucket.setPlanEntity(null);
}
.....
}
//Child
public class PlanGoalBucketEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(optional = false)
#JoinColumns({ #JoinColumn(name = "plan_id", referencedColumnName = "id"),
#JoinColumn(name = "version", referencedColumnName = "version") })
private PlanEntity planEntity;
.....
}
Insert to planentity (parent) with one PlanGoalBucketEntity (child) is all good.
Inserting parent with multiple child, it fails with foreign key violation "Referential integrity constraint violation". Inserting first child is fine but second child fails because it increments parent id for second child.
Couldn't figure out what is going wrong.
You haven't pasted your PlanId class, but I assume it is something like this
public class PlanId implements Serializable {
public Long id;
public Long version;
}
I copied your code and the following test works, all I did was implement PlanId as above and added necessary setters in your entities. I am using an H2 DB
#Test
public void plan() {
PlanEntity planEntity = new PlanEntity();
planEntity.setVersion(1L);
planEntity.addPlanGoalBucketEntity(new PlanGoalBucketEntity());
planEntity.addPlanGoalBucketEntity(new PlanGoalBucketEntity());
planEntityRepository.save(planEntity);
// Check save of 2 plan goal buckets is successful
List<PlanEntity> planEntities =
entityManager.createQuery(
"select distinct p from PlanEntity p left join fetch p.goalBuckets", PlanEntity.class)
.getResultList();
assertEquals(1, planEntities.size());
assertEquals(2, planEntities.iterator().next().getGoalBuckets().size());
}
// JpaRepository
#Repository
public interface PlanEntityRepository extends JpaRepository<PlanEntity, PlanId> {}
Related
I have two entity classes.
Order.java
#Entity
#Table(name = "order_table")
public class Order implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "order_id", referencedColumnName = "id", nullable = false, insertable=false, updatable=false)
private Set<Item> items;
// getters & setters & toString
Item.java
#Entity
#Table(name = "item")
public class Item implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "order_id", nullable = false)
private Long orderId;
// getters & setters && toString
I created a test class like this:
#Test
public void createOrderWithItems() {
Item item = new Item();
item.setName("Iron Man");
Order order = new Order();
order.setName("Toy");
order.getItems().add(item);
Order created = service.createOrder(order);
Order orderById = service.getOrderById(order.getId());
System.out.println("Created Order: " + orderById);
Item itemById = service.getItemById(item.getId());
System.out.println("Created item: " + itemById);
Assert.notNull(created.getId(), "Order ID is Null");
}
Test is green but if you check output, you'll see that orderId field in the Item class is null.
Created Order: Order{id=1, name='Toy', items=[Item{id=2, name='Iron Man', orderId=null}]}
Created item: Item{id=2, name='Iron Man', orderId=null}
Does JPA not update this column in the db automatically? Is this column is redundant? If so, how can I retrieve this information from test code?
You need to set orderId explicitly.
item.setOrderId(order.getId());
order.getItems().add(item);
You can create a method addItem(Item item) in your Order class and hide this logic within it.
Cascading will create an entry in db but it won't initialize field. JPA annotations just indicate to JPA provider how to perform mapping between entity and table.
Moreover, check your annotations. #JoinColumn should be used in the entity which owns the relationship (the corresponding table has column as a foreign key). Check the top answer for this question for detailed explanations: What's the difference between #JoinColumn and mappedBy when using a JPA #OneToMany association
#Entity
#Table(name = "parent");
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class Parent {
#Id
#SequenceGenerator(name = "ME_SEQ", sequenceName = "ME_SEQ", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ME_SEQ")
#Column(name = "PARENT_ID", columnDefinition = "NUMBER(38,0)", nullable = false, unique = true)
private Long id;
}
There is also a child entity (seperate table) which has a PK and FK that points to Parent ID.
#Entity
#Table(name = "child")
public class Child extends Parent {
#Id
#Column(name = "PARENT_ID")
private Long id;
}
Even though there is two separated tables, I get an error from Hibernate:
org.hibernate.mapping.UnionSubclass cannot be cast to
org.hibernate.mapping.RootClass
Is it not possible to have ID in the child class, even if it's a different table from the parent?
Ive added 2 hibernate model objects
First table
#Entity
#Table(name = "ACTIVITIES")
public class ActivityMO extends ModelBase {
#Column(name = "CA_ID", nullable = false, insertable = true,updatable = true, length = 22, precision = 0)
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "G1")
#SequenceGenerator(name = "G1", sequenceName = "CSM_ACTIVITIES_SEQ")
private Long id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "activityId", cascade = {CascadeType.ALL})
#Fetch(FetchMode.JOIN)
List<ActivitiesProductsMO> relatedProducts;
...getters / setters
}
The other table is
#Entity
#Table(name = "ACTIVITIES_PRODUCTS")
public class ActivitiesProductsMO {
#Column(name = "CAP_ID")
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "G1")
#SequenceGenerator(name = "G1", sequenceName = "ACTIVITIES_PRODUCTS_SEQ")
private Long id;
#Column(name = "CAP_ACTIVITY_ID")
private Long activityId;
#Column(name = "CAP_PRODUCT_ID")
private Long productId;
...getters/setters
}
The point is to populate each db record for ActivitiesProductsMO.activityId with ActivityMO.id value
I.e.
If I create an activity record with id = 555
I'll get another activity_product record with activityId of 555
How can i get this to work?
Thank you!
Instead of manually trying to map the entitiy relations with long values you should use a bidirectional OneToMany relationship from ActivityMO to ActivitiesProductsMO
change ActivitiesProductsMO to:
#Entity
#Table(name = "ACTIVITIES_PRODUCTS")
public class ActivitiesProductsMO {
// cut unimportant code ...
#ManyToOne
#JoinColumn(name = "CAP_ACTIVITY_ID")
private ActivityMO activityId;
// cut unimportant code ...
}
If you then were to persist an ActivityMO that already has ActivitiesProductsMO entries in its relatedProducts List, the Cascade type should actually take care and create those products while filling out the CAP_ACTIVITY_ID database field with the right value.
Another Possible Solution:
Use a Unidirectional OneToMany:
#Entity
#Table(name = "ACTIVITIES")
public class ActivityMO extends ModelBase {
#OneToMany(fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
#Fetch(FetchMode.JOIN)
#JoinColumn(name = "CAP_ACTIVITY_ID")
List<ActivitiesProductsMO> relatedProducts;
}
And remove the
private Long activityId;
from your ActivitiesProductsMO class.
This should both lead to identical database structure. But in the second case you would no longer have the "backlink" inside java from ActivitiesProductsMO to ActivityMO
I am having a problem when I perform merge parent table, the existing child records will be insert rather than update.
For example, Member(Parent) and Address(Child) tables,
public class Member implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private int id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "member", cascade = CascadeType.ALL)
public List<Address> addressList;
}
public class Address implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int address_id;
#Column(name = "member_id")
private int mem_id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumns({
#JoinColumn(name = "member_id", referencedColumnName = "id", insertable = false, updatable = false)
})
public Member member;
}
While I perform changes and merge(Member), the address record is being inserted rather than update the existing record.
Does anyone know what would be the reason?
when you perform changes do you set correct address_id ? . Do some debugging and find address_id match with existing id in Address table.
in my approach class will look like below. I don't see need of member_id attribute. because it is same as id attribute of member.
public class Address implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int address_id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "member_id", nullable = false)
public Member member;
public void setMember(Member member) {
this.member = member;
}
}
I can't see any parent child relationship in your snapshot code.
Please amend the code for child class with below code to create the inheritance relationship.
public class Child extends Member implements Serializable{
Extending the Child class to the Parent(Member) will reflect the required changes related to lazy loading.
I am creating a JPA entity with a ManyToOne relationship. Why does having child.setParent(parent); cause the following fail during flush():
org.apache.openjpa.persistence.ArgumentException: Missing field for property "parent_id" in type "class test.ChildPrimaryKey".
The test code that fails:
Parent parent = new Parent();
Child child = new Child();
ChildPrimaryKey childPrimaryKey = new ChildPrimaryKey();
childPrimaryKey.setLookupId(1);
child.setId(childPrimaryKey);
child.setParent(parent); // <-- FAIL because of this
// Begin transaction
entityManager.clear();
entityManager.getTransaction().begin();
LOGGER.info("Persisting parent without child.");
entityManager.persist(parent);
LOGGER.info("Updating parent with child.");
childPrimaryKey.setParentId(parent.getId());
parent.getChildren().add(child);
entityManager.merge(parent);
// Fail happens at flush
entityManager.flush();
Entities:
#Embeddable
public class ChildPrimaryKey {
#Column(name = "lookup_id")
private int lookupId;
#Column(name = "parent_id")
private long parentId;
}
#Entity
#Table(name = "child")
public class Child {
#EmbeddedId
private ChildPrimaryKey id;
#MapsId("parent_id")
#ManyToOne
private Parent parent;
}
#Entity
#Table(name = "parent")
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Child> children;
}
If I remove child.setParent(parent); statement, then my code passes. Using OpenJPA 2.2.2
I beleive your MapsId should be #MapsId("parentId") based on the class structure you've presented. The value of MapsId is an attribute, not a column name.