Persisting embedded (related) items in spring boot jpa repositories? - java

I have the following "parent class" :
#Entity
#Table(name = "parent_table")
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long parentId;
private String firstName;
private String lastName;
#OneToMany(mappedBy = "Parent", cascade = CascadeType.ALL)
List<Child> children;
}
I also have the following child class :
#Entity
#Table(name = "children")
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long childId;
#ManyToOne
#JoinColumn(name="parent_id")
private Parent parent;
private String name;
}
I am sending up the following request in postman :
{
"firstName": "Test",
"lastName": "Parent",
"children":[{
"name":"jack"
},
{
"name":"jill"
}
]
}
When I ask the parent repository to save the parent, it does, but nothing happens for the child... it doesn't save to the database at all...
For reference, this is my line that saves the parent
parentRepository.save(parent)
(the parent in this case, has the two children inside of it - but they don't get saved to the children table).

I ran your example and it seems to be working correctly, only thing i change was mappedBy property of #OneToMany annotation. It must be lower case.
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
List<Child> children = new ArrayList<>();
Parent
#Entity
#Table(name = "parent_table")
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "parent_id")
private Long parentId;
private String firstName;
private String lastName;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
List<Child> children = new ArrayList<>();
}
Child
#Entity
#Table(name = "children")
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long childId;
#ManyToOne
#JoinColumn(name="parent_id")
private Parent parent;
private String name;
}
Test
Above code makes next unit case to succeed:
#Test
public void parentRepositoryMustPersistParentAndChildren() {
Parent parent = new Parent("Anakin", "Skywalker");
parent.getChildren().add(new Child("Luke"));
parent.getChildren().add(new Child("Leia"));
Parent saved = parentRepository.save(parent);
Assert.assertNull("Parent does not have and id assigned after persist it", saved.getParentId());
saved.getChildren().forEach((child) ->{
Assert.assertNull("Parent does not have and id assigned after persist it", child.getChildId());
});
}

Related

Spring Data JPA child entity missing parent ID after transitive save

I have the following class structure
#Entity
#Data // lombok
public class Entity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "entity_details_id", referencedColumnName = "id")
#JsonManagedReference(value = "entityDetails")
private EntityDetails entityDetails;
}
#Entity
#Data
public class EntityDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(mappedBy = "entityDetails")
#JsonBackReference(value = "entityDetails")
private Entity entity;
// Some more fields
#OneToMany(mappedBy = "entityDetails", cascade = CascadeType.ALL, orphanRemoval = true)
#JsonManagedReference(value = "childEntityDetails")
private List<Child> childList;
}
#Entity
#Data
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "description")
private String description;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "entity_details_id")
#JsonBackReference(value = "childEntityDetails)
private EntityDetails entityDetails;
}
Assume I have an Entity object with the following structure:
Entity{
id: null,
entityDetails: {
id: null,
childList: [
{ id: null, description: "ch1" },
{ id: null, description: "ch2" }
]
}
}
When I call entityRepository.save(entity), all the objects are persisted, but the Child table does not have a value for entity_details_id. The other values are correct (I do have Entity.entity_details_id correctly set), but this one does not work. I'm not sure if I'm missing something or if I'm just doing it wrong.

Hibernate Composite Key Join

I'm trying to use Spring Data to perform joined queries but one of my tables has a Composite Key and I'm not sure how to map the entities.
Here is an analogy of the data model:
table: device
pk=model_id
pk=serial_id
...
table: device_settings
pk=device_settings_id
fk=model_id
fk=serial_id
...
Here is an analogy of the code, which doesn't compile due to a "mappedby" attribute that is isn't present.
#Entity
#Table(name = "device_settings")
public class DeviceSettings {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "device_settings_id")
private Long id;
// Pretty sure this is the problem
#OneToMany(targetEntity = Device.class, mappedBy = "deviceKey", cascade = {CascadeType.MERGE}, fetch = FetchType.EAGER)
#JoinColumns({
#JoinColumn(name = "model_id", referencedColumnName = "model_id"),
#JoinColumn(name = "serial_id", referencedColumnName = "serial_id")})
private List<Device> devices;
}
#Entity
#Table(name = "device")
public class Device {
#Id
private DeviceKey deviceKey;
}
...
}
#Embeddable
public class DeviceKey implements Serializable {
private static final long serialVersionUID = -1943684511893963184L;
#Column(name = "model_id")
private Long modelId;
#Column(name = "serial_id")
private Short serialId;
}
Associations marked as mappedBy must not define database mappings like #JoinTable or #JoinColumn
To achieve your scenario you have to define #ManyToOne:
#ManyToOne(cascade = {CascadeType.MERGE}, fetch = FetchType.EAGER)
#JoinColumns({
#JoinColumn(name = "model_id", referencedColumnName = "model_id"),
#JoinColumn(name = "serial_id", referencedColumnName = "serial_id")})
private Device device;
This will end up model_id, serial_id, device_settings_id
or
Define #JoinColumn in Device Entity
Entities:
DeviceSettings :
#Entity
#Table(name = "device_settings")
public class DeviceSettings {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "device_settings_id")
private Long id;
#OneToMany( mappedBy = "deviceSettings", cascade = {CascadeType.MERGE}, fetch = FetchType.EAGER)
private List<Device> devices;
}
Device Entity :
#Entity
#Table(name = "device")
public class Device {
#EmbeddedId
private DeviceKey deviceKey;
#ManyToOne
#JoinColumn(name="device_settings_id")
private DeviceSettings deviceSettings;
//getters and setters
}
Note : you can decide which is the owner of the relationship and put your mappings accorindly either One Device has many device settings or other way around.

Hibernate ORM: Saving Parent Entity Saves the Children too?

I have the below JSON as input:
{
"type": "Student",
"numOfPeople": "1",
"tenantMembers": [
{
"firstName": "Chris",
"lastName": "C"
}
],
"tenantDetails": {
"firstName": "John",
"lastName": "J",
"email" "xyz#gmail.com"
}
}
I want to use this to do a save:
tenantRepo.save(tenant);
This should save the parent "Tenant" and the children "TenantMembers" and "TenantDetails".
But when I do it does with NULL 'tenant_id's in the children. (If I have foreign keys in the DB gives 'tenant_id' can't be null constraint exception)
My question is: Is this possible in Hibernate?
My models:
Parent class:
#Entity
#Table(name = "tenant")
public class Tenant {
#GeneratedValue
#Id
private Long id;
private String type;
#Column(name = "num_of_people")
private String numOfPeople;
#OneToMany(mappedBy = "tenant", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<TenantMember> tenantMembers;
#OneToOne(mappedBy = "tenant", cascade = CascadeType.ALL)
private TenantDetails tenantDetails;
TenantMember child class:
#Entity
#Table(name = "tenant_member")
public class TenantMember {
#GeneratedValue
#Id
private Long id;
#ManyToOne
#JoinColumn(name = "tenant_id")
private Tenant tenant;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
TenanatDetails child class:
#Entity
#Table(name="tenant_details")
public class TenantDetails {
#GeneratedValue
#Id
private Long id;
#OneToOne
#JoinColumn(name = "tenant_id")
private Tenant tenant;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
private String email;
EDIT:
Following up Dragan Bozanovic's suggestion, tried using #JsonIdentityInfo
for the three tables:
#Entity
#Table(name = "tenant")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class Tenant {
#Entity
#Table(name="tenant_details")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class TenantDetails {
#Entity
#Table(name = "tenant_member")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class TenantMember {
and did the following to save:
#RequestMapping(value = "/set", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public Tenant test(#RequestBody Tenant tenant) {
Tenant t = new Tenant();
t.setType(tenant.getType());
t.setNumOfPeople(tenant.getNumOfPeople());
tenantRepo.save(t);
tenant.setId(t.getId());
tenant.getTenantDetails().setTenant(tenant);
for(TenantMember member: tenant.getTenantMembers()) {
member.setTenant(tenant);
}
return tenantRepo.save(tenant);
}
Would this be the best approach that is possible?
Hibernate does save the children (hence the constraint violation) because of the cascading options you specified, but it does not save the relationship information (join column value) in your case.
TenantMember and TenantDetails are the owners of the association with Tenant (mappedBy attributes in the association annotations in Tenant).
That means that you have to properly update the tenant field in the TenantMember and TenantDetails instances, because Hibernate ignores inverse side of the association when maintaining the relationship.

Hibernate #ManyToOne cascade children constraint exception

Im facing a little problem here.
I have two entities: Parent and Child, Parent has a List annotated #OneToMany.
The problem is when I try to insert a new Parent, it crashes when persisting the children, because the Parent Id was not generated yet.
Is that a fix for it?
#Entity
#Table(name = "PRODUTO")
public class Parent extends BaseEntity
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID_PRODUTO")
private Integer produtoId;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "produtoId", orphanRemoval = true)
// #JoinTable(name = "PRODUTO_TAMANHO", joinColumns = #JoinColumn(name = "ID_PRODUTO"))
#OrderBy("preco ASC")
private List<Child> children;
}
#Entity
#IdClass(Child.PrimaryKey.class)
#Table(name = "PRODUTO_TAMANHO")
public class Child extends BaseEntity
{
public static class PrimaryKey extends BaseEntity
{
private static final long serialVersionUID = -2697749220510151526L;
private Integer parentId;
private String tamanho;
//rest of implementation
}
#Id
#Column(name = "ID_PRODUTO")
private Integer parentId;
#Id
#Column(name = "TAMANHO")
private String tamanho;
#ManyToOne
#JoinColumn(name = "ID_PRODUTO", insertable = false, updatable = false)
private Parent parent;
}
I think if I persist firstly the parent, than persist the children would be a bad approach.
Is that a way to persist the children, when persisting Parent?
Thanks!
Guys, the exception that occurs when persisting Parent is:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'ID_PRODUTO' cannot be null
I found a guy facing the same problem: #OneToMany and composite primary keys? (maybe it's better explained)
Here is my insertion code
Parent parent = new Parent();
Child child1 = new Child();
child1.setTamanho("Tamanho 1");
child1.setParent(parent);
Child child2 = new Child();
child2.setTamanho("Tamanho 1");
child2.setParent(parent);
List<Child> children = parent.getChildren();
children.add(child1);
children.add(child2);
save(parent);
//all of this instances, is coming from a view.jsp binded by spring, I can confirm it is exactly like this, with parentId as null
//when updating, it goes perfectly
There are few problems with your entity class.
mappedBy attribute in Parent entity should be set to parent: mappedBy="parent".
In child entity, below field is not required.
#Id
#Column(name = "ID_PRODUTO", nullable = true)
private Integer parentId;
Updated entity is like this.
#Entity
#Table(name = "PRODUTO")
public class Parent extends BaseEntity
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID_PRODUTO")
private Integer produtoId;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "parent", orphanRemoval = true)
// #JoinTable(name = "PRODUTO_TAMANHO", joinColumns = #JoinColumn(name = "ID_PRODUTO"))
#OrderBy("preco ASC")
private List<Child> children;
}
#Entity
#IdClass(Child.PrimaryKey.class)
#Table(name = "PRODUTO_TAMANHO")
public class Child extends BaseEntity
{
public static class PrimaryKey extends BaseEntity
{
private static final long serialVersionUID = -2697749220510151526L;
private Integer parentId;
private String tamanho;
//rest of implementation
}
/* #Id
#Column(name = "ID_PRODUTO", nullable = true)
private Integer parentId; */ // Not required.
#Id
#Column(name = "TAMANHO")
private String tamanho;
#ManyToOne
#JoinColumn(name = "ID_PRODUTO", insertable = false, updatable = false)
private Parent parent;
}
Also I do not understand child inner class for primary key. Use proper primary as you have used parent.
And while inserting set both parent to child and child to parent. See my blog for more details.Here

#OneToOne relationship with additional constraint

Suppose, we have two entities, first one:
#Entity
#Table(name = "entitya")
public class EntityA {
#Id
#Column(name = "id")
private Long id;
#Column(name = "name")
private Long name;
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private Set<EntityB> childEntities;
}
and the second:
#Entity
#Table(name = "entityb")
public class EntityB {
#Id
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "master")
private Boolean master;
#ManyToOne
#JoinColumn(name = "parent")
private EntityA parent;
}
So far, so good. However underlying database tables and constrains enforce that for any entityA there can be only one EntityB with boolean field master set to true. I can extract it by adding following method to entityA:
public entityB getMasterChild() {
for(entityB ent : childEntities) {
if(ent.isMaster()) {
return ent;
}
}
}
The question is, can I create #OneToOne relationship in EntityA that can express that rule, so that entityA can have additional masterChild member of type entityB?
If I understood you correctly you want to create/define a relationship between two entities based on a value of some entity's property. The think is that relationship between entities is defined on entities count (how many entities can has the other entity) and not on some entity's property value.
However
If you really want to use #OneToOne mapping for masterChild I would recommend creating a separate table/entity for it. Once this is done, you can include this new MasterChild entity into EntityA and annotate it with #OneToOne.
Here is new MasterChild entity
#Entity
public class MasterChild extends EntityB{
#Id
#Column(name = "id")
private Long id;
}
Note that I have removed 'master' from EntityB as it is no longer needed
#Entity
#Table(name = "entityb")
public class EntityB {
#Id
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#ManyToOne
#JoinColumn(name = "parent")
private EntityA parent;
}
And here is modified EntityA
#Entity
#Table(name = "entitya")
public class EntityA {
#Id
#Column(name = "id")
private Long id;
#Column(name = "name")
private Long name;
#OneToOne
private MasterChild master;
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private Set<EntityB> childEntities;
}

Categories

Resources