LazyInitializationException on getId() of a #ManyToOne reference - java

I'm facing LazyInitializationException when I'm trying to access ID of a lazy #ManyToOne reference of a detached entity. I do not want to fetch the refrence completely, but just need the ID (which should be exist in original object in order to fetch refrence in a lazy/deferred manner).
EntityA ea = dao.find(1) // find is #Transactional, but transaction is closed after method exits
ea.getLazyReference().getId() // here is get exception. lazyReference is a ManyToOne relation and so the foreight key is stored in EntityA side.
To paraphrase, how can I access ID of LazyReference (which actually exists in initial select for EntityA) without actually fetching the whole LazyReference?

When field access is used, Hibernate treats getId() method the same as any other method, meaning that calling it triggers proxy initialization, thus leading to LazyInitializationException if invoked on a detached instance.
To use property access only for id property (while keeping field access for all the other properties), specify AccessType.PROPERTY for the id field:
#Entity
public class A {
#Id
#Access(AccessType.PROPERTY)
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}

That should be possible. I am able to get only the ID of the #ManyToOne LAZY entity.
But for that I have set annotations on the getters of the entity instead of setting them directly on the instance variables which results in null value.
I believe you are using annotations on the instance variables. You can try getter annotations and see if that helps you.

You get an LazyInitializationException exception, because of Hibernate wraps your persistent with a proxy object. A proxy generates an exception for any getter of a lazy object even for id that LazyReference already has of course.
To get id without LazyInitializationException you can use this method (you can refer the link for other interesting utilite methods)
#SuppressWarnings("unchecked")
public static <T> T getPid(Persistent<?> persistent) {
if (persistent == null) {
return null;
}
if (!(persistent instanceof HibernateProxy) || Hibernate.isInitialized(persistent)) {
return (T) persistent.getPid();
}
LazyInitializer initializer = ((HibernateProxy) persistent).getHibernateLazyInitializer();
return (T) initializer.getIdentifier();
}
Persistent is a base class for all persistents. For your LazyReference you can rewrite code like this
#SuppressWarnings("unchecked")
public static Long getId(LazyReference persistent) {
if (persistent == null) {
return null;
}
if (!(persistent instanceof HibernateProxy) || Hibernate.isInitialized(persistent)) {
return persistent.getId();
}
LazyInitializer initializer =
((HibernateProxy) persistent).getHibernateLazyInitializer();
return initializer.getIdentifier();
}

Related

Spring boot does not throw 409 for duplicate entity in database. What should be the response? [duplicate]

I'm currently playing around on Spring boot 1.4.2 in which I've pulled in Spring-boot-starter-web and Spring-boot-starter-jpa.
My main issue is that when I save a new entity it works fine (all cool).
However if I save a new product entity with the same id (eg a duplicate entry), it does not throw an exception. I was expecting ConstrintViolationException or something similar.
Given the following set up:
Application.java
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
ProductRepository.java
#Repository
public interface ProductRepository extends JpaRepository<Product, String> {}
JpaConfig.java
#Configuration
#EnableJpaRepositories(basePackages = "com.verric.jpa.repository" )
#EntityScan(basePackageClasses ="com.verric.jpa")
#EnableTransactionManagement
public class JpaConfig {
#Bean
JpaTransactionManager transactionManager() {
return new JpaTransactionManager();
}
}
Note JpaConfig.java and Application.java are in the same package.
ProductController.java
#RestController
#RequestMapping(path = "/product")
public class ProductController {
#Autowired
ProductRepository productRepository;
#PostMapping("createProduct")
public void handle(#RequestBody #Valid CreateProductRequest request) {
Product product = new Product(request.getId(), request.getName(), request.getPrice(), request.isTaxable());
try {
productRepository.save(product);
} catch (DataAccessException ex) {
System.out.println(ex.getCause().getMessage());
}
}
}
and finally Product.java
#Entity(name = "product")
#Getter
#Setter
#AllArgsConstructor
#EqualsAndHashCode(of = "id")
public class Product {
protected Product() { /* jpa constructor*/ }
#Id
private String id;
#Column
private String name;
#Column
private Long price;
#Column
private Boolean taxable;
}
The getter, setter and equalsHashcode.. are lombok annotations.
Miscellaneous:
Spring boot : 1.4.2
Hibernate ORM: 5.2.2.FINAL
This issue happens regardless if I annotate the controller with or without #Transactional
The underlying db shows the exception clearly
2016-11-15 18:03:49 AEDT [40794-1] verric#stuff ERROR: duplicate key value violates unique constraint "product_pkey"
2016-11-15 18:03:49 AEDT [40794-2] verric#stuff DETAIL: Key (id)=(test001) already exists
I know that is better (more common) to break the data access stuff into its own service layer instead of dumping it in the controller
The semantics of the controller aren't ReST
Things I've tried:
Spring CrudRepository exceptions
I've tried implementing the answer from this question, unfortunately my code never ever hits the DataAccesException exception
Does Spring JPA throw an error if save function is unsuccessful?
Again similar response to the question above.
http://www.baeldung.com/spring-dataIntegrityviolationexception
I tried adding the bean to my JPAconfig.java class that is:
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
But nothing seemed to happen.
Sorry for long post, ty in advance
My solution is a lot cleaner. Spring Data already provides a nice way for us to define how an entity is considered to be new. This can easily be done by implementing Persistable on our entities, as documented in the reference.
In my case, as is the OP's, the IDs come from an external source and cannot be auto generated. So the default logic used by Spring Data to consider an entity as new if the ID is null wouldn't have worked.
#Entity
public class MyEntity implements Persistable<UUID> {
#Id
private UUID id;
#Transient
private boolean update;
#Override
public UUID getId() {
return this.id;
}
public void setId(UUID id) {
this.id = id;
}
public boolean isUpdate() {
return this.update;
}
public void setUpdate(boolean update) {
this.update = update;
}
#Override
public boolean isNew() {
return !this.update;
}
#PrePersist
#PostLoad
void markUpdated() {
this.update = true;
}
}
Here, I have provided a mechanism for the entity to express whether it considers itself new or not by means of another transient boolean property called update. As the default value of update will be false, all entities of this type are considered new and will result in a DataIntegrityViolationException being thrown when you attempt to call repository.save(entity) with the same ID.
If you do wish to perform a merge, you can always set the update property to true before attempting a save. Of course, if your use case never requires you to update entities, you can always return true from the isNew method and get rid of the update field.
The advantages of this approach over checking whether an entity with the same ID already exists in the database before saving are many:
Avoids an extra round trip to the database
We cannot guarantee that by the time one thread has determined that this entity doesn't exist and is about to persist, another thread doesn't attempt to do the same and result in inconsistent data.
Better performance as a result of 1 and having to avoid expensive locking mechanisms.
Atomic
Simple
EDIT: Don't forget to implement a method using JPA callbacks that sets the correct state of the update boolean field just before persisting and just after loading from the database. If you forget to do this, calling deleteAll on the JPA repository will have no effect as I painfully found out. This is because the Spring Data implementation of deleteAll now checks if the entity is new before performing the delete. If your isNew method returns true, the entity will never be considered for deletion.
I think you are aware of CrudRepository.save() is used for both insert and update. If an Id is non existing then it will considered an insert if Id is existing it will be considered update. You may get an Exception if your send the Id as null.
Since you don't have any other annotations apart from #Id on your id variable, The Unique Id generation must be handled by your code Or else you need to make use of #GeneratedValue annotation.
To build upon Shazins answer and to clarify. the CrudRepositroy.save() or JpaRespository.saveAndFlush() both delegate to the following method
SimpleJpaRepository.java
#Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
Hence if a user tries to create a new entity that so happens to have the same id as an existing entity Spring data will just update that entity.
To achieve what I originally wanted the only thing I could find was to drop back down to JPA solely, that is
#Transactional
#PostMapping("/createProduct")
public Product createProduct(#RequestBody #Valid Product product) {
try {
entityManager.persist(product);
entityManager.flush();
}catch (RuntimeException ex) {
System.err.println(ex.getCause().getMessage());
}
return product;
}
Here if we try to persist and new entity with an id already existing in the database it will throw will throw the constraint violation exception as we originally wanted.
Note that there are 3 scenarios here:
1. Setting ID manually
If there is no choice(like the OP), i.e if you are setting your own id "manually", Spring Data JPA is assuming that you want to check if there are duplicates(hence the SELECT), so it will do a "(i)SELECT + (ii)INSERT" if there is no existing record or a "(i)SELECT + (ii)UPDATE" if there is already an existing record.
In short, 2 SQLs!
2. Use an ID Generator
Cleaner & better, for example:
#Id
#GeneratedValue(generator = "my-uuid")
#GenericGenerator(name = "my-uuid", strategy = "uuid2")
private UUID id;
Result: there is ALWAYS only 1 INSERT statement.
3. Implement Persistable and isNew()
This has already been brilliantly answered by #adarshr, but is also more painful, i.e to implement Persistable(instead of Serializable), and implement the isNew() method.
Result: Also, 1 INSERT statement.
According to Spring Data documentation Spring persists an entity if does not exists or merge, this means update, the existing one:
Saving an entity can be performed via the CrudRepository.save(…)-Method. It will persist or merge the given entity using the underlying JPA EntityManager. If the entity has not been persisted yet Spring Data JPA will save the entity via a call to the entityManager.persist(…)-Method, otherwise the entityManager.merge(…)-Method will be called.

In Hibernate why does saveOrUpdate give an exception when object already exists in database

Previously, when I was adding a entity to database with Hibernate I used to check that it hadn't already been added. But in an effort to improve performance I forgot this check and just tried to add without checking, as I was using saveOrUpdate() it was my understanding that if Hibernate found it was already added it would just update with and changes made by my save.
But instead it fails with
18/08/2018 21.58.34:BST:Errors:addError:SEVERE: Adding Error:Database Error:Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.jthink.songlayer.MusicBrainzReleaseWrapper#95f6f584-407f-4b26-9572-bb8c6e9c580a]
java.lang.Exception
at com.jthink.songkong.analyse.general.Errors.addError(Errors.java:28)
at com.jthink.songkong.exception.ExceptionHandling.handleHibernateException(ExceptionHandling.java:209)
at com.jthink.songkong.db.ReleaseCache.addToDatabase(ReleaseCache.java:394)
at com.jthink.songkong.db.ReleaseCache.add(ReleaseCache.java:65)
#Entity
public class MusicBrainzReleaseWrapper
{
#Id
private String guid;
#Version
private int version;
#org.hibernate.annotations.Index(name = "IDX__MUSICBRAINZ_RELEASE_WRAPPER_NAME")
#Column(length = 1000)
private String name;
#Lob
#Column(length = 512000)
private String xmldata;
public String getGuid()
{
return guid;
}
public void setGuid(String guid)
{
this.guid = guid;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getXmldata()
{
return xmldata;
}
public void setXmldata(String xmldata)
{
this.xmldata = xmldata;
}
}
private static boolean addToDatabase(Release release)
{
Session session = null;
try
{
session = HibernateUtil.beginTransaction();
//Marshall to String
StringWriter sw = new StringWriter();
Marshaller m = jc.createMarshaller();
m.marshal(release, sw);
sw.flush();
MusicBrainzReleaseWrapper wrapper = new MusicBrainzReleaseWrapper();
wrapper.setGuid(release.getId());
wrapper.setName(release.getTitle().toLowerCase(Locale.UK));
wrapper.setXmldata(sw.toString());
session.saveOrUpdate(wrapper);
session.getTransaction().commit();
MainWindow.logger.info("Added to db release:" + release.getId() + ":" + release.getTitle());
return true;
}
catch (ConstraintViolationException ce)
{
MainWindow.logger.warning("Release already exists in db:"+release.getId()+":"+release.getTitle());
return true;
}
catch(GenericJDBCException jde)
{
MainWindow.logger.log(Level.SEVERE, "Failed:" +jde.getMessage());
ExceptionHandling.handleDatabaseException(jde);
}
catch(HibernateException he)
{
MainWindow.logger.log(Level.SEVERE, "Failed:" +he.getMessage());
ExceptionHandling.handleHibernateException(he);
}
catch(Exception e)
{
MainWindow.logger.log(Level.WARNING,"Failed AddReleaseToDatabase:"+release.getId()+ ':' +e.getMessage(),e);
throw new RuntimeException(e);
}
finally
{
HibernateUtil.closeSession(session);
}
return false;
}
Used to check first before call to addToDatabase
if(ReleaseCache.get(release.getId())==null)
{
addToDatabase(release)
}
Hiberante object has 3 states for an Entity. They are:
- Transient Or New
- Detached (Objects are fetched from DB and hibernate session is closed)
- Persistent (Object are fetched from DB and hibernate session is open)
In saveOrUpdate method, it either save the transient object or update the detached/ persistent object.
In your code, you are trying to create Transient/New object and setting the old id in it. That's the reason you are getting above error. The correct way to fetch the object first using id and then update it.
The problem you are hitting is directly related to the Optimistic locking you have enabled through the #Version annotation on the MusicBrainzReleaseWrapper. saveOrUpdate really can either add or update an entity but this is only if the entity version is the same as the one of the detached object you are trying to add or merge.
In your particular example your detached object has a version previous to the last version in the database therefore the operation can not be executed on a stale data.
UPDATE:
MusicBrainzReleaseWrapper wrapper = session.get(release.getId()):
//the wrapper is managed object
if (wrapper == null) {
//initilize wrapper with the values from release
.......
session.save(wrapper)
}
else {
// do not set ID here. ID is aready present!!!
// never manuay set the version field here
wrapper.setName(release.getTitle().toLowerCase(Locale.UK));
wrapper.setXmldata(sw.toString());
session.saveOrUpdate(wrapper);
//In case you don't need update logic at all
// remove the #Version field from the entity
// and do othing in the else clause , or throw exception
// or log error or anything you see fit
}
No. saveOrUpdate method is used either to persist or merge an entity with the current session. It doesn't do what you expect. Either save or update entity is application's specific logic. Hibernate doesn't do any application's specific logic.
Session.merge() can directly save a previously unknown instance, but note it won't necessarily avoid the extra select against the database.
#Pavan is right about the entity being transient or detached in Hibernate (or JPA) terminology. Both of these states mean that Hibernate has not yet got a reference to this instance of the entity in its session (in the StatefulPersistenceContext), but detached clearly means it is known to the database.
merge() instructs Hibernate to stop and check for a detached instance. The first check is for the #Id value in the session, but if it's not already there, it must hit the database.
saveOrUpdate() instructs Hibernate that the caller knows it is safe to only check the StatefulPersistenceContext for the #Id. If it's not there, the entity is assumed to be transient (i.e. new), and Hibernate will proceed to the insert operation.
saveOrUpdate() is good for instances (with or without an #Id value) that are known to the session already.
In your case clearly Hibernate is unaware of the detached instance, so you would need to use merge(). But that also means Hibernate has to check the database for the instance it hasn't seen before - if the entity has an #Id value.
To come back to the original intent in your question, update without select is harder ...
For an update, Hibernate likes to know the prior state of the entity. This makes sense if it's using dynamic updates (so not updating all columns), but otherwise you would think it could go straight for the update. The only option I know of for this is a direct update query (via HQL or JPQL), but this is hardly convenient if you have an entity instance. Maybe someone else knows how to do this.

Hibernate Lazy Object With No Relations

I have an Hibernate object as follows:
#Entity
#Table(name="SOME_TABLE")
public class SomeEntity {
private Long id;
private String someInfo;
#Id
#Column(name = "ID")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Column(name = "SOME_INFO")
public String getSomeInfo() {
return someInfo;
}
public void setSomeInfo(String someInfo) {
this.someInfo = someInfo;
}
}
When loading the object using the following code:
sessionFactory.getCurrentSession().load(getEntityClass(), id);
The object's fields are not loaded, instead a proxy object is returned, and the actual fields are loaded only when I explicitly call them by their getter method.
To the best of my knowledge, plain fields (primitives, strings) should be loaded eagerly. Why does the fields, which are not relations or Collections are loaded lazily? is there any way to ask Hibernate to load them eagerly?
This is problematic for me as I use this object as the return value of a Spring REST application, and then I get a could not initialize proxy - no Session exception.
The reason you obtain a proxy is because the Session#load contract is permitted to return a proxy as a placeholder without ever querying the database for the specified object. This is also why it's crucial that the provided identifier for which you wish to load exists as you'll run into unexpected ObjectNotFoundException errors later on if so.
What you want to use is Session#get which is guaranteed to query the database and will not return a proxy, thus those basic attributes you mentioned will be eagerly loaded as you would expect.
For example:
final Comment comment = new Comment( "This is a comment" );
comment.setOwner( session.load( Product.class, productId ) );
session.save( comment );
The benefit here is that the Product isn't fully initialized. We create a persistent proxy with the specified productId value and associate it as the owner of the comment. This is sufficient when we persist the new Comment to make the foreign-key relationship occur without having to actually load the state of Product, avoiding unnecessary overhead.

When I use #Transactional annotation many-to-one relations come null

When I use #Transactional, after I save objects, hibernate get function returns object which join fields are null.
This is my business layer. #Transactional annotation is there.
#Override
#Transactional(propagation = Propagation.REQUIRED)
public KonKonfigCoverView addWithNewObj(KonKonfigCoverView objView, Long kurumId)
throws MosBaseException {
EnvanterKitTanimView kitView = null;
if (objView.getKit() != null){
kitView = objView.getKit();
if ("0001".equals(kitView.getEnvanterKitTip().getKod())){ //ATS
kitView = atsKitBO.addObj(kitView, kurumId);
}
else if("0002".equals(kitView.getEnvanterKitTip().getKod())){ //OGSHGS
kitView = ogsHgsBO.addObj(kitView, kurumId);
}
else if("0004".equals(kitView.getEnvanterKitTip().getKod())){ //TTS
kitView = akaryakitKitBO.addObj(kitView, kurumId);
}
objView.getKonKonfig().setKit(kitView);
objView.getKonKonfigDegisiklikDetay().setKit(kitView);
objView.setKit(kitView);
}
KonKonfigCoverView view = this.addObj(objView, kurumId);
if (objView.getKit() != null){
view.setKit(kitView);
}
return view;
}
This is the this.addObj function:
#Override
public KonKonfigView addObj(KonKonfigView view, Long kurumId) throws MosBaseException{
KonKonfig addedObj = konKonfigDAO.addObj(beforeAddObj, ilkTakilmaOdo, sonTakilmaOdo);
addedObj= konKonfigDAO.getObj(addedObj.getId());
return (KonKonfigView) BaseHelper.gulmekYok(KonKonfig.class, KonKonfigView.class, addedObj, true, EnViewTip.FULL_DESCRIPTION, null);
}
This add operation works well, however getObj function returns object that many-to-one relation fields are null.
One of my fields which comes null is that:
#ManyToOne(optional=true)
#JoinColumn(name="CAR_ID", insertable=false, updatable=false)
private Car car;
Unless you are saving the car object before you save the enclosing object, then the field will definitely be null. Hibernate sets the cascade type for a #ManyToOne mapping to none.
This means that operations that were done on the enclosing object will not be done to the objects mapped inside unless you specify a specific cascade type.
You can do the following to solve this problem:
Option 1: #ManyToOne(cascade = CascadeType.PERSIST)
Option 2:
#ManyToOne
#Cascade(CascadeType.PERSIST)
Checkout the different CascadeTypes so you can choose the right one for what you need.

Because of Hibernate Mapping need to have some of the fields as #Transient but JSP does not have access to them

In Java, I have access to value of Transient fields of the class. However, I do not access to the same fields on JSP. How can I make them available to JSP?
I am retrieving the values using Hibernate, I reckon a solution to this would be to Transformers.aliasToBean option but is there any other solution to it?
Is there anyway to get rid of transient annotation but have the same mapping in Hibernate? In that case, the problem will be solved.
#AssociationOverrides({
#AssociationOverride(name = "tta.names", joinColumns = #JoinColumn(name = "id"))})
public class Names implements java.io.Serializable {
private static final long serialVersionUID = -30956546435023625398L;
#EmbeddedId
private TableToAssociate tta = new TableToAssociate();
#Transient
public String getid() {
return tta.getMyIds().getId();
}
public void setid(String id) {
this.tta.getMyIds().setId(id);
}
In Java, I can access them using following code
System.out.println(mystudents.getNames().iterator().next().getId());
In JSP, I do not have access to them!
<c:forEach var="nm"items="${mystudents.names}">
${nm.id}
</c:forEach>
If I put another field of names that is not transient, JSP successfully show the value of that item.
Try renaming the methods to match the JavaBean specification.
Instead of:
#Transient
public String getid() {
return tta.getMyIds().getId();
}
public void setid(String id) {
this.tta.getMyIds().setId(id);
}
you should have:
#Transient
public String getId() {
return tta.getMyIds().getId();
}
public void setId(String id) {
this.tta.getMyIds().setId(id);
}
Get rid of #Transient on your entity. Based on your embedded id, you've chosen field annotations. You should be able to have a getter that Hibernate won't try to persist without explicitly marking it as such. And change the getter/setter to use correct JavaBean syntax. getId instead of getid.
It is happening because transient keyword stops the field to be serialized. To pass an object to JSP, it must be serialized. This field will not be there in your serialized object and hence, it is not available to JSP.
For solving your problem, you should see James' comment to use #Transient annotation. It is supplied by JPA and it should allow you to NOT save your value in DB, but serialize it so your JSP can use it.
What you are asking is not possible.. As smarter then me said before.
#Transient is just saying not to serialize/deserialize. Hibernate doesn't serialize this and the same is what jsp bean.
You can do several things:
I think the best thing here is to wrap field in getter and setter in the bean.. This way you will set the property on the bean and in case of only one field every other solution will be overhead.
public class Bean() {
private Names name;
private String id;
public Bean() {
//few action to load name
this.id = name.getid();
}
public String getId() {
return this.id;
}
Create different entity for ui(Bean) and DB(Hibernate). In many cases ui layer and entities are not the same and sometime should not be coupled
You can use inheritance and create diffrent implemenation in the hibernate entity and on ui layer(Jsp bean)
If you ask me i think the best is to do what on section 1. If this happen more oftain you should consider decouple your ui layer and business entity.

Categories

Resources