Last updated Apr 2, 2024

How to update your app for Hibernate2 removal

AvailabilityConfluence 7.17 or later

In Confluence 8.0, we will remove support for Hibernate2 across Confluence. This will allow us to upgrade Hibernate more frequently without breaking your app, or requiring you to do significant testing and rework when things change.

For apps this means:

  1. No direct usage of Hibernate in plugins.
  2. No use of deprecated code from Confluence plugin API.

How to check if your app uses Hibernate2?

Your app uses Hibernate2 if:

  • The Java source code of your app has any references to classes from net.sf.hibernate.* packages.
  • The app code uses any Hibernate APIs directly.
  • The app code uses any deprecated classes or methods from Confluence API.

When testing your app in Confluence, you can also set the following system property. It will log warnings if your app makes use of PluginHibernateSessionFactory to use Hibernate sessions directly. This property is available from Confluence 7.18.

1
2
-Dcheck-confluence8-compatibility=true

Changes required to pom.xml

Check your app's pom.xml, look for any dependencies on Hibernate 2 packages, and remove them.

You should also remove any occurrences of net.sf.hibernate.*.

1
2
<build>
  <plugins>
      <plugin>
        ...
          <configuration>
            <instructions>
              <Import-Package>
              ...
                net.sf.hibernate,
                net.sf.hibernate,
                net.sf.hibernate.cache,
                net.sf.hibernate.collection,
                net.sf.hibernate.connection,
                net.sf.hibernate.dialect,
                net.sf.hibernate.engine,
                net.sf.hibernate.exception,
                net.sf.hibernate.metadata,
                net.sf.hibernate.persister,
                net.sf.hibernate.type,        
             ...
             </Import-Package>
        ...
  </build>

Changes required to Java code

Custom Hibernate queries

If your app uses any Custom Hibernate queries, there is a new way of using custom queries. You should use content-custom-query over content-hibernate-2-query.

If your code uses any implementations of com.atlassian.confluence.content.persistence.hibernate.HibernateContentQueryFactory make sure you remove the override of the deprecated method public Query getQuery(Session session, Object... parameters) , as it will be deleted in Confluence 8.0. Use the non-deprecated method public Query getQuery(EntityManager entityManager, Object... parameters) and override it instead. For example:

Existing code
1
2
public class FindInSpaceByMessageIdQueryFactory implements HibernateContentQueryFactory {
    @Override
    public Query getQuery(Session session, Object... parameters) throws HibernateException {
        long spaceId = (Long) parameters[0];
        String messageId = (String) parameters[1];

        Query query = session.createQuery("from CustomContentEntityObject content " +
                "left join content.contentProperties as props " +
                "where content.originalVersion is null and " +
                "content.space.id = :spaceId and " +
                "props.name = 'messageId' and " +
                "props.stringValue = :messageId ");
        query.setParameter("spaceId", spaceId, Hibernate.LONG);
        query.setParameter("messageId", messageId, Hibernate.STRING);
        return query;
    }
}
New code
1
2
public class FindInSpaceByMessageIdQueryFactory implements HibernateContentQueryFactory {

    @Override
    public Query getQuery(EntityManager entityManager, Object... parameters) throws PersistenceException {
        long spaceId = (Long) parameters[0];
        String messageId = (String) parameters[1];

        Query query = entityManager.createQuery("from CustomContentEntityObject content " +
                "left join content.contentProperties as props " +
                "where content.originalVersion is null and " +
                "content.space.id = :spaceId and " +
                "props.name = 'messageId' and " +
                "props.stringValue = :messageId ");
        query.setParameter("spaceId", spaceId);
        query.setParameter("messageId", messageId);
        return query;
    }
}

The tag content-custom-query was introduced in Confluence 7.17.  We recommended you use content-custom-query over content-hibernate-2-query as content-hibernate-2-query may be removed in future. 

In the meantime, you can use both to define your custom queries with two modules having version restrictions as in the example below.

1
2
<content-hibernate-2-query query-name="query-name" key="legacy-query-name" class="com.test.MyQueryFactory">
   <restrict application="confluence" version="(,7.17.0)" />
</content-hibernate-2-query>

<content-custom-query query-name="query-name" key="my-query-name" class="com.test.MyQueryFactory">
   <restrict application="confluence" version="[7.17.0,)" />
</content-custom-query>

PluginHibernateSessionFactory usages

PluginHibernateSessionFactory usage is no longer supported. If your code uses the com.atlassian.hibernate.PluginHibernateSessionFactory class to get sessions you should remove the component-import for this class from atlassian-plugin.xml (if present) and use the EntityManagerProvider instead.

Existing code
1
2
<component-import key="pluginHibernateSessionFactory"
interface="com.atlassian.hibernate.PluginHibernateSessionFactory"/>
New code
1
2
<component-import key="entityManagerProvider"
interface="com.atlassian.confluence.persistence.EntityManagerProvider"/>

You should also remove the component-import annotations for this class from your java code (if present) and use the EntityManagerProvider instead.

Existing code
1
2
@ComponentImport PluginHibernateSessionFactory pluginHibernateSessionFactory
New code
1
2
@ComponentImport EntityManagerProvider entityManagerProvider,

The EntityManagerProvider can be used to replace the corresponding methods from Session.

Existing code
1
2
Session session = pluginHibernateSessionFactory.getSession();
session.save(someObject);
session.delete(someObject);
Query titleQuery = hibernateSession.createQuery(queryString);
List<String> titles = (List<String>) titleQuery.list();
New code
1
2
EntityManager entityManager = entityManagerProvider.getEntityManager();
entityManager.persist(propertySetItem);
entityManager.remove(propertySetItem);
Query titleQuery = entityManager.createQuery(queryString);
List<String> titles = (List<String>) titleQuery.getResultList();

Getting a JDBC connection

We strongly recommend you use EntityManagerProvider to execute the database queries. In some cases getting a JDBC connection may be required. 

TransactionalExecutorFactory  provides the ability to execute a block of code/lambda with a JDBC connection.

Existing code
1
2
PluginHibernateSessionFactory pluginHibernateSessionFactory;
Connection connection = pluginHibernateSessionFactory.getSession().getConnection();
doSomethingUsingConnection(connection);
New code
1
2
TransactionalExecutorFactory transactionalExecutorFactory;
transactionalExecutorFactory.create().execute(connection -> {
  doSomethingUsingConnection(connection);
});

How to get a concrete instance of EntityManager during an integration test when EntityMangerProvider is not injected by the framework

In this example, your class has a constructor parameter of type EntityMangerProvider which needs to be injected by the framework as below:

1
2
public Example(final @ComponentImport EntityManagerProvider entityManagerProvider) {
    this.entityManagerProvider = entityManagerProvider;
}

You want to write integration tests for your above class Example, but you realise that in your test environment the EntityManagerProvider bean is not available and can't be injected.

Our suggestion in this situation is to mock EntityManagerProvider and create a concrete EntityManager instance to carry on your integration test because EntityManager instance is the main one that does actual job. Here's an example using a MySQL database:

1
2
@Mock
protected EntityManagerProvider entityManagerProvider;
@Mock
protected EntityManager entityManager;

public void setUp() throws Exception {
	final EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("testconfig", getTestConnectionProperty());
	entityManager = entityManagerFactory.createEntityManager();
	when(entityManagerProvider.getEntityManager()).thenReturn(entityManager);        
}

private Map<String, Object> getTestConnectionProperty() {
        final Map<String, Object> connectionProperties = new HashMap<>();
        connectionProperties.put("provider", org.hibernate.jpa.HibernatePersistenceProvider.class);
        connectionProperties.put("javax.persistence.jdbc.url", "url");
        connectionProperties.put("javax.persistence.jdbc.user", "username");
        connectionProperties.put("javax.persistence.jdbc.password", "password");
        connectionProperties.put("javax.persistence.jdbc.driver", mysql.getJdbcDriverInstance().getClass().getName());
        return connectionProperties;
}

From the above code you can see we have passed testconfig as persistanceUnitName parameter in the createEntityManagerFactory method. To do this you need to create a persistence.xml file under your test/resources/META-INF folder, as shown below:

Screenshot showing IDE with persistence.xml selected

In the image above you can see we've used persistence header version 2.0. You may need to change it depending on your environment otherwise you may get a parser error.

Rate this page: