OpenJPA is a well known JPA implementation. Did you know it supports Jakarta namespace? Let see how to use it.

JPA is a JavaEE/JakartaEE specification but it is likely the least portable one so changing of provider is rarely an option + it is often part of the EE stack you use so it is always saner to use the one you have than customizing an EE server. If your provider is OpenJPA, let see how Jakarta namespace can be supported.

Dependencies

OpenJPA provides dependencies with jakarta classifier to support Jakarta namespace. To import them you need to import OpenJPA but also the JPA 3 dependency (specification API), SERP (transitive dependency for OpenJPA) and JTA API:

<!-- specs -->
<dependency>
  <groupId>jakarta.persistence</groupId>
  <artifactId>jakarta.persistence-api</artifactId>
  <version>3.0.0</version>
</dependency>
<dependency>
  <groupId>jakarta.transaction</groupId>
  <artifactId>jakarta.transaction-api</artifactId>
  <version>2.0.0</version>
</dependency>

<!-- openjpa -->
<dependency>
  <groupId>net.sourceforge.serp</groupId>
  <artifactId>serp</artifactId>
  <version>1.15.1</version>
  <exclusions>
    <exclusion>
      <groupId>*</groupId>
      <artifactId>*</artifactId>
    </exclusion>
  </exclusions>
</dependency>
<dependency>
  <groupId>org.apache.openjpa</groupId>
  <artifactId>openjpa</artifactId>
  <version>${openjpa.version}</version>
  <classifier>jakarta</classifier>
  <exclusions>
    <exclusion>
      <groupId>*</groupId>
      <artifactId>*</artifactId>
    </exclusion>
  </exclusions>
</dependency>
if you don’t use build time enhancement you must also import ASM (XBean ones) dependencies.

Define your persistence unit without XML

Indeed you can use a persistence.xml but OpenJPA is one of the rare provider enabling you to not have XML files. If you are interested by this option, a common way to do it is to follow these steps:

  1. Define a PersistenceUnitInfo implementation

    // mainly just a DTO
    public class PersistenceUnitInfoImpl implements PersistenceUnitInfo {
        private String persistenceXMLSchemaVersion = "2.0";
        private String persistenceUnitName;
        private String persistenceProviderClassName;
        private DataSource jtaDataSource;
        private DataSource nonJtaDataSource;
        private List<String> managedClassNames;
        private URL persistenceUnitRootUrl;
        private PersistenceUnitTransactionType transactionType = PersistenceUnitTransactionType.RESOURCE_LOCAL;
        private List<String> mappingFileNames = List.of();
        private List<URL> jarFileUrls = List.of();
        private boolean excludeUnlistedClasses = true;
        private SharedCacheMode sharedCacheMode = SharedCacheMode.NONE;
        private ValidationMode validationMode = ValidationMode.NONE;
        private Properties properties = new Properties();
        private ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        private ClassLoader newTempClassLoader = classLoader;
        private final Collection<ClassTransformer> transformers = new ArrayList<>();
    
        public PersistenceUnitInfoImpl persistenceXMLSchemaVersion(final String persistenceXMLSchemaVersion) {
            this.persistenceXMLSchemaVersion = persistenceXMLSchemaVersion;
            return this;
        }
    
        public PersistenceUnitInfoImpl persistenceUnitName(final String persistenceUnitName) {
            this.persistenceUnitName = persistenceUnitName;
            return this;
        }
    
        public PersistenceUnitInfoImpl persistenceProviderClassName(final String persistenceProviderClassName) {
            this.persistenceProviderClassName = persistenceProviderClassName;
            return this;
        }
    
        public PersistenceUnitInfoImpl jtaDataSource(final DataSource jtaDataSource) {
            this.jtaDataSource = jtaDataSource;
            return this;
        }
    
        public PersistenceUnitInfoImpl nonJtaDataSource(final DataSource nonJtaDataSource) {
            this.nonJtaDataSource = nonJtaDataSource;
            return this;
        }
    
        public PersistenceUnitInfoImpl managedClassNames(final List<String> managedClassNames) {
            this.managedClassNames = managedClassNames;
            return this;
        }
    
        public PersistenceUnitInfoImpl managedClasses(final List<Class<?>> managedClasses) {
            return managedClassNames(managedClasses.stream()
                    .flatMap(this::hierarchy)
                    .distinct()
                    .map(Class::getName)
                    .collect(toList()));
        }
    
        private Stream<Class<?>> hierarchy(final Class<?> it) {
            return it == null || it == Object.class ?
                    Stream.empty() :
                    Stream.concat(Stream.of(it), hierarchy(it.getSuperclass()));
        }
    
        public PersistenceUnitInfoImpl persistenceUnitRootUrl(final URL persistenceUnitRootUrl) {
            this.persistenceUnitRootUrl = persistenceUnitRootUrl;
            return this;
        }
    
        public PersistenceUnitInfoImpl transactionType(final PersistenceUnitTransactionType transactionType) {
            this.transactionType = transactionType;
            return this;
        }
    
        public PersistenceUnitInfoImpl mappingFileNames(final List<String> mappingFileNames) {
            this.mappingFileNames = mappingFileNames;
            return this;
        }
    
        public PersistenceUnitInfoImpl jarFileUrls(final List<URL> jarFileUrls) {
            this.jarFileUrls = jarFileUrls;
            return this;
        }
    
        public PersistenceUnitInfoImpl excludeUnlistedClasses(final boolean excludeUnlistedClasses) {
            this.excludeUnlistedClasses = excludeUnlistedClasses;
            return this;
        }
    
        public PersistenceUnitInfoImpl sharedCacheMode(final SharedCacheMode sharedCacheMode) {
            this.sharedCacheMode = sharedCacheMode;
            return this;
        }
    
        public PersistenceUnitInfoImpl validationMode(final ValidationMode validationMode) {
            this.validationMode = validationMode;
            return this;
        }
    
        public PersistenceUnitInfoImpl properties(final Properties properties) {
            this.properties = properties;
            return this;
        }
    
        public PersistenceUnitInfoImpl classLoader(final ClassLoader classLoader) {
            this.classLoader = classLoader;
            return this;
        }
    
        public PersistenceUnitInfoImpl newTempClassLoader(final ClassLoader newTempClassLoader) {
            this.newTempClassLoader = newTempClassLoader;
            return this;
        }
    
        @Override
        public String getPersistenceXMLSchemaVersion() {
            return persistenceXMLSchemaVersion;
        }
    
        @Override
        public String getPersistenceUnitName() {
            return persistenceUnitName;
        }
    
        @Override
        public String getPersistenceProviderClassName() {
            return persistenceProviderClassName;
        }
    
        @Override
        public PersistenceUnitTransactionType getTransactionType() {
            return transactionType;
        }
    
        @Override
        public DataSource getJtaDataSource() {
            return jtaDataSource;
        }
    
        @Override
        public DataSource getNonJtaDataSource() {
            return nonJtaDataSource;
        }
    
        @Override
        public List<String> getManagedClassNames() {
            return managedClassNames;
        }
    
        @Override
        public List<String> getMappingFileNames() {
            return mappingFileNames;
        }
    
        @Override
        public List<URL> getJarFileUrls() {
            return jarFileUrls;
        }
    
        @Override
        public URL getPersistenceUnitRootUrl() {
            return persistenceUnitRootUrl;
        }
    
        @Override
        public SharedCacheMode getSharedCacheMode() {
            return sharedCacheMode;
        }
    
        @Override
        public ValidationMode getValidationMode() {
            return validationMode;
        }
    
        @Override
        public Properties getProperties() {
            return properties;
        }
    
        @Override
        public ClassLoader getClassLoader() {
            return classLoader;
        }
    
        @Override
        public ClassLoader getNewTempClassLoader() {
            return newTempClassLoader;
        }
    
        @Override
        public boolean excludeUnlistedClasses() {
            return excludeUnlistedClasses;
        }
    
        @Override
        public void addTransformer(final ClassTransformer transformer) {
            transformers.add(transformer);
        }
    }
  2. Load the PersistenceProvider:

    // if you can get multiple impl handle conflicts
    final var provider = ServiceLoader.load(PersistenceProvider.class).iterator().next();
  3. Create the EntityManagerFactory:

    // config/depends your app
    final var ds = getDataSource();
    final var props = getUnitProperties();
    final var classes = getEntityNames();
    final var name = getUnitName();
    
    // create the emf
    final var emf = provider.createContainerEntityManagerFactory(
        new PersistenceUnitInfoImpl()
                .persistenceUnitName(name)
                .properties(props)
                .jtaDataSource(ds)
                .managedClasses(classes),
        Map.of())
  4. Integrate the EntityManagerFactory in your application (spring-boot, Apache DeltaSpike, CDI, …​).

Indeed, this option is mainly for standalone applications since EE containers will have it integrated out of the box but it enables to simplify the JPA setup a bit and unifying its configuration with your own application configuration which is always a plus when it enables to reduce the needed properties/entries.

Enhancement

JPA requires enhancement to be fully functional - even with Hibernates ;).

The most common way to do it for OpenJPA is to use its Maven Plugin. It is the standard plugin, no jakarta flavor needed there. The only trick is to add to the plugin dependencies, the jakarta openjpa jar.

<plugin>
  <groupId>org.apache.openjpa</groupId>
  <artifactId>openjpa-maven-plugin</artifactId>
  <version>${openjpa.version}</version>
  <executions>
    <execution>
      <id>enhancer</id>
      <phase>compile</phase>
      <goals>
        <goal>enhance</goal>
      </goals>
      <configuration>
        <toolProperties>
          <MetaDataFactory>jpa</MetaDataFactory>
        </toolProperties>
      </configuration>
    </execution>
  </executions>
  <configuration>
    <includes>com/superbiz/jpa/**/*.class</includes>
    <sqlAction>build</sqlAction>
    <toolProperties>
      <MappingDefaults>jpa(ForeignKeyDeleteAction=restrict,JoinForeignKeyDeleteAction=restrict)</MappingDefaults>
      <MetaDataFactory>jpa</MetaDataFactory>
      <Log>slf4j</Log>
    </toolProperties>
  </configuration>
  <dependencies>
    <!-- jakarta openjpa jar as in first section -->
  </dependencies>
</plugin>
as you can see in this snippet, you can customize the logging of the plugin. Using <Log>slf4j</Log> enables to use Maven logging automatically which makes the output nicer.

Conclusion

Using jakarta OpenJPA dependency and wiring it in its Maven plugin enables to develop and run OpenJPA entities under the jakarta namespace. Using a persistence.xml or not is up to you but also enables some more advanced features which can open more doors to your configuration (exposed to end users).

From the same author:

In the same category: