We are using Hibernate and MySQL in our project. We are using saveOrUpdate() method for updating data.
Our problem When We update the existing data first we are checking some conditions. If those conditions are passed we are throwing error like below snippet code.
Else we are saving the data. We put debug and checked at the saveOrUpdate() method. It is not going to saveOrUpdate(). But some how data is saving into database.
This is DAO layer
public List<Student> getOverlappingStudentDetails(String studentId, String classNumber){
Session session = sessionFactory.getCurrentSession();
Criteria criteria = session.createCriteria(Student.class, "student");
criteria.add(Restrictions.eq("student.id", studentId));
criteria.add(Restrictions.eq("student.classNumnber", classNumber));
return criteria.list();
}
public Student save(Student student) {
Session session = sessionFactory.getCurrentSession();
session.saveOrUpdate(student);
session.flush();
return student;
}
This is service implementation
private void checkForOverlappingStudentDetails(Student student) {
List<Student> overlappingStudents = studentDAO.getOverlappingStudentDetails(student.getStudentId(),student.getClassName());
if (overlappingStudents.size() >0 ) {
throw new ValidationException(student.getName() + " is already present." ;
}
}
It is throwing error. But, data is overriding into database. Is there any solution to stop the data to for updating. We tried session.clear() and we are using session.flush() method after saving the method.
Related
I'm learning about #Transactional and I want to ask you a question. Why is important to use #Transactional at the following methods?
#Repository
public class CustomerDAOImpl implements CustomerDAO {
// need to inject the session factory
#Autowired
private SessionFactory sessionFactory;
#Override
#Transactional
public List<Customer> getCustomers() {
// get the current hibernate session
Session currentSession = sessionFactory.getCurrentSession();
// create a query ... sort by last name
Query<Customer> theQuery =
currentSession.createQuery("from Customer order by lastName",
Customer.class);
// execute query and get result list
List<Customer> customers = theQuery.getResultList();
// return the results
return customers;
}
#Override
#Transactional
public void saveCustomer(Customer theCustomer) {
// get current hibernate session
Session currentSession = sessionFactory.getCurrentSession();
// save/upate the customer ... finally LOL
currentSession.saveOrUpdate(theCustomer);
}
#Override
#Transactional
public Customer getCustomer(int theId) {
// get the current hibernate session
Session currentSession = sessionFactory.getCurrentSession();
// now retrieve/read from database using the primary key
Customer theCustomer = currentSession.get(Customer.class, theId);
return theCustomer;
}
#Override
#Transactional
public void deleteCustomer(int theId) {
// get the current hibernate session
Session currentSession = sessionFactory.getCurrentSession();
// delete object with primary key
Query theQuery =
currentSession.createQuery("delete from Customer where id=:customerId");
theQuery.setParameter("customerId", theId);
theQuery.executeUpdate();
}
}
I thought that we need to use #Transactional when we have 2 or more writes on a database. For example if we want to transfer $100 from user A to user B. In this case we need to do 2 things, first we need to decrease $100 from user A, and second we need to add $100 to user B. And we need this 2 writes as a single atomic operation. And I understand why we need #Transactional in this situation.
But what I don't understand is why do we need #Transactional for the 4 methods in the above code. In getCustomers() method we just retrieve the customers, in saveCustomer() we just save a customer in the database, deleteCustomer() we just delete a customer. So in these methods we have only one write in the database. Then why do we need #Transactional? Thank you!
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 am creating a CRUD API using hibernate as my persistence layer.
The API takes JSON and serializes it to a POJO. A management layer then converts the POJO into a new Hibernate Domain object.
This exact same code is run for both Create and Update - the only difference is that for Update I also set the ID field of the hibernate object.
Creating works just fine, but Update fails out with a org.hibernate.exception.LockTimeoutException. After hours of snooping around I'm going to wave the white flag and hope someone can explain all the reasons I'm an idiot.
ClientManager Code
public class ClientManager {
private static final ClientDAO clientDAO = new ClientDAO();
...
public Client updateClient(ClientVO inputVO) {
// Generate a Client from the input
Client client = ClientManager.generateClient(inputVO);
client.setClientKey(Integer.parseInt(inputVO.getPersonalId()));
client.setUpdateDate(new Date());
client.setUpdateTimestamp(new Date());
// Update the client
clientDAO.update(client);
}
...
public static Client generateClient(ClientVO clientVO) {
Client client = new Client();
client.setFirstName(clientVO.getFirstName());
client.setMiddleName(clientVO.getMiddleName());
client.setLastName(clientVO.getLastName());
return client;
}
}
BaseDAO Code (ClientDAO extends BaseDAO)
public class BaseDAO {
public Boolean save(Object object) {
Session session = getSession();
Transaction tx = session.beginTransaction();
session.save(object);
tx.commit();
session.close();
return Boolean.TRUE;
}
public Boolean update(Object object) {
Session session = getSession();
Transaction tx = session.beginTransaction();
session.merge(object);
tx.commit();
session.close();
return Boolean.TRUE;
}
public Session getSession()
{
return HibernateSessionFactory.getSession();
}
}
Entry Point Code
#PUT
#Path("clients/{personalId}")
#Produces({MediaType.APPLICATION_JSON})
public String updateClient(#PathParam("personalId") String personalId, String data) throws JsonParseException, JsonMappingException, IOException {
ClientVO inputVO = om.readValue(data, ClientVO.class);
inputVO.setPersonalId(personalId);
ClientVO outputVO = clientManager.updateClient(inputVO);
return om.writeValueAsString(outputVO);
}
Note that clientKey is the primary key.
The timeout is happening at the point of the .commit() in the update() method of BaseDAO.
I'm happy to provide more code (e.g. ClientVO) if useful.
The only way this can happen is that you have two database connections that both attempt to modify the same entity.
If this happens with a single-user, it is because you don't use the same Session for the whole request but instead you create several ones. I would say that you open a Hibernate Session and a transaction in some outer-level and when the update method is called you open another Session and a new transaction that conflicts with the outer one that might have already acquired locks on the same entity (because you loaded the entity and change it).
I'm new to hibernate and as I researched, I have found out that the HQL insert query gets data from other tables. According to what I've read, I can make use of session.save for the insert functionality.
In my DAO I have this addToCart() method
#Override
public void addToCart(ShoppingCart cart) {
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction transaction = session.beginTransaction();
session.save(cart);
transaction.commit();
session.close();
}
The code above doesn't work. Maybe I am missing something because I'm still new to hibernate.
I have a simple User Account application in which the user is able to change his details.
Updating the Database
The Managed Bean's method which takes the form parameters and calls the Service method:
public String changeDetails(){
Date date = DateUtil.getDate(birthDate);
Integer id = getAuthUser().getId();
UserDetail newDetails = new UserDetail(id, occupation, date, originCity, residenceCity, description);
EntityTransaction transaction = getTransaction();
userService.updateDetail(newDetails);
transaction.commit();
return null;
}
The Service Method:
public boolean updateDetail(UserDetail newDetails) {
boolean ok = true;
if (newDetails != null) {
UserDetail user = readDetail(newDetails.getId());
user.setOccupation(newDetails.getOccupation());
user.setOriginCity(newDetails.getOriginCity());
user.setResidenceCity(newDetails.getResidenceCity());
user.setBirth(newDetails.getBirth());
user.setDescription(newDetails.getDescription());
}
return ok;
}
Fetching data from DB
#PostConstruct
public void init(){
userService = new UserService();
sessionController.setAuthUser(userService.read(getAuthUser().getId()));
originCity = getAuthUser().getUserDetail().getOriginCity();
residenceCity = getAuthUser().getUserDetail().getResidenceCity();
occupation = getAuthUser().getUserDetail().getOccupation();
birthDate = DateUtil.getStringDate(getAuthUser().getUserDetail().getBirth());
description = getAuthUser().getUserDetail().getDescription();
}
The problem is that the behavior of this code is different. Sometimes I obtain the desired result: once I submit the new details and call the #PostConstruct init () the new details are printed. Some other times the old details are printed even though the DB entry is updated.
Conclusion: Sometimes the JPA brings me different result from what is in the DB. I guess that this results consist of data from the Persistance Context, data which isn't updated. Is there a way in which I can be sure that the JPA always brings the data directly from the DB? Or is there something I'm missing?
If you are using JPA 2 then #Cacheable(false) on your entity definition should make it read from the DB every time.
You mean is there a way to turn the cache off or empty it before an operation ?
emf.getCache().evictAll();