JPA insertable = false updatable = false id not updated on creation - java

We have 2 entities with a #ManyToOne relationship.
When we create an instance of EntityB within a #Transactional method, entityAId (insertable = false updatable = false), is not updated automatically - even though that the entityA instance was already persisted.
Is there a way around this? Do we have to update it manually in the ctor?
#Entity
public class EntityA {
#Id
#GeneratedValue
private Long id;
public EntityA() {
super();
}
...
}
#Entity
public class EntityB {
#Id
#GeneratedValue
private Long id;
#ManyToOne(optional = true, fetch = FetchType.LAZY)
private EntityA entityA;
#Column(name = "entityA_id", insertable = false, updatable = false)
private Long entityAId;
public EntityB() {
super();
}
public EntityB(EntityA entityA) {
super();
this.entityA = EntityA;
}
...
}
EDIT: Also tried the following, but still entityAId = null within the transaction (even though entityA was persisted before).
#Entity
public class EntityB {
#Id
#GeneratedValue
private Long id;
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "entityA_id", insertable = false, updatable = false)
private EntityA entityA;
#Column(name = "entityA_id")
private Long entityAId;
...
}

Hibernate is not going to populate entity fields 'on the fly' (when you change some other fields or similar). It is also not going to do it on persist/flush (exceptions being some special fields like id and version).
Non-insertable/non-updatable fields are populated when entity instances are fetched from the DB. So, to make such fields initialized/refreshed by Hibernate in the same transaction in which you perform changes to the underlying columns they are mapped to, you should first flush the session and then either:
clear the session and re-read the entities;
or, refresh the entities for which you want to reflect such kind of changes.

To update the id field a persist action of the object is required. By default, objects in field entityA are not automatically persisted when persisting an object of EntityB.
I see two possible solutions:
A) Use cascade
#ManyToOne(optional = true, fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST})
private EntityA entityA;
(or use CascadeType.ALL)
B) Persist entityA manually
entityManager.persist(entityA);

To me your mapping does not look right. #ManyToOne or any other association defined between entities but you have defined it on entityAId. Ideally it should be entity (an here you should use insertable = false updatable = false)and you should have separate field entityAId with #column defined on it. Now you should update this field yourself.
If you want to handle hibernate for you remove insertable = false updatable = false

Related

Hibernate Multiple #OneToMany bound to same entity type

I have yet another #OneToMany question. In this case, I'm trying to model a person having a list of excluded people they shouldn't be able to send items to. This is a Spring Boot app using JPA.
In the code below, the exclusions list populates properly but the excludedBy List does not. Because of this, I believe that is causing the deletion of a Person that is excluded by another person to fail because the Exclusion in excludedBy is not mapped on the object properly.
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue
#Column(nullable = false)
Long id;
...
#OneToMany(mappedBy = "sender", cascade = { CascadeType.ALL })
List<Exclusion> exclusions = new ArrayList<>();
//This is not getting populated
#JsonIgnore
#OneToMany(mappedBy = "receiver", cascade = { CascadeType.ALL })
List<Exclusion> excludedBy = new ArrayList<>();
...
}
#Entity
#Table(name = "exclusions")
public class Exclusion {
#Id
#GeneratedValue
#Column(nullable = false)
Long id;
#ManyToOne
#JsonIgnore
Person sender;
#ManyToOne
Person receiver;
...
}
I would expect that this would have mapped the bidirectional relationship properly and as such the excludedBy List would be populated as well.
Any wisdom on this matter would be great!
1 - An #Id is by default not nullable, not required:
#Column(nullable = false)
2 - There is no need for an #Id in this class. Both sides of the exclusion are together unique. Not needed:
#Id
#GeneratedValue
Long id;
3 - An "Exclusion" requires both an excludedBy and an excluded, give them names that match and they are your #Id. It is a 2 way ManyToMany relationship.
#Entity
#Table(name = "exclusions")
public class Exclusion {
#Id
#ManyToMany // An ID so not optional, so no need for (optional = false)
Person excludedBy;
#Id
#ManyToMany // An ID so not optional, so no need for (optional = false)
Person excluded;
}
Entity Exclusion always knows both sides of the story.
#ManyToMany(mappedBy = "excludedBy", cascade = { CascadeType.ALL })
List<Exclusion> excluded = new ArrayList<>();
#ManyToMany(mappedBy = "excluded", cascade = { CascadeType.ALL })
List<Exclusion> excludedBy = new ArrayList<>();
Tip: JSON DTOs shouldn't be defined in your JPA DTOs, otherwise you can't change your internal data model independently of your external API model.
I had this problem in the past. Your key problem ist that your ORM Mapper hibernate does not know which of your database entries need to be assinged to exclusions and which are assiged to excludedBy. You need a discriminator and add the constraint in your select. I would propose a solution that looks something like this:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "PRIMARY_KEX_IN_EXCLUSION_TABLE", referencedColumnName = "id")
#Where(clause = "is_excluded_by = 0")
private Set<Exclusion> exclusions;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "PRIMARY_KEX_IN_EXCLUSION_TABLE", referencedColumnName = "id")
#Where(clause = "is_excluded_by = 1")
private Set<Exclusion> excludedBy;
the value isExcludedBy needs to be a database column, part of your Entity and set in your code manually.
I think you also need to use Set instead of List when having multiple collections in one Entity. https://vladmihalcea.com/spring-data-jpa-multiplebagfetchexception/

#ManyToOne relationship is not audited

I have two entities:
#Entity
#Table(name = "entity_a")
#Audited
public class EntityA {
#Column(name = "entity_a_uuid", columnDefinition = "char", updatable = false)
#Type(type = "uuid-char")
private UUID uuid = UUID.randomUUID();
/**
* #deprecated in favor of uuid
*/
#Deprecated
#Id
#GeneratedValue
#Column(name = "entity_a_id")
private Integer id;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "entity_a_id", nullable = false)
#BatchSize(size = 100)
#NotAudited
private List<EntityB> entityBs = new ArrayList<>();
}
and
#Entity
#Audited
#Table(name = "entity_b")
public class EntityB {
#Id
#Column(name = "entity_b_uuid", columnDefinition = "char", updatable = false)
#Type(type = "uuid-char")
private UUID uuid = UUID.randomUUID();
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "entity_a_id", nullable = false, insertable = false, updatable = false)
private EntityA entityA;
}
Each is correctly audited into two tables entity_a_audit and entity_b_audit. However, the entity_a_id field in entity_b_audit is always null.
Some details:
If I do not have the #NotAudited in EntityA, I will get an error that says something to the effect of: The table EntityA_EntityB_audit does not exist. This seems like it's trying to audit them as a single table, which I do not want.
I have tried applying #Audited(targetAuditMode = elationTargetAuditMode.NOT_AUDITED) to each side. If applied only in EntityA, I get the above error. If applied only in EntityB, nothing changes. If applied in both, I get the error above. If applied to neither, I get the error above.
I suspect the entity_a_id is null in entity_b_audit because the id isn't generated until EntityA hits the DB. entity_a_id is auto-incrementing in the entity_a table.
Using hibernate-envers-5.4.32.Final.jar
Ultimately, I would like for entity_a_id to not be null in entity_b_audit. Alternatively, if I could somehow get entity_a_uuid to be captured instead, that would also suffice.
Any help would be greatly appreciated! Thanks.
You marked the column as insertable = false, updatable = false, so there is nothing to audit here, because Hibernate can never change the value of that column.

Understanding Hibernate cascade behavior with multiple item references

I don't understand a certain Hibernate behavior regarding an object that should be persisted by a Cascade.ALL setting, but is then regarded as unsaved in another reference in the same transaction.
Example:
OrderProposal proposal = new OrderProposal();
ProposalLineItem proposalLine = new ProposalLineItem();
proposalLine.setProposal(proposal);
proposal.addLineItem(proposalLine); //to be saved by cascade.all via proposal
saveOrUpdate(proposal);
Order order = new Order();
OrderLineItem orderLine = new OrderLineItem(); //to be saved by cascade.all via order
orderLine.setProposalLine(proposalLine); //proposalLine is not mapped as cascaded from orderLine
proposalLine.setOrderLine(orderLine);
order.addLineItem(orderLine);
saveOrUpdate(order);
If this is run in a single transaction, Hibernate throws when processing the cascades of the order object:
org.hibernate.TransientPropertyValueException: object references an
unsaved transient instance - save the transient instance before flushing : orderLine.proposalLine
Do I have to save proposalLine explicitly for getting this to work?
EDITED
Here are the affected hibernate mappings:
#Entity
#Access(AccessType.FIELD)
#Proxy(lazy = false)
#OptimisticLocking(type = OptimisticLockType.VERSION)
public class ProposalLineItem {
#ManyToOne(optional = false)
#JoinColumn(name = "proposal_id")
private OrderProposal proposal;
#OneToOne(mappedBy = "proposalLine")
#NotFound(action = NotFoundAction.EXCEPTION)
private OrderLineItem orderLine;
}
#Entity
#OptimisticLocking(type = OptimisticLockType.VERSION)
#Proxy(lazy = false)
#Access(AccessType.FIELD)
public class OrderProposal {
#OneToMany(mappedBy = "proposal", orphanRemoval = true, fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#OrderColumn(name = "listIndex", nullable = false)
private List<ProposalLineItem> lineItems;
}
#Entity
#Proxy(lazy = false)
#OptimisticLocking(type = OptimisticLockType.VERSION)
#Access(AccessType.FIELD)
public class OrderLineItem {
#OneToOne(optional = false)
#NotFound(action = NotFoundAction.EXCEPTION)
#JoinColumn(name = "proposal_line_id", nullable = false)
private ProposalLineItem proposalLine;
}
#Entity
#OptimisticLocking(type = OptimisticLockType.VERSION)
#Proxy(lazy = false)
#Access(AccessType.FIELD)
public class Order {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#JoinColumn(name = "order_id")
#IndexColumn(nullable = false, name = "listIndex", base = 0)
private List<OrderLineItem> lineItems;
}
Cascade is a convenient feature to save the lines of code needed to
manage the state of the other side manually.
Let us suppose one scenario,where one object depend on the other one and you want to delete it how you will delete it? You have to fire the 1 after other query manually if cascading not there. So in hibernate cascading attribute is mandatory where you are define relationship between objects so by defining it child class object also going to effect if you make any change to Parent class object.
Now you have mentioned cascade = “all” you are telling cascading will be apply for insert/update/delete
Cascading can be apply save/update/delete etc.
Now if cascade = “none” then only Parent class object will effected and not the child class.

Confused regarding JPA

I have two classes.
public class Invoice {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "invoice_id", unique = true)
private int invId;
#OneToMany(mappedBy = "invoiceList", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private List<Item> itemList;
#Column(name = "invoice_amt", nullable = false)
private Double invAmt;
}
And,
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "item_id", unique = true)
private int itemId;
#ManyToOne(optional = false, targetEntity = Invoice.class)
#JoinColumn(name="invoice_id")
private List<Invoice> invoiceList;
}
I am new to JPA. So my understanding may not be accurate.
My understanding is that, if I save Invoice, the invoice_id of that instant should cascade down to invoice_id of all the items.
However, I see Item being saved but get null in place of invoice_id of the Item.
What am I missing?
UPDATE!!! UPDATE!!!
Ok so I changed the #ManyToOne to be a singular attribute and did objItem.setInvoice(objInvoice) and saved it. However, I still get NULL on invoice_id.
You are annotating a many-to-one relation, but use collections on both sides. This will not work. The one-side has to map the relation to a singular attribute. In your case, it would be
#ManyToOne
private Invoice invoice
Perhaps you rather need a many-to-many relation. In this case, you will need to change the annotations to #ManyToMany and get rid of the cascades (they tend not to work as expected from a many-side).
targetEntity attribute and the #JoinColumn annotation are redundant on the invoice attribute of Item.
In order for the Item to save the id of the related invoice, you first need to set the invoice attribute of the Item since item is the owning side (the one where the relation information is stored).
I'm not sure this is your only problem, but a 1:n relationship shouldn't have a List both ways. If you turn List<Invoice> into a simple Invoice object, you'll at least be closer to a solution. We can go from there if your code still fails.
public class Item {
#ManyToOne(optional = false, targetEntity = Invoice.class)
#JoinColumn(name = "invoice_id")
private Invoice invoice;
}
public class Invoice {
#OneToMany(mappedBy = "invoiceList", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private List<Item> itemList;
}

Open JPA Saving OneToMany , foreign key not set

I've two tables: TaStock and TaStockPrice. Field tastockid in table TaStockPrice is the foreign key to table TaStock.
#Entity
public class TaStock {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Integer id
#OneToMany(mappedBy = "taStock", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<TaStockPrice> tastockpriceList;
public void addTaStockPrice(TaStockPrice taStockPrice) {
if (taStockPrice == null) {
return;
}
taStockPrice.setTaStock(this);
if (tastockpriceList == null) {
tastockpriceList = new ArrayList<TaStockPrice>();
tastockpriceList.add(taStockPrice);
} else if (!tastockpriceList.contains(taStockPrice)) {
tastockpriceList.add(taStockPrice);
}
}
....
}
#Entity
public class TaStockPrice {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Integer id
#Column
private Integer tastockid;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "tastockid", nullable = false, updatable = false, insertable = false)
private TaStock taStock;
...
}
persisting taStock with Children
#Test
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void createTaStock() throws Exception {
TaStock taStock = new TaStock();
...
TaStockPrice taStockPrice = new TaStockPrice();
...
taStock.addTaStockPrice(taStockPrice);
taStockService.persist(taStock);
}
I read that when persisting a parent class, hibernate automatically persist the children of that class. But instead, the following exception occurs:
javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: ERROR: null value in column "tastockid" violates not-null constraint
I removed private Integer tastockid" from TaStockPrice, and modified
#JoinColumn(name = "tastockid", nullable = false, updatable = false, insertable = true) to solve it.
You are setting the collection as being not insertable nor updateable. This way hibernate will never persist it.
You could set how hibernate should treat this relation using the cascade-setting in your annotation. For more information, here is a thorough blog-post on the subject: http://www.mkyong.com/hibernate/hibernate-cascade-example-save-update-delete-and-delete-orphan/.
Use below annotation on tastockpriceList.
#OneToMany
#Cascade(CascadeType.ALL)
#JoinColumn(name="tastock")
That should resolve the problem.
In order to enable save ability on a #OneToMany relation e.g.
#OneToMany(mappedBy="myTable", cascade=CascadeType.ALL)
private List<item> items;
Then you have to tell to your #ManyToOne relation is allowed to update myTable like this updatable = true
#ManyToOne
#JoinColumn(name="fk_myTable", nullable = false, updatable = true, insertable = true)

Categories