Here is a simple hibernate code that inserts a value into a table.
If the row already exists, query the row and return the data.
Most of the time, the code works fine with no issues.
In a very special case, three different clients are trying to insert the exact the same row into the table. Ofcourse, only one row gets inserted. The other two insertions fail and the fall into the try-catch block.
There is a query in the try catch block, which queries the data and sends the value to the client. This results in an error for subsequent operations on the session.
Hibernate throws "ERROR org.hibernate.AssertionFailure - an assertion
failure occured (this may indicate a bug in Hibernate, but is more
likely due to unsafe use of the session)" in the logs.
Here is the code. What would be the right way to handle this scenario?
#Override
public void addPackage(PackageEntity pkg) {
try{
getCurrentSession().save(pkg);
getCurrentSession().flush();
}catch( ConstraintViolationException cve ){
// UNIQ constraint is violated
// query now, instead of insert
System.out.println("Querying again because of UNIQ constraint : "+ pkg);
PackageEntity p1 = getPackage(pkg.getName(), pkg.getVersion());
if( p1 == null ){
// something seriously wrong
throw new RuntimeException("Unable to query or insert " + pkg);
}else{
pkg.setId(p1.getId());
}
}catch (Exception e) {
e.printStackTrace();
}catch (Throwable t) {
t.printStackTrace();
}
}
Primary (or) composite Key makes each row data unique and avoids this error.
If you need the data from all these three requests then create a unique primary key in your table and add it to the entity.
Primary Key could be any unique thing from your data, an auto generated sequence or UUID/GUID.
Related
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.
I'm working on an application where we need to query for a collection of entities using hibernate and then perform some indexing on the results. Unfortunately, the database the application is querying from does not enforce foreign key constraints on all associations, so the application needs to handle potentially broken references.
Currently, the code responsible for doing the query looks like this:
private List<? extends Entity> findAll(Class entity, Integer startIndex, Integer pageSize)
throws EntityIndexingServiceException {
try {
Query query = entityManager
.createQuery("select entity from " + entity.getName() + " entity order by entity.id desc");
if (startIndex != null && pageSize != null) {
return query.setFirstResult(startIndex).setMaxResults(pageSize).getResultList();
} else {
return query.getResultList();
}
}
catch (Throwable e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
log.warn(sw.toString());
return Collections.EMPTY_LIST;
}
}
The problem with this code is that bad data in any of the results will result in the whole page of data being skipped. So if I'm using this method to get a list of objects to index, none of the query results will be included in the indexing even though only one or two were invalid.
I know Hibernate has a #NotFound annotation, but that comes with it's own side-effects like forced eager loading that I would rather avoid. If possible, I want to exclude invalid entities from the results, not load them in a broken state.
So the question then is how can I handle this query in such a way that invalid results (those that cause an EntiyNotFoundException to be thrown) are excluded from the return values without also throwing out the 'good' data?
I have a column called Sequence No. Anytime my java program receives a message, the message has a sequence number that is always higher than the last message. When I update a row I need to check if the sequence number is greater than the sequence number in my database, if it is not I need to drop the message. Something like Update MyTable t where t.sequenceNo < SEQUENCE_NO_VALUE set (t.sequenceNo = SEQUENCE_NO_VALUE, Set all my columns..); I have done this before with SQL, but never using JPA. My other issue is that I would like to update the entire row at once. Something like this...
public void saveWithCondition(Account entity, Integer sequenceNO) {
EntityManagerHelper.log("saving Account instance", Level.INFO, null);
try {
CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
CriteriaUpdate<Account> update = criteriaBuilder.createCriteriaUpdate(Account.class);
update.set(entity);//set the entire row instead of each column.
update.where(criteriaBuilder.lessThan(entity.getODSSequenceNo(), SequenceNO));
this.em.createQuery(update).executeUpdate();
} catch (RuntimeException re) {
EntityManagerHelper.log("save failed", Level.SEVERE, re);
throw re;
}
}
i am getting below exception when trying to insert my rows
java.sql.SQLException: Parameter metadata not available for the given statement
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1078)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:989)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:975)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:920)
at com.mysql.jdbc.MysqlParameterMetadata.checkAvailable(MysqlParameterMetadata.java:70)
at com.mysql.jdbc.MysqlParameterMetadata.getParameterType(MysqlParameterMetadata.java:119)
at org.springframework.jdbc.core.StatementCreatorUtils.setNull(StatementCreatorUtils.java:231)
at org.springframework.jdbc.core.StatementCreatorUtils.setParameterValueInternal(StatementCreatorUtils.java:213)
at org.springframework.jdbc.core.StatementCreatorUtils.setParameterValue(StatementCreatorUtils.java:144)
at org.springframework.jdbc.core.ArgumentPreparedStatementSetter.doSetValue(ArgumentPreparedStatementSetter.java:65)
at org.springframework.jdbc.core.ArgumentPreparedStatementSetter.setValues(ArgumentPreparedStatementSetter.java:46)
at org.springframework.jdbc.core.JdbcTemplate$2.doInPreparedStatement(JdbcTemplate.java:822)
at org.springframework.jdbc.core.JdbcTemplate$2.doInPreparedStatement(JdbcTemplate.java:818)
where as my insert was successful, but after seeing the console i found there was exception thrown during my insert.
though this exception doesnt stop my flow, it cause potential issue when there is an error on actual insert, for e.g i have unique column in the table and when i try with duplicate column value , i am not getting the MySQLIntegrityConstraintViolationException , which i need to handle this.
EDIT:-
public int addUserAcc(UserDO userDO)
throws UserDataException {
JdbcTemplate jd = this.getJdbctemplate();
try {
Long userId= jd.queryForObject(USER_KEY,
Long.class);
jd.update(ADD_USERACC, new Object[] { userId,
userDO.getFirstName(), userDO.getLastName(),
userDO.getMobile(), userDO.getEmail(),
userDO.deleteFlag() });
} catch (DataAccessException dExp) {
throw new UserDataException(
"error creating user ", dExp);
}
return 0;
}
Thanks for your answers.
I faced same exception, it's because types is not specified as third argument.
I run the below code in multithreaded program and get exception:-
org.hibernate.exception.ConstraintViolationException: ERROR: duplicate
key value violates unique constraint "value_pkey"
#Stateless
#TransactionAttribute(javax.ejb.TransactionAttributeType.REQUIRED)
public class GetHelloBean{
#PersistenceContext(unitName = "test-unit")
private EntityManager entityManager;
public Hello<?> insertOrUpdateHello(Hello<?> value) {
Hello<?> existing = null;
try {
existing = this.entityManager.find(Hello.class,
value.getKey());
if (existing != null) {
value = this.entityManager.merge(value);
} else {
this.entityManager.persist(value);
}
this.entityManager.flush();
} catch (Exception e) {
this.logger.error(" value not saved : " + value.toString()
+ " of class " + value.getClass() + ":" + e.getMessage());
}
//
return value;
}
}
Can someone explain why and how can i handle this?
This error may be caused by a race condition in your code. If two or more threads are trying to update a Hello entity with the same key, they may both have a null returned from find and will attempt to persist the entity. Consequently only the "fastest" of the threads will succeed, while others encounter the constraint violation.
If value.getKey() is the primary key of the Hello entities, doing only a merge(...) should be sufficient. Hibernate will check if the entity already exists in the db or the cache and executes an INSERT or UPDATE depending on that. Concurrent executions may still yield unexpected results though (perceived out of order updates).
If it is not the primary key of the Hello entities maybe this answer can help you.