Spring MVC, JPA and Hibernate in Jira plugin

Hello everybody! I’m new to JIRA and Confluence plugin development so I have a few questions.

  1. Is it possible to use pure Spring MVC in my plugin, including RestController, ControllerAdvice annotations, using Spring global exception handling, Spring AOP, etc. For some reason, there are no answers to these questions in Google. I know that atlassian products use the Jersey framework as a standard for developing a REST application, but I would like to use the familiar Spring products. If it is possible to use Spring MBC, can you have a link to an example application configuration? (how to correctly register a servlet dispatcher, etc.)
  2. Atlassian recommends using ActiveObjects as an ORM for plugin development. But I found a meager amount of documentation for this plugin, and in Jira since version 6.0, transactions have been dropped in ActiveObjects, so this is not very suitable for production. Also, BLOBs are not supported in ActiveObjects. Is it possible to set up interaction with the database in the Gira/Confluence plugin using JPA/Hibernate? Google again did not give clear answers to these questions, so if possible, please share how this can be configured.

Thanks for all the answers

1 Like

To use JPA/Hibernate, should be implemented org.hibernate.engine.jdbc.connections.spi.ConnectionProvider:

package com.company.jira.hibernate.impl;

import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.ofbiz.core.entity.ConnectionFactory;
import org.ofbiz.core.entity.GenericEntityException;

import java.sql.Connection;
import java.sql.SQLException;

public class ConnectionProviderImpl implements ConnectionProvider {

    private static final String CONNECTION_NAME = "defaultDS";

    @Override
    public Connection getConnection() throws SQLException {
        try {
            return ConnectionFactory.getConnection(CONNECTION_NAME);
        } catch (GenericEntityException e) {
            throw new SQLException(e);
        }
    }

    @Override
    public void closeConnection(final Connection conn) throws SQLException {
        conn.close();
    }

    @Override
    public boolean supportsAggressiveRelease() {
        return false;
    }

    @Override
    public boolean isUnwrappableAs(final Class<?> unwrapType) {
        return false;
    }

    @Override
    public <T> T unwrap(final Class<T> unwrapType) {
        return null;
    }
}

And then just configure your session factory:

package com.company.jira.hibernate.impl;

import com.atlassian.plugin.spring.scanner.annotation.component.JiraComponent;
import com.company.jira.entity.field.Field;
import com.company.jira.entity.field.FieldOption;
import com.company.jira.entity.layout.Layout;
import com.company.jira.entity.layout.LayoutSection;
import com.company.jira.entity.layout.LayoutSectionField;
import com.company.jira.entity.value.ProjectValue;
import com.company.jira.entity.value.ProjectValueHistory;
import com.company.jira.hibernate.SessionFactoryProvider;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.Properties;

@JiraComponent
public class SessionFactoryProviderImpl implements SessionFactoryProvider {

    private static final String PROPERTIES_FILE = "hibernate.properties";

    private final SessionFactory sessionFactory;

    private SessionFactoryProviderImpl() throws IOException {
        this.sessionFactory = new Configuration()
            .addProperties(getHibernateProperties())
            .addAnnotatedClass(Field.class)
            .addAnnotatedClass(FieldOption.class)
            .addAnnotatedClass(ProjectValue.class)
            .addAnnotatedClass(ProjectValueHistory.class)
            .addAnnotatedClass(Layout.class)
            .addAnnotatedClass(LayoutSection.class)
            .addAnnotatedClass(LayoutSectionField.class)
            .buildSessionFactory();
    }

    @Override
    public SessionFactory getSessionFactory() {
        return sessionFactory;
    }

    @PreDestroy
    private void preDestroy() {
        sessionFactory.close();
    }

    private Properties getHibernateProperties() throws IOException {
        final Properties properties = new Properties();
        properties.load(getClass().getClassLoader().getResourceAsStream(PROPERTIES_FILE));
        return properties;
    }
}

Config file:

hibernate.connection.driver_class=com.mysql.cj.jdbc.Driver
hibernate.connection.provider_class=com.company.jira.hibernate.impl.ConnectionProviderImpl
hibernate.dialect=org.hibernate.dialect.MySQLDialect
hibernate.show_sql=true
hibernate.current_session_context_class=thread
hibernate.hbm2ddl.auto=update

required dependencies:

<dependency>
            <groupId>org.hibernate.orm</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>6.2.3.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate.orm</groupId>
            <artifactId>hibernate-jpamodelgen</artifactId>
            <version>6.2.3.Final</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.woodstox</groupId>
            <artifactId>woodstox-core</artifactId>
            <version>6.4.0</version>
        </dependency>
        <dependency>
            <groupId>jakarta.xml.bind</groupId>
            <artifactId>jakarta.xml.bind-api</artifactId>
            <version>4.0.0</version>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>8.0.32</version>
        </dependency>

enjoy! :slightly_smiling_face: