Hibernate 5.2 and @Transient


#1

In few of our entity classes we have an annotation called @NoPersist. If the value of this annotation is false (configurable centrally elsewhere) then we are supposed to make the property behave as @Transient.

2 questions regarding this:

Still now we were using MetadataProviderInjector to set a custom MetadataProvider written by us which in turn internally use an implementation of AnnotationReader to return @Transient when required. Unfortunately with Hibernate 5.2 that mechanism is not functional anymore. Do you have any example of doing this using Integrator or any other means.
I tried using a custom integrator, entity listener and attribute converter but none of those seems to work. It helps if you provide some working example which can be easily tried which will not break again with advent of Hibernate 6 and later.

If the same behavior is required with SchemaExporter to not to export the column when required, how to do this effectively.
I just need the examples to be simple enough so that we can maintain them with future Hibernate versions (v6.0 or else) going forward.


#2

Better provide a custom DefaultPersistEventListener which inspects if there’s any @NoPersist annotation for the currently persisting entity and skip the execution.

Check out the User Guide for more details.


#3

We are using @NoPersist on property level rather than class level. This means the schema / column representing this property might not exist in database (depending on configuration) so during load time we need to read the @Entity class property and return @Transient so that Hibernate ignores this column altogether.

I used EventListenerRegistry from my integrator i.e. eventListenerRegistry.prependListeners(EventType.LOAD, MyLoadEventListener.class) so that we can replace the property having @NoPersist annotation with @Transient.

The @Entity having @NoPersist annotation is used with a call like getSession().createCriteria() and the listener MyLoadEventListener is never getting called (I had overridden onLoad() method there to do some logging).

It throws an exception (as the property is there in @Entity but not in database) like following:

       at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:106) ~[hibernate-core.jar:5.2.15.Final]
        at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42) ~[hibernate-core.jar:5.2.15.Final]
        at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:111) ~[hibernate-core.jar:5.2.15.Final]
        at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:97) ~[hibernate-core.jar:5.2.15.Final]
        at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:69) ~[hibernate-core.jar:5.2.15.Final]
        at org.hibernate.loader.Loader.getResultSet(Loader.java:2168) ~[hibernate-core.jar:5.2.15.Final]
        at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1931) ~[hibernate-core.jar:5.2.15.Final]
        at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1893) ~[hibernate-core.jar:5.2.15.Final]
        at org.hibernate.loader.Loader.doQuery(Loader.java:938) ~[hibernate-core.jar:5.2.15.Final]
        at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:341) ~[hibernate-core.jar:5.2.15.Final]
        at org.hibernate.loader.Loader.doList(Loader.java:2692) ~[hibernate-core.jar:5.2.15.Final]
        at org.hibernate.loader.Loader.doList(Loader.java:2675) ~[hibernate-core.jar:5.2.15.Final]
        at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2507) ~[hibernate-core.jar:5.2.15.Final]
        at org.hibernate.loader.Loader.list(Loader.java:2502) ~[hibernate-core.jar:5.2.15.Final]
        at org.hibernate.loader.criteria.CriteriaLoader.list(CriteriaLoader.java:109) ~[hibernate-core.jar:5.2.15.Final]
        at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1897) ~[hibernate-core.jar:5.2.15.Final]
        at org.hibernate.internal.CriteriaImpl.list(CriteriaImpl.java:370) ~[hibernate-core.jar:5.2.15.Final]

Other than EventType.LOAD I tried with EventType.PERSIST, EventType.PRELOAD and EventType.POSTLOAD along with respective listeners but everywhere we are seeing same results.

Please provide some good examples to try out which can be locally tested before picking it up for my work.


#4

First, the exception you provided is just a fragment and lack the root cause. Therefore, it’s impossible to tell what went wrong.

Second, providing a custom DefaultLoadEventListener to replace the standard one require a lot of diligence. For instance, calling getSession().createCriteria() to load the entity inside the DefaultLoadEventListener sounds like a hacky solution. Why not use the doOnLoad from DefaultLoadEventListener?

Please provide some good examples to try out which can be locally tested before picking it up for my work.

We don’t have any such example since this use case is not supported by Hibernate.


#5

I am not using the custom DefaultLoadEventListener to call getSession().createCriteria().

The issue is my LoadEventListener interface implementation class MyLoadEventListener is not getting called when somebody else is trying to load my entity using getSession().createCriteria(MyEntity.class) where MyEntity is having the @NoPersist annotation.

Following here are the questions:

  1. In Hibernate 5.2 does an entity load using createCriteria() gets a callback in the registered listener ?
  2. How to use doOnLoad() or anything else when my listener is not getting called at all in the first hand ?

#6

The getSession().createCriteria is deprecated, so I will talk about the JPA Criteria API instead. Theoretically, the underlying mechanism should be the same.

Yes. The org.hibernate.loader.Loader doe sit:

initializeEntitiesAndCollections(
	hydratedObjects,
	rs,
	session,
	queryParameters.isReadOnly( session ),
	afterLoadActions
);

How to use doOnLoad() or anything else when my listener is not getting called at all in the first hand?

Just debug it to see why it is not getting called by the Loader.


#7

Just debugged this issue. Let’s say we have an Entity class called MyEntity

public class MyEntity
{
private String a;

private String b;

@NoPersist
@Column(name = "c")
private String c;

}

The table MyTable has only 2 columns a & b instead of 3 (a, b & c).
Now if somebody is calling session.createCriteria(MyEntity.class) org.hibernate.loader.Loader is executing the following code:

public Object loadSingleRow(
		final ResultSet resultSet,
		final SharedSessionContractImplementor session,
		final QueryParameters queryParameters,
		final boolean returnProxies) throws HibernateException {

	final int entitySpan = getEntityPersisters().length;
	final List hydratedObjects = entitySpan == 0 ?
			null : new ArrayList( entitySpan );

	final Object result;
	try {
		result = getRowFromResultSet(
				resultSet,
				session,
				queryParameters,
				getLockModes( queryParameters.getLockOptions() ),
				null,
				hydratedObjects,
				new EntityKey[entitySpan],
				returnProxies
		);
	}
	catch (SQLException sqle) {
		throw factory.getJdbcServices().getSqlExceptionHelper().convert(
				sqle,
				"could not read next row of results",
				getSQLString()
		);
	}

	initializeEntitiesAndCollections(
			hydratedObjects,
			resultSet,
			session,
			queryParameters.isReadOnly( session )
	);
	session.getPersistenceContext().initializeNonLazyCollections();
	return result;

Now getRowFromResultSet() method will definitely throw an Exception out even before it is reaching initializeEntitiesAndCollections(). As the entity MyEntity and the table schema MyTable doesn’t match.

Using MetadataProviderInjector/MetadataProvider/AnnotationReader till Hibernate 3.x we convert MyEntity metamodel like following:

Entity
Table(name = “MyTable”)
public class MyEntity
{
private String a;

private String b;

**@Transient**
@Column(name = "c")
private String c;

}

Is there a way in Hibernate 5.2 to achieve this safely ? I thought the interface LoadEventListener is just for this purpose and you also said the same.

We need such listener to be called beforehand so that the Entity, MyEntity and the table schema MyTable becomes compatible on the fly before org.hibernate.loader.Loader executes the query.


#8

Why would the table and the entity not match?

I guess it’s not very clear to me what you are doing.


#9

We have the notion of annotations like NoPersist which can be configured centrally. Now suppose this NoPersist annotation is set to true.

The database schema creation script would come and see if NoPersist is true then don’t create a column as in my example ‘c’.

In MyEntity class as will use NoPersist annotation, in runtime we need to replace it with @Transient so that Hibernate overlooks it.

This way the day when NoPersist will become false, both the database script and the Entity MyEntity will start using the column ‘c’ dynamically.

We do this in Hibernate 3.x using MetadataProviderInjector/MetadataProvider and AnnotationReader, is there any way to do this in Hibernate 5.2 ?


#10

Officially, no. Even on 3.x, you hapenned to found a workaround. I’m not familiar with 3.x, being way too old and no longer unsupported as well.

But even if you find a workaround by extending internal classes, it’s not guaranteed that will work in 6.x.

The only way to do it safely is by providing the right entities before you bootstrap. Using XML mappings would be more flexible since you can just vary the XML mapping based on some rules and a pre-XSLT processor.

Having it fully dynamic and varying the schema at runtime is not how Hibernate was designed to work.