I want to be able to check whether a session contains an entity of a given class/identifier. I can't see a way to do this at the moment.
contains() takes an entity object not class + key
get() queries the database if the entity is not present which I don't want to do
load() never returns null as a proxy is always created so I can't use this method
Is it possible to do the above with no side-effects/queries to DB?
This works:
public boolean isIdLoaded(Serializable id)
{
for (Object key : getSession().getStatistics().getEntityKeys())
{
if (((EntityKey) key).getIdentifier().equals(id))
{
return true;
}
}
return false;
}
Not that I know of. The session will always check the DB if you attempt to get an entity of a particular type and id and it doesn't contain it.
In fact that is one of the good things about the model. You don't have to care where JPA/Hibernate gets the object from, cache (1st or 2nd level) or the DB.
If you have an entity in memory you can check to see if it is dettached from the session but not if it is in the session.
Answering my own question.
I do not believe this is possible with the public APIs. However if you are willing to tolerate some cheese you can do the following
SessionImplementor sessionImplementor = ((SessionImplementor)session);
EntityPersister entityPersister = sessionImplementor.getFactory().getEntityPersister( clazz.getName() );
PersistenceContext persistenceContext = sessionImplementor.getPersistenceContext();
EntityKey entityKey = new EntityKey( id, entityPersister, EntityMode.POJO );
Object entity = persistenceContext.getEntity( entityKey );
if ( entity != null )
return entity;
entity = persistenceContext.getProxy( entityKey );
if ( entity != null )
return entity;
return null;
This relies on internal APIs of hibernate so may not work in future if it changes internally.
Use getIdentifier(..). It returns the identifier value of the given entity as associated with current session see Javadoc.
public static boolean sessionContains(Session session, Class<?> aClass, Serializable id) {
return Hibernate.isInitialized(session.load(aClass, id));
}
session#load() doesn't go to the database and always return Object.
If returned value is a Proxy then session doesn't contain passed id/class pair.
Otherwise session already has this object.
PersistenceUnitUtil persistenceUnitUtil = entityManagerFactory.getPersistenceUnitUtil();
Object object = entityManager.getReference(entity.getClass(), entity.getId());
boolean inSessionCache = persistenceUnitUtil.isLoaded(object);
If entity is in Session cache then getReference() return entity, else it return proxy object. isLoaded() return false if object is proxy.
Related
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.
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.
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();
}
I have this code:
Candidate candidate = new Candidate();
candidate.setName("testUser");
candidate.setPhone("88888");
candidateService.add(candidate);
sessionFactory.getCurrentSession().flush();
return candidate;
CandidateService marked as #Transactional;
Can you explain me why after execution of candidateService.add(candidate);
candidate get id field value.
Maybe it is normally?
candidateService.add(candidate) realization:
public void add(Candidate candidate) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String login = auth.getName();
User user = utilService.getOrSaveUser(login);
candidate.setAuthor(user);
candidateDao.add(candidate);
}
#Override
public Integer add(Candidate candidate) throws HibernateException{
Session session = sessionFactory.getCurrentSession();
if (candidate == null) {
return null;
}
Integer id = (Integer) session.save(candidate);
return id;
}
I thought it happened if candidate in persistent state.
I messed.
Since the ID is primary key of the table candiate,when you add it to database a Id generates and returned by the save method.
If you see docs of save method.
Persist the given transient instance, first assigning a generated identifier. (Or using the current value of the identifier property if the assigned generator is used.) This operation cascades to associated instances if the association is mapped with cascade="save-update".
Returns:
the generated identifier
I have got a onetomany relation in Hibernate:
#Entity
public class Project
#OneToMany(cascade = CascadeType.ALL)
#OrderColumn(name = "project_index")
List<Application> applications;
#Entity
public class Application
Now, I tried to update an application directly:
session.merge(application);
The result is that a new Application is created, instead of updating the entity. How can I reatach my entity properly and update my entity?
Thank you
Solution:
#Override
public boolean equals(Object obj) {
if (!(obj instanceof Application)) {
return false;
}
Application app = (Application)obj;
return id == app.getId();
}
#Override
public int hashCode () {
return status.hashCode();
}
When you call session.merge(), it returns an object which is the value of that entity in the context of the current session. You can't simply reuse the existing object in a new session, because the object is outside of its session's life (and a new object to represent the same entity may already have been created in the new session). You need to honor hibernate's single-instance-per-entity-per-session semantics by using the new object in place of the old.
If you find that calling merge() results in a new entity being created (an insert statement is generated on flush), this might mean that the identifier of the object you're trying to merge doesn't match, or there is an issue with your equals() or hashCode() implementation.