How to handle EntityNotFoundException during Hibernate query? - java

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?

Related

Writing distinct in hibernate criteria

I want to write the below query using criteria .
I need to find the distinct rows and also use the current date in where clause .How can I achieve this in Criteria.
SELECT DISTINCT *
FROM EMAIL_PROGRAM
WHERE CURRENT_DATE >=PGM_START_DT
AND CURRENT_DATE <= PGM_END_DT
AND EMAIL_PGM_FLG ='Y'
AND EMAIL_PGM_DESC IS NOT NULL
and RGN_CD = 'US';
Below is my code in which I need to apply .
SessionFactory factory = null;
Session session = null;
try {
factory = getSessionFactory();
session = factory.openSession();
final Criteria criteria = session
.createCriteria(EmailDataBean.class);
returnList = criteria.list();
} catch (Exception e) {
logger.error(e.getMessage());
throw new DAOException(e);
} finally {
DBUtil.close(factory, session);
}
if (logger.isInfoEnabled()) {
logger.info(LOG_METHOD_EXIT);
}
return returnList;
}
you can use below on your criteria object.
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
You should do something like
//first add conditions in the where clause (you should use property names as defined in your Hbernate entities , not column names in your DB)
criteria.add(Restrictions.ge("PGM_START_DT", startDt));
criteria.add(Restrictions.le("PGM_END_DT", endDt));
criteria.add(Restrictions.eq("EMAIL_PGM_FLG", "Y"));
criteria.add(Restrictions.isNotNull("EMAIL_PGM_DESC"));
criteria.add(Restrictions.eq("RGN_CD", "US"));
Now, add every column (i.e. a Hibernate entity property/field) to a Projection list (this is needed to support distinct in the query)
ProjectionList pList = Projections.projectionList();
pList.add(Projections.property("PGM_START_DT"), "PGM_START_DT");
pList.add(Projections.property("PGM_END_DT"), "PGM_END_DT");
// add all the other properties (columns) and then have the Projections.distinct method act on the entire row (all the columns)
criteria.setProjection(Projections.distinct(pList));
By default, using Projections does return the result as a List<Object[]> rather than List<EmailDataBean>, which is usually not convenient. To remedy that, you should set a ResultTransformer
crit.setResultTransformer(Transformers.aliasToBean(EmailDataBean.class));
Alternatively, instead of using Projections, you can use
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
but this will not fetch distinct rows from the database but rather have Hibernate filter the results (removing the duplicates).

How to get First 5 elements Hibernate

I have a very long object, so to make the system faster, I decided to show only the top 5 on "attendance" attribute (insted to show all the elements) when retrieve by the "get" service.
I have a attendances list, but there is a way to say only get the top 5 (on the list get) and when I got the results from the "detail" service, I will have all the itens.
#ManyToMany
private List<Attendance> attendance;
Write a custom repostitory, inject the entitymanager and use a query like that:
entityManager.createQuery("Select a from Enity e join e.attendence a")
.setMaxResults(5)
.getResultList();
Even you can do by Hibernate Criteria:
public List findByLimit(Class persistentClass, String orderproperty, int limit)
{
Session session = getSession(); // Get Session here
try
{
Criteria criteria = session.createCriteria(persistentClass);
criteria.addOrder(Order.desc(orderproperty));
criteria.setMaxResults(limit);
criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
return criteria.list();
}
catch (RuntimeException re)
{
//Catch exception
}
finally
{
//close the session
}
}
Now call the above function :
findByLimit(Example.class,id,5); // here id is your order property

Spring3/Hibernate AssertionFailure

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.

Is the following Hibernate custom ID generator code correct?

I just created a custom hibernate ID generator, and since I'm not an hibernate expert I would like to get some feedback on my code. The generated ID is select max(id) from table, +1.
public class MaxIdGenerator implements IdentifierGenerator, Configurable {
private Type identifierType;
private String tableName;
private String columnName;
#Override
public void configure(Type type, Properties params, Dialect dialect) {
identifierType = type;
tableName = (String) params.getProperty("target_table");
columnName = (String) params.getProperty("target_column");
}
#Override
public synchronized Serializable generate(SessionImplementor session,
Object object) {
return generateHolder(session).makeValue();
}
protected IntegralDataTypeHolder generateHolder(SessionImplementor session) {
Connection connection = session.connection();
try {
IntegralDataTypeHolder value = IdentifierGeneratorHelper
.getIntegralDataTypeHolder(identifierType
.getReturnedClass());
String sql = "select max(" + columnName + ") from " + tableName;
PreparedStatement qps = connection.prepareStatement(sql);
try {
ResultSet rs = qps.executeQuery();
if (rs.next())
value.initialize(rs, 1);
else
value.initialize(1);
rs.close();
} finally {
qps.close();
}
return value.copy().increment();
} catch (SQLException e) {
throw new IdentifierGenerationException(
"Can't select max id value", e);
}
}
}
I'd like to know:
How can I make this multi-transaction-safe? (ie if two concurrent transactions insert data, how can I safely assume that I will not end-up having twice the same ID?) -- I guess the only solution here would be to prevent two concurrent hibernate transaction to run at the same time if they use the same generator, is this possible?
If the code could be improved: I feel wrong having to use hard-coded "select", "target_column", etc...
To guarantee point 1), I can fallback if necessary on synchronizing inserts on my java client code.
Please do not comment on the reasons why I'm using this kind of generator: legacy code still insert data onto the same database and uses this mechanism... and can't be modified. And yes, I know, it sucks.
I think the easiest way to achive a transaction-safe behaviour is to put the code you use to retrive the maximum id and do the insert statement, in a transactional block.
Something like:
Transaction transaction = session.beginTransaction();
//some code...
transaction.commit();
session.close()
I also recommend to use HQL (Hibernate Query Language) to create the query, instead of native sql where possible. Moreover, from your description, I have understood that you expect from the query a unique result, the maximum id. So, you could use uniqueResult() method over your query instead of executeQuery.
you can use AtomicInteger for generating ID. That can be used by many threads concurrently.
If you are free to use any other provider of ID then i will suggest to use UUID class for generating random ID.
UUID.randomUUID();
You can refer to link which contain some other ways to generate ID.

How do I stop hibernate returning multiple instances when using "join" fetch mode?

Given the following I am trying to force the child collection (countryData) to be loaded when I perform the query, this works however I end up with duplicates of the Bin records loaded.
public Collection<Bin> getBinsByPromotion(String season, String promotion) {
final Session session = sessionFactory.getCurrentSession();
try {
session.beginTransaction();
return (List<Bin>) session.createCriteria(Bin.class).
setFetchMode("countryData", FetchMode.JOIN).
add(Restrictions.eq("key.seasonCode", season)).
add(Restrictions.eq("key.promotionCode", promotion)).
add(Restrictions.ne("status", "closed")).
list();
} finally {
session.getTransaction().commit();
}
}
I don't want the default (lazy) behavior as the query will return ~8k records thus sending 16k additional queries off to get the child records.
If nothing else I'd prefer.
select ... from bins b where b.seasonCode = ?
and b.promotionCode = ?
and b.status <> 'Closed';
select ... from binCountry bc where bc.seasonCode = ?
and bc.promotionCode = ?;
you can use CriteriaSpecification.DISTINCT_ROOT_ENTITY;
criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);

Categories

Resources