When hibernate sets primary key(id)? - java

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

Related

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.

What is difference between save() and update() in Hibernate

I am developing user module using jsp/servlet and hibernate. While updating user I was using same save method which is used while adding new user.
User service
public static User updateUserInstance(HttpServletRequest request) {
String id = request.getParameter("userId");
String firstName = request.getParameter("firstName");
String lastName = request.getParameter("lastName");
String userName = request.getParameter("userName");
String gender = request.getParameter("gender");
String dob = request.getParameter("dob");
String email = request.getParameter("email");
String userType = request.getParameter("userType");
User user = getUser(Integer.parseInt(id));
System.out.println("User is : "+user);
user.setFirstName(firstName);
user.setLastName(lastName);
user.setUserName(userName);
user.setGender(gender);
user.setDob(dob);
user.setEmail(email);
user.setUserType(userType);
return user;
}
UserDao
public boolean saveUser(User user) {
try{
SessionFactory factory = HibernateUtility.getSessionFactory();
Session session = factory.getCurrentSession();
session.beginTransaction();
session.save(user);
session.getTransaction().commit();
return true;
} catch(Exception e) {
System.out.println(e);
return false;
}
}
public boolean updateUser(User user) {
try{
SessionFactory factory = HibernateUtility.getSessionFactory();
Session session = factory.getCurrentSession();
session.beginTransaction();
session.update(user);
session.getTransaction().commit();
return true;
} catch(Exception e) {
System.out.println(e);
return false;
}
}
So my service is finding user based on Id and updating user values. After that I was using same save method of userDao and instead of updating that user It is creating new user. So I have created new method called updateUser.
I tried to find difference between save and update and found that save will insert into database if identifier doesn’t exist else it will throw an error. While in my case its not showing any error and creating new user. So I am not able to clearly identify how save and update internally works.
What are the differences between the different saving methods in Hibernate?
My question is quite different. I want to know same but its clear mentioned that in above question that "save Persists an entity. Will assign an identifier if one doesn't exist. If one does, it's essentially doing an update. Returns the generated ID of the entity." I asking same, My object has identifier still its creating new object instead of updating old object.
RTFM.
save():
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.)
update():
Update the persistent instance with the identifier of the given detached instance. If there is a persistent instance with the same identifier, an exception is thrown.
Summary:
Save is for new things. Hibernate assigns an id as required
Update is for old things. Hibernate uses the id the object as is

Spring JPA repository: prevent update on save

My user DB table looks like this:
CREATE TABLE user (
username VARCHAR(32) PRIMARY KEY,
first_name VARCHAR(256) NOT NULL,
last_name VARCHAR(256) NOT NULL,
password VARCHAR(32) NOT NULL,
enabled BOOL
) ENGINE = InnoDB;
This is the field definitions of my entity:
#Entity
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(nullable = false)
private String username;
#Column(nullable = false)
private String firstName;
#Column(nullable = false)
private String lastName;
#Column(nullable = false)
private String password;
The field username is the key of my table/entity and it's up to me to set its value.
When I need to create another user, I do this in my service:
public User insertUserImpl(String username, String firstName, String lastName) {
Assert.hasText(username);
Assert.hasText(firstName);
Assert.hasText(lastName);
String password = UUID.randomUUID().toString().substring(0, 4); // temp
User user = new User(username, password);
user.setFirstName(firstName);
user.setLastName(lastName);
user.setEnabled(false);
this.userRepository.save(user);
// FIXME - assegnare un ruolo
return user;
}
Anyway, if the username is already taken, the repository just do an update, because the specified identifier is not null. This is not the behaviour that I want, I need it to throw something like a duplicate entry exception.
Is there any way to prevent it? Do I have to do it by myself?
E.g.:
User user = this.userRepository.findOne(username);
if(user != null) {
throw new RuntimeException("Username already taken"); // FIXME - eccezione applicativa
}
When using the default configuration, and using CrudRepository#save() or JpaRepository#save() it will delegate to the EntityManager to use either persists() if it is a new entity, or merge() if it is not.
The strategy followed to detect the entity state, new or not, to use the appropiate method, when using the default configuration is as follows:
By default, a Property-ID inspection is performed, if it is null, then it is a new entity, otherwise is not.
If the entity implements Persistable the detection will be delegated to the isNew() method implemented by the entity.
There is a 3rd option, implementing EntityInformation, but further customizations are needed.
source
So in your case, as you are using the username as ID, and it isn't null, the Repository call ends up delegating to EntityManager.merge() instead of persist(). So there are two possible solutions:
use a diferent ID property, set it to null, and use any auto-generation method, or
make User implement Persistable and use the isNew() method, to determine if it is a new entity or not.
If for some reason, you don't want to modify your entities, you can also change the behaviour modifying the flush mode configuration. By default, in spring data jpa, hibernate flush mode is set to AUTO. What you want to do is to change it to COMMIT, and the property to change it is org.hibernate.flushMode. You can modify this configuration by overriding a EntityManagerFactoryBean in a #Configuration class.
And if you don't want to mess the configuration of the EntityManager, you can use the JpaRepository#flush() or JpaRepository#saveAndFlush() methods, to commit the pending changes to the database.
One can perhaps use existsById(ID primaryKey) to test it, if userRepository extends CrudRepository:
if(userRepository.existsById(username)){
//Throw your Exception
} else {
this.userRepository.save(user);
}
see https://docs.spring.io/spring-data/jpa/docs/current/reference/html/
Instead of
this.userRepository.save(user)
, can you try
this.userRepository.saveAndFlush(user)
My best guess is, it will make your entity detached and as per the JPA documentation, it states an EntityExistsException is thrown by the persist method when the object passed in is a detached entity. Or any other PersistenceException when the persistence context is flushed or the transaction is committed.

LazyInitializationException on getId() of a #ManyToOne reference

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();
}

Hibernate session.contains( Class clazz, Serializable id )

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.

Categories

Resources