I am using Spring Data JPA + Hibernate. I need to get ID of entity after it's added to a list of other entity and saved. Here is the code:
#Entity
public class Product extends AbstractAuditable<Long> {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "product_id", nullable = false)
private List<Feedback> feedbacks = new ArrayList<Feedback>();
...
}
#Entity
public class Feedback extends AbstractPersistable<Long> {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "product_id", insertable = false, updatable = false, nullable = false)
private Product product;
...
}
public interface ProductRepository extends JpaRepository<Product, Long> {
}
Feedback feedback = new Feedback();
product.getFeedbacks().add(feedback);
productRepository.saveProduct(product);
feedback.getId(); // returns null
How to correctly get ID of feedback after it's saved?
You can execute refresh method from Hibernate Session interface (or EntityManager interface) to "re-read the state from the given instance from the underlying database". Example:
session.save(object);
session.flush();
session.refresh(object);
You can find more information here:
Hibernate calling get() after save() on the newly created record within the same transaction
Hibernate: Refresh, Evict, Replicate and Flush
Hibernate calling get() after save() on the newly created record within the same transaction
Force refresh of collection JPA entityManager
Related
We have code like this (i simplified the code to make it more clear):
#Entity
#Table(name="storages")
class Storage {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name = "storage_items", joinColumns = #JoinColumn(name = "storage_id"), inverseJoinColumns = #JoinColumn(name = "item_id"))
private Set<Item> items;
void putItemToStorage(Session session) {
Item item = new Item();
item.setStorage(this);
session.save(item);
items.add(item);
}
}
#Entity
#Table(name="items")
class Item {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "storage_id")
private Storage storage;
public void setStorage(Storage storage) {
this.storage = storage;
}
}
We call 'putItemToStorage' in a transaction, but in hibernate 5.5 it causes the following error, while in hibernate 5 same code worked like a charm:
> javax.persistence.PersistenceException:
> org.hibernate.exception.ConstraintViolationException: could not execute statement
> ...
> caused by org.postgresql.util.PSQLException: ERROR: insert or update on table
> "storage_items" violates foreign key constraint
> "fkla3c4upmtw4myssb3bfg2svkj" Detail: Key (storage_id)=(164) is not
> present in table "items".
So, hibernate 5 resolved both inserts into items table and storage_items table and worked as intended (adding both item to items table and linking the item to corresponding storage through joining table storage_items), but in hibernate 5.5 it no longer works. I spent quite time in google and documentation and can't find what was changed or what am I doing wrong.
I had similar error in other place, where I temporarily resolved it separating saving of an object and inserting the object into 2 separate transactions (it works, but it's definitely not a right solution), so, any help how to fix it correctly would be very much appreciated.
You should define mapping only on one side (the one that holds the relation), eg. like this:
#Entity
#Table(name="storages")
class Storage {
#OneToMany(mappedBy = "storage", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Item> items;
void putItemToStorage() {
Item item = new Item();
item.setStorage(this);
items.add(item);
}
}
#Entity
#Table(name="items")
class Item {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "storage_id", referencedColumnName = "id")
private Storage storage;
public void setStorage(Storage storage) {
this.storage = storage;
}
}
Notice that this approach will most likely result in 2 queries:
That will create Item record in DB with storage_id = null
That will update recods and set storage_id to value that it should be
To prevent it adjust annotations on field storage:
#Entity
#Table(name="items")
class Item {
#ManyToOne(optional = false)
#JoinColumn(name = "storage_id", referencedColumnName = "id", nullable = false, updatable = false)
private Storage storage;
}
You might also consider adding orphanRemoval = true to items in case you will want to delete record in DB.
#Entity
#Table(name="storages")
class Storage {
#OneToMany(mappedBy = "storage", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private Set<Item> items;
}
Your code then should looks like this:
// assumming within transaction context
var storage = // get from eg. EntityManager or JPA repository (in spring)
storage.putItemToStorage();
// There is no need to call EntityManager::persist or EntityManager::merge
// if you are withing transaction context
// and you are working with managed entity
// and have cascade = CascadeType.ALL
I have Many To Many with additional column. Here realization(getters/setters generated by lombok, removed for clarity):
public class User extends BaseEntity {
#OneToMany(mappedBy = "user",
orphanRemoval = true,
fetch = FetchType.LAZY
cascade = CascadeType.ALL,)
private List<UserEvent> attendeeEvents = new ArrayList<>();
}
#Table(
name = "UC_USER_EVENT",
uniqueConstraints = {#UniqueConstraint(columnNames = {"user_id", "event_id"})}
)
public class UserEvent extends BaseEntity {
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "event_id")
private Event event;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private User user;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "user_response_id")
private UserResponse userResponse;
}
public class Event extends BaseEntity {
#OneToMany(mappedBy = "event",
orphanRemoval = true,
fetch = FetchType.EAGER,
cascade = CascadeType.ALL)
private List<UserEvent> userEvents = new ArrayList<>();
}
I want this - when i delete Event, All "UserEvents" connected to it should be removed. And if I delete User, all "UserEvents" should be removed too.
I delete my event(eventRepository is Spring Jpa interface):
eventRepository.delete(event);
Then retrieving UserEvents from db:
List<UserEvent> userEvents = userEventsId.stream()
.map(id -> entityManager.find(UserEvent.class, id)).collect(Collectors.toList());
And there is collection with 2 items(this is count of UserEvents), but they all "null".
I can't understand what happening and how to do it right.
I need them deleted and when I check collection there should be 0, instead of 2.
The delete says marked for deletion, please try calling flush after deletion, and then find.
I guess find goes to the database, find the two rows, but when trying to instantiate them, find the entities marked for deletion and then you have this strange behaviour.
Recomendation: try to abstract more from the database and use less annotations. Learn the conventions of names for columns and tables (if you need to) and let JPA do its job.
Why I am getting this error? This is the portion of my daoImpl Im calling
#Transactional
#Repository
public class PersonDaoImpl implements PersonDao{
#Autowired
private SessionFactory sessionFactory;
#SuppressWarnings("unchecked")
#Override
#Transactional(readOnly=true)
public List<Person> getAllPersons(){
List<Person> persons = (List<Person>) sessionFactory.getCurrentSession()
.createCriteria(Person.class)
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.list();
return persons;
}
Portion of my person model
#OneToMany(fetch = FetchType.LAZY, mappedBy = "person", cascade = CascadeType.ALL)
private Set<Contact> contacts;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "PERSON_ROLE", joinColumns = {
#JoinColumn(name = "person_id", nullable = false, updatable = false) },
inverseJoinColumns = { #JoinColumn(name = "role_id",
nullable = false, updatable = false) })
private Set<Role> roles = new HashSet<Role>(0);
In my service impl, I convert model to dto using BeanUtils
What am I doing wrong?
My feeling is that in a time when you try to convert the entity to dto using BeanUtils the entity is already detached (e.g. outside persistence context/hibernate session). In your Person class you have a Set<Contact> of contacts which is loaded lazily - that is why it fails.
If Contact does not contain many relations you might change to FetchType.EAGER or you can convert entity while Person is still attached.
In the BeanUtils, you need to initialize the objects via Hibernate as follows:
MyProfile pf = null;
try {
session.beginTransaction();
Query query = session.createQuery("from MyProfile as term where term.profileId=:pId ");
query.setString("pId", pid);
pf = (MyProfile)query.uniqueResult();
if(pf != null)
{
Hibernate.initialize(pf);
}
} catch (HibernateException he) {
throw he;
}
Using Hibenrate.initialize(pf) will initialize the objects contained inside MyProfile object.
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
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.