JPA Hibernate MapsId for grand child - spring

I am trying to save an entity in JPA 2.1.
I have three tables - MVCollection, MVCollectionVersion (which is versions of MVCollection) and MVBelongsCollection(which is the items belonging to a version).
The Primary Key of MVCollection is a generated sequence number.
When I generate a collection with a version (without any items) I am using #MapsId, and the ID generated is used within the child. However I cannot seem to understand how I can replicate this with the items.
Here are snippets from the code so far :
#Entity
public class MVCollection {
#GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "MVCOLLECTION_SEQ")
#SequenceGenerator(name = "MVCOLLECTION_SEQ",
sequenceName = "VMD.MVCOLLECTION_SEQ")
#Id
#Column(name = "MVCOLLECTIONID")
private Long id;
MVCollectionVersion
#Entity
public class MVCollectionVersion {
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "versionId", column = #Column(name = "MVCVSNID")) })
private MVCollectionVersionId id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumns({
#JoinColumn(name = "MVCOLLECTIONID", referencedColumnName = "MVCOLLECTIONID"),
})
#MapsId("mvCollectionId")
private MVCollection mvCollection;
#OneToMany(fetch = FetchType.LAZY, mappedBy="mvCollectionVersion", cascade={CascadeType.MERGE, CascadeType.PERSIST})
private List<MVBelongsCollection> mvCollectionItems;
MVCollectionId
#Embeddable
public class MVCollectionVersionId implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 2551937096790427792L;
private Long mvCollectionId;
private Integer versionId;
MVBelongsCollection
#Entity
public class MVCollectionItems
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "managedViewId", column = #Column(name = "MANAGEDVIEWID")),
#AttributeOverride(name = "mvCollectionId", column = #Column(name = "MVCOLLECTIONID")),
#AttributeOverride(name = "versionId", column = #Column(name = "MVCVSNID")) })
private MVBelongsCollectionId id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "MVCOLLECTIONID", referencedColumnName = "MVCOLLECTIONID"),
#JoinColumn(name = "MVCVSNID", referencedColumnName = "MVCVSNID") })
private MVCollectionVersion mvCollectionVersion;
and finally MVBelongsCollectionId
#Embeddable
public class MVBelongsCollectionId implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
#Column ( name = "MANAGEDVIEWID", nullable = false, precision = 38)
private Long managedViewId;
#Column ( name = "MVCOLLECTIONID", nullable = false, precision = 38)
private Long mvCollectionId;
#Column ( name = "MVCVSNID", nullable = false, precision = 38)
private Integer versionId;
if I try to create a collection with a version and with belongsCollection items, the create fails as it states the mvCollectionId field is null
"ORA-01400: cannot insert NULL into ("VMD"."MVBELONGSCOLLECTION"."MVCOLLECTIONID")"
Therefore I tried to add #MapsId as I had done with MVCollectionVersion.
public class MVBelongsCollection {
/**
* primary key
*/
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "managedViewId", column = #Column(name = "MANAGEDVIEWID")),
//#AttributeOverride(name = "mvCollectionId", column = #Column(name = "MVCOLLECTIONID")),
#AttributeOverride(name = "versionId", column = #Column(name = "MVCVSNID")) })
private MVBelongsCollectionId id;
/**
* collection that this joins to.
*/
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("mvCollectionId")
#JoinColumns({
#JoinColumn(name = "MVCOLLECTIONID", referencedColumnName = "MVCOLLECTIONID"),
#JoinColumn(name = "MVCVSNID", referencedColumnName = "MVCVSNID") })
private MVCollectionVersion mvCollectionVersion;
However in Eclipse, this shows an error on the #ManyToOne Annotation of
The type of the ID mapped by the relationship 'mvCollectionVersion' does not agree with the primary key class of the target entity.
If I start the process, I get
Caused by: org.hibernate.MappingException: Repeated column in mapping for entity: MVBelongsCollection column: MVCVSNID (should be mapped with insert="false" update="false")
I have tried adding insertable = false, and updatable = false to my #JoinColumn, the #AttributeOVerride and the underlying ID class but still get the same error.
This only happens when the #MapsId is present.
I am now at a loss how I get the MVBelongsCollection to use the generated MVCollectionId, or how I can stop both the eclipse and the runtime error.
If anyone can help I would be grateful.
Thanks in advance

I found the error of my ways...
I needed to use the same embeddedId throughout.
Therefore the MVBelongsCollectionId needed to change to include the embedded id of the parent class:
#Embeddable
public class MVBelongsCollectionId implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
#Embedded
#AttributeOverrides( {
#AttributeOverride(name = "mvCollectionId", column = #Column(name = "MVCOLLECTIONID", nullable = false, precision = 38, scale = 0)),
#AttributeOverride(name = "versionId", column = #Column(name = "MVCVSNID", nullable = false, precision = 8, scale = 0))
})
MVCollectionVersionId collectionVersionId;
#Column ( name = "MANAGEDVIEWID", nullable = false, precision = 38)
private Long managedViewId;
....

Related

Spring Data persisting Phantom Child with Null value - not null property references a null or transient value

I have the following Entities in my Project:
#Getter
#Setter
#Entity
#Table(uniqueConstraints = #UniqueConstraint(columnNames = { "purchaseId" }))
public class Purchase {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long purchaseId;
#Column(unique = true, nullable = false, length = 15)
private String purchaseNo;
#Column(nullable = false, length = 15)
private String batchCode;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "supplier.supplierId", foreignKey = #ForeignKey(name = "FK_purchase_supplier"), nullable = false)
private Supplier supplier;
#Column(nullable = false)
private LocalDate purchaseDate;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "purchaseId", nullable = false)
private List<PurchaseItem> purchaseItems;
private Double totalAmount;
#ManyToOne
#JoinColumn(name = "userId", nullable = false, foreignKey = #ForeignKey(name = "FK_invoice_purchases"))
private User staff;
#Column(length = 100)
private String remarks;
#Column(nullable = false, updatable = false)
#CreationTimestamp
private LocalDateTime createdAt;
private boolean isDeleted = false;
}
#Getter
#Setter
#Entity
#Table(uniqueConstraints = #UniqueConstraint(columnNames = {"purchaseItemId"}))
public class PurchaseItem {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long purchaseItemId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "purchaseId", insertable = false, updatable = false, foreignKey = #ForeignKey(name="FK_purchase_item"))
private Purchase purchase;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "productId", foreignKey = #ForeignKey(name="FK_product_item"), nullable = false)
private Product product;
private Double itemAmount;
#Column(nullable = false)
private Double quantity;
private Double itemTotalAmount;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#PrimaryKeyJoinColumn(foreignKey = #ForeignKey(name = "FK_purchacase_item_batch"))
private PurchaseProductBatch productPurchaseBatch;
public void setPurchaseProductBatch() {
PurchaseProductBatch productPurchaseBatch = new PurchaseProductBatch();
productPurchaseBatch.setProduct(this.product);
productPurchaseBatch.setQuantity(this.quantity);
productPurchaseBatch.setPurchaseItem(this);
this.productPurchaseBatch = productPurchaseBatch;
}
}
#Getter
#Setter
#Entity
#Table()
public class PurchaseProductBatch{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long productBatchId;
#ManyToOne(cascade = CascadeType.DETACH)
#JoinColumn(name = "productId", foreignKey = #ForeignKey(name = "FK_product_purch"))
private Product product;
private Double quantity;
#OneToOne(fetch = FetchType.EAGER)
#MapsId
private PurchaseItem purchaseItem;
private boolean isDeleted = false;
#OneToMany(cascade = CascadeType.PERSIST)
#JoinColumn(name = "productBatchId", foreignKey = #ForeignKey(name = "FK_purchase_batch_qty"))
private Set<InvoicePurchaseBatchQuantity> invoicePurchaseBatchQuantities;
}
During Purchase Insert, everything works fine. However, if I update the Purchase record in the database and add new PurchaseItem entry, I encounter the issue below:
org.springframework.dao.DataIntegrityViolationException: not-null property references a null or transient value : com.be.entity.PurchaseItem.product; nested
I have debugged my application and I see that there is a Product instance inside all of the PurchaseItem. When I commented out the PurchaseProductBatch inside PurchaseItem, everything works fine so I conclude that it is the causing the issue. However, I don't understand how and why JPA seems to create phantom PurchaseItem Records with no value.
Also, if I only update an existing PurchaseItem entry in Purchase, I don't encounter any issues.

JPA and Hibernate One To One Shared Primary Key Uni-directional Mapping in Spring Boot

I want to have one-to-one uni-directional mapping with 2 child entities using shared primary key. Below are model classes
public class Template implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "templatekey")
Integer templateKey;
#Column(name = "templateid", unique = true)
String templateId;
#OneToOne(cascade = CascadeType.ALL, optional = false)
#PrimaryKeyJoinColumn(name = "templatekey", referencedColumnName = "templatekey")
InstantOfferNoEsp instantOfferNoEsp;
#OneToOne(cascade = CascadeType.ALL, optional = false)
#PrimaryKeyJoinColumn(name = "templatekey", referencedColumnName = "templatekey")
Mobile mobile;
//constructor , setter and getters
}
Child 1 :
public class Mobile implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "templatekey")
Integer templateKey;
String mobileNewUrl;
//constructor , setter and getters
}
Child 2:
public class InstantOfferNoEsp {
#Id
#Column(name = "templatekey")
Integer templateKey;
String offerCodeType;
String headerUrl;
//constructor , setter and getters
}
I want templateKey as PK in all tables. and I am calling templateRepository.save(template); to save all entities at once but its not working and getting ids for this class must be manually assigned before calling save() error.
Any suggestions would be of great help. Thank you.
I was able to do what you want with bidirectional #OneToOne like below:
#Entity
public class Mobile {
#Id
Integer templateKey;
#OneToOne
#MapsId
#JoinColumn(name = "templatekey")
Template template;
// ...
}
#Entity
public class InstantOfferNoEsp {
#Id
Integer templateKey;
#OneToOne
#MapsId
#JoinColumn(name = "templatekey")
Template template;
// ...
}
#Entity
public class Template {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "templatekey")
Integer templateKey;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "template", optional = false)
InstantOfferNoEsp instantOfferNoEsp;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "template", optional = false)
Mobile mobile;
// ...
public void setMobile(Mobile mobile)
{
this.mobile = mobile;
this.mobile.setTemplate(this);
}
public void setInstantOfferNoEsp(InstantOfferNoEsp instantOfferNoEsp)
{
this.instantOfferNoEsp = instantOfferNoEsp;
this.instantOfferNoEsp.setTemplate(this);
}
}
and an example of saving:
Mobile mobile = new Mobile();
mobile.setMobileNewUrl("MOB1");
InstantOfferNoEsp instant = new InstantOfferNoEsp();
instant.setOfferCodeType("INST_OFF1");
Template template = new Template();
template.setTemplateId("TMP1");
template.setInstantOffer(instant);
template.setMobile(mobile);
entityManager.persist(template);
P.S. The following mapping works too, but only if we set Template.templateKey manually.
#Entity
public class Template
{
#Id
// #GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "templatekey")
Integer templateKey;
#OneToOne(cascade = CascadeType.ALL, optional = false)
#JoinColumn(name = "templatekey", insertable = false, updatable = false)
InstantOfferNoEsp instantOfferNoEsp;
#OneToOne(cascade = CascadeType.ALL, optional = false)
#JoinColumn(name = "templatekey", insertable = false, updatable = false)
Mobile mobile;
// ...
}
and an example of saving:
Mobile mobile = new Mobile();
mobile.setMobileNewUrl("MOB1");
InstantOfferNoEsp instant = new InstantOfferNoEsp();
instant.setOfferCodeType("INST_OFF1");
Template template = new Template();
template.setTemplateKey(20);
template.setTemplateId("TMP1");
template.setInstantOffer(instant);
template.setMobile(mobile);
entityManager.persist(template);
Also I would suggest your explicitly specify what generation strategy you want to use (do not use GenerationType.AUTO) and use corresponding object wrapper classes instead of primitive types for #Id fields.

How to fix Error ManyToMany in #RestController?

I'm an entity Story:
#Entity
#Table(name = "story", schema = "")
#Data
public class Story implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "sID", unique = true, nullable = false)
private Long sID;
#Column(name = "vnName", nullable = false)
private String vnName;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "_scategory",
joinColumns = {#JoinColumn(name = "sID", nullable = false)},
inverseJoinColumns = {#JoinColumn(name = "cID", nullable = false)})
private List<Category> categoryList;
}
And Entity Category:
#Entity
#Table(name = "category", schema = "", uniqueConstraints = {#UniqueConstraint(columnNames = {"cMetatitle"}),
#UniqueConstraint(columnNames = {"cName"})})
#Data
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "cID", unique = true, nullable = false)
private Integer cID;
#Column(name = "cName", unique = true, nullable = false, length = 150)
private String cName;
#ManyToMany(mappedBy = "categoryList")
private List<Story> storyList;
}
But when I grab the Story in RestController, I get the following error message:
WARN http-nio-8080-exec-9
o.s.w.s.m.s.DefaultHandlerExceptionResolver:234 - Failure while trying
to resolve exception
[org.springframework.http.converter.HttpMessageNotWritableException]
java.lang.IllegalStateException: Cannot call sendError() after the
response has been committed
Can anybody show me how to fix it? Thank you!

Is there any way to delete entities from child entity using JPA?

I have 3 tables Table A, B, C. Table A is associated #OneToMany with Table B. Table B is associated #ManyToOne with Table C. Now when I find by Id of Table A, I am able to get details of A,B,C. But when I persist / delete, It is affecting only Table A&B. Table C is unaffected.
Is this possible in JPA to delete entities from child entity? Googled lot, but could not find any clue.
Below are the entity models of all the three tables
#Entity
#Table(name = "FEATUREMASTER")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class FeatureMaster implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#Column(name = "FGID")
private String featureid;
#Column(name = "FEATURENAME", nullable = false, unique = false)
private String featurename;
#Column(name = "DESCRIPTION", nullable = true, unique = false)
private String description;
#Column(name = "LIBNAME", nullable = true, unique = false)
private String libname;
#Column(name = "ISENABLED", nullable = false, unique = false)
private String isenabled;
#Column(name = "EDRULEGRP", nullable = true, unique = false)
private String edrulegrp;
// Do Not use - [orphanRemoval = true & CascadeType.ALL]- If used, deletion is not happening
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "FGID")
private List<CfgMaster> parameters;
// Getters and Setters
}
#Entity
#Table(name = "CFGMASTER")
public class CfgMaster implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#EmbeddedId
private CfgMasterPK id;
#Column(name = "CONFIGNAME", length = 45, nullable = true, unique = false)
private String parameter_name;
#Column(name = "CONFIGTYPE", length = 20, nullable = true, unique = false)
private String type;
#Column(name = "SUBPARAM", nullable = true, unique = false)
private Integer subparam;
#Column(name = "CONFIGDESCRIPTION", nullable = true, unique = false)
private String description;
#Column(name = "CONFIGLIMITFROM", nullable = true, unique = false)
private String from;
#Column(name = "CONFIGLIMITTO", nullable = true, unique = false)
private String to;
#ManyToOne(cascade = {CascadeType.ALL}, optional = true, fetch = FetchType.LAZY )
// #ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
#NotFound(action=NotFoundAction.IGNORE) // This is required to handle when no CfgData is found
#JoinColumns({
#JoinColumn(name = "FGID", insertable = false, updatable = false),
#JoinColumn(name = "DATAKEY", insertable = false, updatable = false)
})
private CfgData cfgData;
//Getters and Setters
}
#Entity
#Table(name = "CFGDATA")
public class CfgData implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
/*#EmbeddedId
private CfgDataPK id;*/
#Id
#Column(name = "FGID")
private String fgid;
#Id
#Column(name = "DATAKEY")
private String datakey;
#Column(name = "EPID", nullable = false, unique = false)
private int epid;
#Column(name = "RESERVED1", length = 45, nullable = true, unique = false)
private String reserved1;
#Column(name = "VALUE1", length = 100, nullable = true, unique = false)
private String value1;
#Column(name = "VALUE2", length = 100, nullable = true, unique = false)
private String value2;
//Getters and Setters
}
The problem I am facing is, I am not able to delete/save the entities of CfgData by passing FeatureMaster's primary id. Any operation I do is affecting only parent &child, not the grand child (CfgData) I tried a lot googling, but I cant find the solution.

Spring JpaRepository manyToMany bidirectional should save instead of update

if got a language table and a system table with a many-to-many relationship:
Language:
#JsonAutoDetect
#Entity
#Table(name = "language")
public class Language implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "language_id", nullable = false)
private int languageId;
#Column(name = "language_name", nullable = false)
private String languageName;
#Column(name = "language_isocode", nullable = false)
private String languageIsoCode;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "system_language", joinColumns = {#JoinColumn(name = "language_id", updatable = false)},
inverseJoinColumns = {
#JoinColumn(name = "system_id", updatable = false)}, uniqueConstraints = {
#UniqueConstraint(columnNames = {
"language_id",
"system_id"
})})
private List<System> systems;
public Language() {
}
// GETTER & SETTERS
// ....
}
System
#JsonAutoDetect
#Entity
#Table(name = "system")
public class System implements Serializable {
#Id
#Column(name = "system_id", nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer systemId;
#Column(name = "system_name", nullable = false, unique = true)
private String systemName;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "university_id", nullable = false)
private University university;
#JoinColumn(name = "calender_id", nullable = false)
#OneToOne(fetch = FetchType.EAGER)
private Calendar calender;
#OneToMany(mappedBy = "system")
#LazyCollection(LazyCollectionOption.FALSE)
private List<SystemUserRole> systemUserRoleList;
#OneToMany(mappedBy = "system")
#LazyCollection(LazyCollectionOption.FALSE)
private List<Role> roleList;
#OneToOne(mappedBy = "system")
#LazyCollection(LazyCollectionOption.FALSE)
private CsmUserEntity csmUserEntity;
#ManyToMany(mappedBy = "systems")
#LazyCollection(LazyCollectionOption.FALSE)
private List<Language> languages;
public System() {
}
// GETTER & SETTERS
// ....
}
When im writing a first dataset (systemId=1, language_id=20) into the table, everything works fine. But when i try to write a second dataset with the same language_id but with other system_id (systemId=2, language_id=20), then the existing dataset gets updated. But i want to have a new dataset instead. What can i do?
Thanks in advance!

Resources