Hibernate check if entity has changed - java

I have two applications - Server that has connection with database and Client which doesn't. Server loads entity and sends it to Client. Client edits it and sends it back so that Server can update it. But... there are many Clients and many of them can edit that entity and want to update it with their own value but I want Server to update it only if it hasn't been updated since it had been send to Client. Namely: Clients try to set ticket's status to their own value but only if current status in database is the same that it was when that Client received it. Currently I have it like that:
public boolean changeStatus (Ticket ticket, short newStatus) {
short previousStatus = ticket.getStatus();
boolean result = false;
Session session = sessionFactory.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
String sql = "UPDATE ticket SET status = :newStatus WHERE idTicket = :idTicket AND status = :previousStatus";
NativeQuery query = session.createSQLQuery(sql);
query.setParameter("idTicket", ticket.getIdTicket());
query.setParameter("newStatus", newStatus);
query.setParameter("previousStatus", previousStatus);
result = query.executeUpdate() == 1;
but sometimes I also want to set other atributes and check if other atributes hasn't changed and using that method would make it very ugly and I want it to be more object-oriented not plain sql. Any suggestions?
Forgot to mention that each Client has it's own Server thread that might try to update

Related

Return null instead of org.hibernate.ObjectNotFoundException: Java

I have a table called ad_session which logs user sessions. I am using Java to get a list of all successful sessions from that table. I then loop through that list to get the user for each session (which is a foreign key to the ad_user table). I then get the client that belongs to that user, and I add the client to a list. However, one of the users no longer exists, so my code stops running and it gives throws the following exception:
org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [ADUser#76A5C22E6D2446A399AE9AD7C1DED0C7]
This is my original code:
List<Session> sessions = getAllSuccessfulSessionsInTable();
List<Client> clientsForThatDay = new ArrayList<>();
try {
for (Session session : sessions) {
//code fails when trying to get the non-existent user:
User user = session.getCreatedBy();
Client userClient = user.getClient();
clientsForThatDay.add(userClient);
}
} catch (Exception e) {
log.error("Error getting client from user: ", e);
}
I assumed that when getting a non-existent record, it would return null, so this is what I tried:
List<Session> sessions = getAllSuccessfulSessionsInTable();
List<Client> clientsForThatDay = new ArrayList<>();
//Create new user object to stand in place of the non-existent user
User deletedUser = new User();
deletedUser.setName("Deleted User");
//Create new client object to stand in place of the non-existent client
Client deletedUserClient = new Client();
deletedUserClient.setName("Unknown Client");
try {
for (Session session : sessions) {
//check is User is null, if it is, use the deletedUser object, otherwise, use the existing user
User user = session.getCreatedBy() == null ? deletedUser : session.getCreatedBy();
Client userClient = user.getName().equals("Deleted User") ? deletedUserClient : user.getClient();
clientsForThatDay.add(userClient);
}
} catch (Exception e) {
log.error("Error getting client from user: ", e);
}
However, it is not returning null, it's just throwing the exception and then stopping.
How can I get it to return null here so I can deal with the missing record without my code stopping?
Thanks in advance for any suggestions.
It seems that your database is missing a foreign key constraint.
This means that the table mapping User has a reference to a row in the table for Client that no longer exist.
This can only happen if a client has been deleted without updating the user table. The solution would be to add a foreign key constraint between the tables.
Keep in mind that if the data in your tables are not correct, when Hibernate loads the entity User, it will also believe there's a client. This means that User#getClient won't be null, and every place in the code where you have a check like user.getClient() == null is going to fail. A try-catch approach won't help you with this (unless you set the association to null in case of error, I guess).
The solutions I can think of:
Add the foreign key constraint (imho, the best solution)
Don't map the association, map client_id as an attribute and load the client using a second query or find (I would only do this if you cannot update the database)
class User {
#Column(name = "client_id")
Long clientId;
}
User user = ...
Client client = session.find(Client.class, user.getClientId());
You can load the client via session.find(Client.class, user.getClient().getId()) and set the association with the result:
User user = //...
Client client = session.find(Client.class, user.getClient().getId());
user.setClient(client);
Don't map the association at all in User, and run a native SQL query to load the client:
User user = ...
String sql = "select * from Client c join User u on c.id = u.client_id where u.id = :uid";
Client client = session.createNativeQuery(sql, Client.class)
.setParameter("uid", user.getId())
.getSingleResultOrNull();
You can pick what works best for you, but keep in mind that mapping an association without the foreign key constraint, will cause all sort of consistency issues.
I've decided to put option 3 only because, sometimes, people have some impossible situations at work, but I wouldn't recommend it.

Hibernate - the second query gives Unknown service requested

I'm trying to understand better how Hibernate works...
I've a problem I cannot resolve.
When the application starts, it makes a query
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
int result;
String query = "SELECT count(*) as posti_disponibili from occupazione t inner join ";
query += "(select id_posto_park, max(date_time) as MaxDate from occupazione group by id_posto_park) tm on ";
query += "t.id_posto_park = tm.id_posto_park and t.date_time = tm.Maxdate and t.isOccupied = 0";
BigInteger bi = (BigInteger) session.createSQLQuery(query).uniqueResult();
result = bi.intValue();
HibernateUtil.shutdown();
At the end I close the current session.
Then, after it, I have a second query to be accomplished:
I open a new session (the first one was closed with the method HibernateUtil.shutdown();)
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
Client client = new Client();
client.setIdClient(clientId);
String queryString ="from it.besmart.models.Client where clientId = :c)";
List<?> list = session.createQuery(queryString).setProperties(client).list();
but I got, now,
org.hibernate.service.UnknownServiceException: Unknown service requested [org.hibernate.cache.spi.RegionFactory]
at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:184)
at org.hibernate.cfg.Settings.getRegionFactory(Settings.java:300)
at org.hibernate.internal.SessionFactoryImpl$SessionBuilderImpl.openSession(SessionFactoryImpl.java:1322)
at org.hibernate.internal.SessionFactoryImpl.openSession(SessionFactoryImpl.java:677)
at it.besmart.parkserver.SocketClientHandler.run(SocketClientHandler.java:78)
at java.lang.Thread.run(Thread.java:744)
I cannot understand why, I closed the first session, but then opened a new one..
Is it correct to close the session on each query
EDIT
I'm trying to solve this problem, but with no result.
Now I have the first select query, which goes well. It's at the startup of the application.
try {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
String query = "SELECT count(*) as posti_disponibili from occupazione t inner join ";
query += "(select id_posto_park, max(date_time) as MaxDate from occupazione group by id_posto_park) tm on ";
query += "t.id_posto_park = tm.id_posto_park and t.date_time = tm.Maxdate and t.isOccupied = 0";
BigInteger bi = (BigInteger) session.createSQLQuery(query).uniqueResult();
result = bi.intValue();
}
I do not commit or flush it.
Then, going up with the application, I have the second query, so I getCurrentSession and try to do the select
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Client client = new Client();
client.setIdClient(clientId);
String queryString ="from it.besmart.models.Client c where c.clientId = :c";
logger.debug(queryString);
// logger.debug(session);
Query theQuery = session.createQuery(queryString).setProperties(client);
List<?> list = theQuery.list();
The application stops, nothing comes out, I don't know what's going on also because I cannot setup hibernate to log with pi4j...
Is there something wrong in how I use hibernate sessions?
If you use sessionFactory.getCurrentSession(), you'll obtain a "current session" which is bound to the lifecycle of the transaction and will be automatically flushed and closed when the transaction ends (commit or rollback).
If you decide to use sessionFactory.openSession(), you'll have to manage the session yourself and to flush and close it "manually".
For more info go to Hibernate transactions.

InnoDB database locking blocks threads

I have a really weird issue with a project I'm working with. I would appreciate if someone could point me to a right direction here.
// Setup
There are multiple web servers and a loadbalancer is in front of them. Servers are handling requests that might come in multiple parts and parts can be handled by different servers. These multi-part requests should be combined to a one single transaction that is going forward once all the parts are received.
The server that does the final processing doesn't matter, but only one server can do it. Other servers that receive the previous parts should just mark the part received, store the data and give a immediate response back.
For now I'm using database table to handle the synchronization between nodes.
The basic idea is that when a server gets a part it tries to acquire the lock with a transaction id coming with the rquest. This is done by trying to insert a row to a Lock table with the txid as a primary key. If insert is successful, that server gets the lock and processes the part it received, by storing it to database checks if other parts have been received and returns a response immediately if not.
// The Problem
The problem I have is that the threads seem to randomly lock at the database and thus freezing the whole processing. I have debugged it to the point that in a situation where multiple requests come to processing at the same time they just get stuck at trying to acquire the lock and ultimately timeout after 30 seconds. Few of the first requests might get processed or not it seems to be random but even something like 7 concurrent requests block the database.
To me there should not be any way how this could get stuck and I'm fresh out of ideas.
// Information
I am using MySQL with an InnoDB engine. Servers are running Java code and Hibernate is used as a ORM layer to access the DB.
The Lock table:
CREATE TABLE `lock` (
`id` varchar(255) NOT NULL,
`expiryDate` datetime DEFAULT NULL,
`issueDate` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
The id is the transaction id used to combine the parts.
I have an basic interface that manages the lock accessing.
public interface LockProviderDao {
public boolean lock(String id);
public boolean unlock(String id);
}
And a implementation of that class that uses Hibernate to access database.
#Override
public boolean lock(String id) {
Session session = this.sessionFactory.openSession();
Lock lock = new Lock(id);
Transaction tx = null;
boolean locked = false;
try {
// Try to lock
tx = session.beginTransaction();
session.save(lock);
tx.commit();
locked = true;
} catch(Exception e) {
if(tx != null) {
tx.rollback();
}
} finally {
session.close();
}
return locked;
}
#Override
public boolean unlock(String id) {
Session session = this.sessionFactory.openSession();
boolean status = true;
Transaction tx = null;
try {
Lock lock = (Lock) session.load(Lock.class, id);
tx = session.beginTransaction();
session.delete(lock);
tx.commit();
} catch(Exception e) {
if(tx != null) {
tx.rollback();
}
status = false;
} finally {
session.close();
}
return status;
}
Seems simple enough. Here is the code that does the processing. This thread has a Hibernate session opened already so the Session opened inside the lock and unlock methods is a nested Session, if that makes any difference.
int counter = 0;
boolean lockAcquired = false;
do {
// Try to acquire the lock
lockAcquired = this.lockProviderDao.lock(txId);
if (!lockAcquired) {
// Didn't get it try a bit later
try {
Thread.sleep(defaultSleepPeriod);
} catch (Exception e) {
}
if (counter >= defaultSleepCycles) {
return;
}
counter++;
}
} while (!lockAcquired);
// DO THE PROCESSING HERE ONCE LOCK ACQUIRED
// Release the lock
this.lockProviderDao.unlock(txId);
I would lock after inserting the data. This means, that you would have to change your algorithm to something like this:
Begin transaction
Insert the fragment to database
Commit transaction
Begin transaction
Count number of framgents inserted / exit, if not equal to expected fragment count
Insert a row, that indicates that fragments will be processed (e.g. your lock row). If this fails, fragments have been processed or are being processed (= rollback)
Commit transaction
Begin transaction
Read fragments (and verify that they still exist)
Process fragments
Delete lock and fragments (verify they still exist)
Commit transaction
If you need to increase reliability, you have three options:
1. Use JMS with JTA to control program flow
2. Have your client poll the server for status and start processing, if all parts have been received, but processing has not started yet or has been stalled
3. Create a scheduler that starts processing, if same conditions apply

hibernate query result not updating after change

I am having an issue with hibernate. the query result is not updating.
i have a simple query which checks customertable to see if the 'enabled' column = true. the query works fine. but when i change the colum value from 'true' to 'false' and run the same query... it still gives me 'true' as a result.
if i close the application and recompile, and run query again, it THEN shows false, but then again if i change it back to true.. it still shows 'false' result. what am I doing wrong?
public void isEnabled(String customer){
Session session = sessionFactory.openSession();
Transaction tx = null;
try{
tx=session.beginTransaction();
Query query = session.createQuery("FROM Customer WHERE enabled=true");
List qr = query.list();
boolean check = qr.iterator().hasNext();
boolean enabled;
sw = new ServerWriter(writer);
if(check){
for(Iterator itr = qr.iterator();itr.hasNext();)
{
Customer c =(Customer)itr.next();
enabled=c.geEnabled();
sw.sendMessage("Customer is enabled");
}
}else{
sw.sendMessage("Customer is not enabled");
}
} catch(HibernateException e){
if(tx!=null){tx.rollback();}
e.printStackTrace();
}finally{session.close();}
}
First you forgot to close the transaction:
session.getTransaction().commit();
The reason you get the same value when querying second time is Hibernate cache. You always have a first level cache and if you configured it, you can have a second level cache, too.
You can refresh the first level cache before executing the query with:
session.refresh()
If you happen to have a second level cache you can skip it with this hint:
query.setHint("org.hibernate.cacheMode", CacheMode.IGNORE);

Delay during commit (using JPA/JTA)

I would like to ask you for help with following problem. I have method:
String sql = "INSERT INTO table ...."
Query query = em.createNativeQuery(sql);
query.executeUpdate();
sql = "SELECT max(id) FROM ......";
query = em.createNativeQuery(sql);
Integer importId = ((BigDecimal) query.getSingleResult()).intValue();
for (EndurDealItem item : deal.getItems()) {
String sql2 = "INSERT INTO another_table";
em.createNativeQuery(sql2).executeUpdate();
}
And after executing it, data are not commited (it takes like 10 or 15 minutes until data are commited). Is there any way how to commit data explicitly or trigger commit? And what causes the transaction to remain uncommited for such a long time?
The reason we use nativeQueries is, that we are exporting data on some shared interface and we are not using the data anymore.
I would like to mention, that the transaction is Container-Managed (by Geronimo). EntityManager is created via linking:
#PersistenceContext(unitName = "XXXX", type = PersistenceContextType.TRANSACTION)
private EntityManager em;
Use explicitly the transaction commit:
EntityManager em = /* get an entity manager */;
em.getTransaction().begin();
// make some changes
em.getTransaction().commit();
This should work. The time of execution of all operation between .begin() and .end() depends of course also from the cycle you're performing, the number of row you're inserting, from the position of the database (the speed of the network matters) and so on...

Categories

Resources