Hibernate AUTO flush strategy order - java

I have 2 methods that exhibit a different behavior in regard to flushing in Hibernate.
The first one is:
#Transactional
public void firstMthod(int id, int status) {
Person entity = session.get(Person.class, id);
entity .setStatus(personStatus.registered);
session.merge(entity);
updatePersonAge(id,18);
}
The updatePersonAge method is located in another class, and the SQL output of this method looks like this:
select personel0_.ID as ID1_119_0_,
personel0_.status as status2_119_0_,
personel0_.age as age3_119_0_,
personel0_.CreatedBy as CreatedBy4_119_0_,
personel0_.UpdatedBy as UpdatedBy5_119_0_,
personel0_.CreatedDate as CreatedDate6_119_0_,
personel0_.UpdatedDate as UpdatedDate7_119_0_,
personel0_.Ip as Ip8_119_0_
from tbl_personel personel0_
where personel0_.ID = ?
update tbl_person set status = ? where ID = ?
update tbl_person set age = ? where ID = ?
and for the second use case, we have the following method:
#Override
#Transactional
public void secondMethod(int id,int courseId, int status) {
Course courseEntity=session.get(Course .class, courseId);
courseEntity.setCreatedDate(new Date());
session.merge(courseEntity);
updatePersonAge(id,18);
}
For which the updatePersonAge method generates the following SQL output:
select course0_.ID as ID1_120_0_,
course0_.CreatedBy as CreatedBy7_120_0_,
course0_.UpdatedBy as UpdatedBy8_120_0_,
course0_.CreatedDate as CreatedDate9_120_0_,
course0_.UpdatedDate as UpdatedDate10_120_0_,
course0_.Ip as Ip11_120_0_
from tbl_course course0_
where course0_.ID = ?
update tbl_course set created_date = ? where ID = ?
update tbl_person set age = ? where ID = ?
The updatePersoneAge method is :
public int updatePersonAge(int id,int age){
Query query = session.createQuery("update " + domainClass.getName() + " e set e.age= :age ");
query.setParameter("age ", age);
return query.executeUpdate();
}
According to my expectations, the output of the second method should be the same with the output of the first method. So why the difference? It is really confusing.

First of all, it makes no sense to call merge on an entity that is already attached to the currently running Session. Merge is meant to be used when you want to attach a detached entity.
Second, Hibernate FetchMode.AUTO flush only triggers the flush if the query about to be run overlaps with entities in the ActionQueue.
In the first example, because you modified the Person, and the query is run against a Person, it makes sense to trigger a flush as otherwise, the SQL query might return stale results.
In the second case, you modify a Course entity, yet you want to select from Person. So, there is no need to trigger the flush.
You can control this behavior using Query.addSyncronizedEntityName.

Apart from updating the age explicitly, inside the updatePersonAge method, in each of those transactions you are also updating implicitly (by getting an entity and changing one of the fields) other field of a managed entity.
As you are merging those changes, the PErsistence Provider has the obligation to flush those changes at the end of the transaction.
Thats why when you change the status:
Person entity = session.get(Person.class, id);
entity .setStatus(personStatus.registered);
session.merge(entity);
Hibernate persists that change along with the explicit age upate:
update tbl_person set status = ? where ID = ?
update tbl_person set age = ? where ID = ?
In the second method when you change a field of the Course entity:
Course courseEntity=session.get(Course .class, courseId);
courseEntity.setCreatedDate(new Date());
session.merge(courseEntity);
That change is persisted along with the explicit age update:
update tbl_course set created_date = ? where ID = ?
update tbl_person set age = ? where ID = ?

Related

Update by JPA native query

I am using spring boot repository with native query. While I am trying to update few fields by query and supply wrong values it is not throwing any exception.
here is my code,
#Modifying(clearAutomatically = true)
#Query(value = "update tbl_user set is_phone_Verified=true, mobile_verification_otp='XXX', updated_At=:updatedAt where " +
"phone_number=:registeredMobile and " +
"mobile_verification_otp=:phoneVerificationOtp", nativeQuery = true)
void updateMobileVerified(#Param("registeredMobile") String registeredMobile,
#Param("phoneVerificationOtp") String phoneVerificationOtp,
#Param("updatedAt") Date updatedAt);
Now, the issue is if I supply even wrong otp value, it is not throwing any exception. From service I am calling this method. and if there is no exception then I am returning true. Please find the service method.
#Override
public Boolean mobileVerification(String registeredMobile, String phoneVerificationOtp) {
try{
Date date = new Date();
userRepository.updateMobileVerified(registeredMobile,phoneVerificationOtp, date);
return true;
} catch(Exception e) {
return false;
}
}
Can someone please suggest me some way how can I determine if the update is successful.
Thanks.
If you would like to determine whether any rows were updated, the updateMobileVerified method can be defined to return int instead of void to indicate the number of rows updated
It seems that you are updating an unknown entity in the database. I highly recommend looking for a tbl_user entity by a unique id, like user_id or something like that. If the user doesn't exist, you either throw an exception or return false. In the case the given tbl_user was found, you basically update your desired attributes. This way, you force a strong control over updating users' data. I hope this might clarify your vision.

Sql query call fails if no record is found

I am trying to fetch a string from sql db. I have the following query:
select DISTINCT HARDWARE.NAME
from HARDWARE INNER JOIN
HARDWARE_LINKING
on HARDWARE.ID = HARDWARE_LINKING.ID
where HARDWARE_LINKING.EXTERNALID='5528752'.
Now when there is no record against any id the function call fails.
here is my function code that I am calling to fetch data:
public String search(String externalId) {
String SQL = "select DISTINCT HARDWARE.NAME from HARDWARE INNER JOIN HARDWARE_LINKING on HARDWARE.ID = HARDWARE_LINKING.ID where HARDWARE_LINKING.EXTERNALID=?";
Object[] input = new Object[] {externalId};
String name = jdbcTemplate.queryForObject(SQL,input, String.class);
return name;
}
Is there any way I can return a default value? and sometimes there are multiple values return but I want only single string value to be returned, Is that possible? Thankyou in advance
One method is to put the default into the query itself. Use an aggregation function and COALESCE():
select coalesce(h.NAME, '<default value>') as NAME
from HARDWARE h INNER JOIN
HARDWARE_LINKING hl
on h.ID = hl.ID
where hl.EXTERNALID = '5528752';
An aggregation query with no GROUP BY always returns one row, so this would seem to be more what you are looking for.
Also note that I put table aliases into the query. These make the query easier to write and to read.
You should be able to check name for null or isEmpty and then decide if you should return the name from the query or a default value.
if(name != null && !name.isEmpty()){
return name;
} else {
return "default";
}

Verify if Insertion query was completed in hibernate

So say I did something like this:
Employee emp=new Employee();
emp.setId(1); // PK
emp.setName("Earl");
emp.setAge("73");
session.save();
According to the tutorial here: http://www.mkyong.com/hibernate/hibernate-one-to-one-relationship-example/ , this should bring about an insert in the Employee table, assuming all mappings are correct. However, I keep on getting a serialization error. Is it because I am giving the ID a value? The query generates (However it takes Id as NULL... why is this?)
Is there any way I can verify barring checking the database to see if the query was done? Also, please do look at my other queries. I am very new to hibernate.
This can be done by keeping your id as generated value and you can type cast the return type to integer.In model class place this annotation upon id
#generatedvalue(strategy = generationtype.auto)
private int id;
Then there no need to insert the Employee id hibernate automatically generates the value and inserts and to check that values are inserted
Int i=(Integer)session.save(emp);
if(i > 0)
{
System.out.println("Values inserted");
}
First of all check that you pass a value to the function session.save():
session.save(emp);
If it still fails...
May be your primary key is set to auto increment. So you need not to specify a value for it.
Also try wrapping it in a transaction.
Just try this :
Transaction txn = session.beginTransaction();
Employee emp=new Employee();
//emp.setId(1); // PK
emp.setName("Earl");
emp.setAge("73");
session.save(emp);
txn.commit();
And best wishes..

Hibernate Envers - Doesn't write audit records for createQuery(...).executeUpdate(), only .persist() and .merge()

I have 3 ways things get written to the DB
public void create(T object) {
entityManager.persist(object);
}
public void update(T object) {
object = entityManager.merge(object);
}
public int updateStatus(String id, String status) {
final int changes =
entityManager.createQuery("update item set state = :newState," +
" last_modified = current_timestamp" +
" where id = : id ")
.setParameter("newState", status)
.setParameter("id", id)
.executeUpdate();
return changes;
}
The problem I have, is in order to get the Hibernate Envers to actually write the audit records to the corrsponsing x_aud and revinfo DB tables. It only works successfully for '.persist()' or '.merge()'. I cannot get it to work for 'createQuery(...).executeUpdate()'
Am I missing something or does it just not work for this. The problem is, a lot of my code has been written using .executeUpdate and not merge, so really I need this to work with the existing code.
Can anyone help please?
No, Envers won't work if you are using executeUpdate. That is because the update doesn't pass through Hibernate's event mechanism, so Envers has no chances of intercepting the change, and writing the audit.
It looks like Avinash T. is right - if you want to create native SQL query, use createNativeQuery(String sqlString) method of EntityManager. Using createQuery(String ejbqlString) is only possible if you're using EJB QL. Hope it would help.
Try this -
public int updateStatus(String id, String status) {
final int changes =
entityManager.createQuery("Update Item set state = :newState," +
" lastModified = CURRENT_TIMESTAMP" +
" where id = : id ")
.setParameter("newState", status)
.setParameter("id", id)
.executeUpdate();
return changes;
}
Following link wiil help you to learn more about JPQL -
http://docs.oracle.com/javaee/6/tutorial/doc/bnbtg.html

JPA Entity and its custom features

I have an entity Venue with its Events:
Event { ID, Name, DateTime }
Venue { ID, Name, #OneToMany List<Event> events}
What I would like to achieve is to be able to call these functions in view (with OpenEntityManagerInView):
customVenue.getId(); // no problem
customVenue.getName(); // no problem
customVenue.getEvents(); // no problem
customVenue.getCurrentEvents(); // hm?
customVenue.getPastEvents(); // hm?
There might be thousands of events in the database, so iterating over the "events" to get the current ones might not be a good idead.
Is this a correct approach? Is this feasible? How can I split events in current and past and to have them ordered?
Thanks!
Of course it's possible. But not using a method of customVenue (unless the entity has a reference to the entity manager, which is a bad idea).
Execute a query:
String jpql = "select event from Venue venue"
+ " inner join venue.events event"
+ " where event.date < :now"
+ " order by event.date asc";
List<Event> pastEvents = em.createQuery(jpql, Event.class)
.setParameter("now", new Date())
.getResultList();

Categories