CLOVER🍀

That was when it all began.

SeBootstrapで起動したRESTEasy上で、Jakarta Data(Hibernate)とJTA(Narayana CDI)を動かしてみる

これは、なにをしたくて書いたもの?

前に、Jakarta Data(Hibernate)をJava SE環境で動かしてみました。

Jakarta DataをJava SE環境で動かしてみる(Hibernate、Weld、Narayana、Agroal) - CLOVER🍀

今回はここにJakarta RESTful Web Services(JAX-RS)とJakarta Transactions(JTA)を組み合わせてみます。

つまりJava SE環境でこのあたりを強引に動かしてみようという試みですが、基本的にネタです。ネタなのですが、
今後やりたいことの足がかりにする予定もあります。

方針

こちらのエントリーで行った方法はほぼそのまま使います。

Jakarta DataをJava SE環境で動かしてみる(Hibernate、Weld、Narayana、Agroal) - CLOVER🍀

またこちらの知識も元になっています。

JAX-RS 3.1.0のSeBootstrapを使って、RESTEasy(+Undertow、CDI)をJava SE環境で動かす - CLOVER🍀

できるようにしたいことはこんな感じです。

  • Java SE環境で、アプリケーションサーバーなしで実行する
  • JAX-RS、CDI、JTA、Jakarta Dataを使う
  • JTAは@Transactionalを使って宣言的トランザクションができるようにする

つまり、アプリケーションコードをパッと見た感じではいかにもJakarta EE環境で動かしているような状態になっている
ことを目指します。

そこに実用性はありません。つまり、あまり説明する気もありません(笑)。ただ、この後ちょっと使いたい予定が
あるのです。

利用するライブラリーなどはこんな感じになります。

  • JAX-RS … RESTEasy
  • Servlet … Undertow
  • CDI … Weld
  • JTA … Narayana
  • Jakarta Data … Hibernate
  • JNDI … Simple-JNDI
  • コネクションプール … Agroal
  • クラスのインデックス … Jandex

完全にネタ枠ですが、書いていきましょう。

環境

今回の環境はこちら。

$ java --version
openjdk 25.0.3 2026-04-21
OpenJDK Runtime Environment (build 25.0.3+9-2-24.04.2-Ubuntu)
OpenJDK 64-Bit Server VM (build 25.0.3+9-2-24.04.2-Ubuntu, mixed mode, sharing)


$ mvn --version
Apache Maven 3.9.16 (2bdd9fddda4b155ebf8000e807eb73fd829a51d5)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 25.0.3, vendor: Ubuntu, runtime: /usr/lib/jvm/java-25-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "6.8.0-136-generic", arch: "amd64", family: "unix"

データベースはMySQLを使います。172.17.0.2で動作しているものとします。

 MySQL  localhost:33060+ ssl  practice  SQL > select version();
+-----------+
| version() |
+-----------+
| 8.4.11    |
+-----------+
1 row in set (0.0004 sec)

MySQLは一般ログを有効にして、発行されたクエリーが確認できるようにしています。

general_log      = ON

準備

Maven依存関係などはこちら。

    <properties>
        <maven.compiler.release>25</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.jboss.resteasy</groupId>
                <artifactId>resteasy-bom</artifactId>
                <version>7.0.2.Final</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.hibernate.orm</groupId>
                <artifactId>hibernate-platform</artifactId>
                <version>7.4.5.Final</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-undertow-cdi</artifactId>
        </dependency>
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-json-binding-provider</artifactId>
        </dependency>
        <dependency>
            <groupId>jakarta.data</groupId>
            <artifactId>jakarta.data-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.jboss.narayana.jta</groupId>
            <artifactId>cdi</artifactId>
            <version>7.3.4.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate.orm</groupId>
            <artifactId>hibernate-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.hibernate.orm</groupId>
            <artifactId>hibernate-scan-jandex</artifactId>
        </dependency>

        <dependency>
              <groupId>io.agroal</groupId>
              <artifactId>agroal-api</artifactId>
              <version>3.2.1</version>
        </dependency>
        <dependency>
              <groupId>io.agroal</groupId>
              <artifactId>agroal-pool</artifactId>
              <version>3.2.1</version>
              <scope>runtime</scope>
        </dependency>
        <dependency>
              <groupId>io.agroal</groupId>
              <artifactId>agroal-narayana</artifactId>
              <version>3.2.1</version>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>9.7.0</version>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.github.h-thurow</groupId>
            <artifactId>simple-jndi</artifactId>
            <version>0.25.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>io.smallrye</groupId>
                <artifactId>jandex-maven-plugin</artifactId>
                <version>3.6.0</version>
                <executions>
                    <execution>
                        <id>make-index</id>
                        <goals>
                            <goal>jandex</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.15.0</version>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.hibernate.orm</groupId>
                            <artifactId>hibernate-processor</artifactId>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>3.11.0</version>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/libs</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>libs/</classpathPrefix>
                            <mainClass>org.littlewings.jakarta.App</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>

Narayanaのcdiというアーティファクトがありますが、これは@Transactionalでトランザクション管理を行うために
必要です。

Maven Dependency PluginとMaven Jar Pluginが含まれているのは、java -jarコマンドで実行できるようにするためですね。

Jandex Maven Pluginは、JAX-RSリソースクラスを検出するために使います。

ソースコードを書く

では、基本的なソースコードを書いていきましょう。そのままだと動かないのですが、Jakarta EE環境であれば動作する
コードや設定ファイルです。

エンティティークラス。

src/main/java/org/littlewings/jakarta/book/Book.java

package org.littlewings.jakarta.book;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "book")
public class Book {
    @Id
    private String isbn;

    private String title;
    private Integer price;

    // getter/setterは省略
}

リポジトリーインターフェース。

src/main/java/org/littlewings/jakarta/book/BookRepository.java

package org.littlewings.jakarta.book;

import jakarta.data.repository.BasicRepository;
import jakarta.data.repository.Repository;

@Repository
public interface BookRepository extends BasicRepository<Book, String> {}

JAX-RSリソースクラス。

src/main/java/org/littlewings/jakarta/book/BookResource.java

package org.littlewings.jakarta.book;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import java.util.List;

@Path("/books")
@ApplicationScoped
@Transactional
public class BookResource {
    @Inject
    private BookRepository bookRepository;

    @GET
    @Path("{isbn}")
    @Produces(MediaType.APPLICATION_JSON)
    public Book findByIsbn(@PathParam("isbn") String isbn) {
        return bookRepository.findById(isbn).orElse(null);
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<Book> findAll() {
        return bookRepository.findAll().toList();
    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public void create(Book book) {
        bookRepository.save(book);
    }

    @POST
    @Path("fail")
    @Consumes(MediaType.APPLICATION_JSON)
    public void fail(Book book) {
        bookRepository.save(book);

        throw new RuntimeException("Oops!!");
    }
}

@Transactionalをつけていますが、ひとつロールバックさせるためのメソッドが混じっています。

JAX-RSの有効化。

src/main/java/org/littlewings/jakarta/RestApplication.java

package org.littlewings.jakarta;

import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;

@ApplicationPath("/")
public class RestApplication extends Application {}

persistence.xml。

src/main/resources/META-INF/persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_2.xsd"
             version="3.2">
    <persistence-unit name="MyPersistenceUnit" transaction-type="JTA">
        <jta-data-source>java:comp/env/datasources/MySqlDs</jta-data-source>
        <properties>
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="jakarta.persistence.schema-generation.database.action" value="drop-and-create"/>
        </properties>
    </persistence-unit>
</persistence>

いろいろ細工

ここまでに用意したコードをJava SE環境で動かすためにいろいろと細工していきます。

CDIを有効にするために、beans.xmlを空で作成します。

src/main/resources/META-INF/beans.xml




リポジトリーの実装クラスが必要とするEntityManagerFactoryをCDI管理Beanとして登録します。

src/main/java/org/littlewings/jakarta/environment/EntityManagerFactoryProducer.java

package org.littlewings.jakarta.environment;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.inject.Produces;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;

@ApplicationScoped
public class EntityManagerFactoryProducer {
    private EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("MyPersistenceUnit");

    @Dependent
    @Produces
    public EntityManagerFactory createEntityManagerFactory() {
        return entityManagerFactory;
    }
}

リポジトリーの実装クラスが期待するのは@PersistenceContextでのインジェクションなので、これをPortable Extensionsを
使って@Injectionでもできるようにします。

src/main/java/org/littlewings/jakarta/environment/PersistenceExtension.java

package org.littlewings.jakarta.environment;

import jakarta.data.repository.Repository;
import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.event.Observes;
import jakarta.enterprise.inject.literal.InjectLiteral;
import jakarta.enterprise.inject.spi.Extension;
import jakarta.enterprise.inject.spi.ProcessAnnotatedType;
import jakarta.enterprise.inject.spi.WithAnnotations;
import jakarta.persistence.PersistenceUnit;
import java.util.Arrays;

public class PersistenceExtension implements Extension {
    public <T> void processRepositories(@Observes @WithAnnotations(Dependent.class) ProcessAnnotatedType<T> pat) {
        var annotatedType = pat.getAnnotatedType();
        var clazz = annotatedType.getJavaClass();

        if (Arrays.stream(clazz.getInterfaces()).anyMatch(c -> c.isAnnotationPresent(Repository.class))) {
            pat.configureAnnotatedType().fields().forEach(fieldConfigurator -> {
                if (fieldConfigurator.getAnnotated().isAnnotationPresent(PersistenceUnit.class)) {
                    fieldConfigurator.add(InjectLiteral.INSTANCE);
                }
            });
        }
    }
}

src/main/resources/META-INF/services/jakarta.enterprise.inject.spi.Extension

org.littlewings.jakarta.environment.PersistenceExtension

このあたりは前のエントリーそのままですね。

Jakarta DataをJava SE環境で動かしてみる(Hibernate、Weld、Narayana、Agroal) - CLOVER🍀

データソースやJTAに関するリソースをJNDI登録します。

src/main/java/org/littlewings/jakarta/environment/ResourceInitializer.java

package org.littlewings.jakarta.environment;

import com.arjuna.ats.arjuna.common.CoreEnvironmentBeanException;
import com.arjuna.ats.arjuna.common.arjPropertyManager;
import com.arjuna.ats.jta.TransactionManager;
import com.arjuna.ats.jta.common.jtaPropertyManager;
import com.arjuna.ats.jta.utils.JNDIManager;
import io.agroal.api.AgroalDataSource;
import io.agroal.api.configuration.supplier.AgroalConnectionFactoryConfigurationSupplier;
import io.agroal.api.configuration.supplier.AgroalConnectionPoolConfigurationSupplier;
import io.agroal.api.configuration.supplier.AgroalDataSourceConfigurationSupplier;
import io.agroal.api.security.NamePrincipal;
import io.agroal.api.security.SimplePassword;
import io.agroal.narayana.NarayanaTransactionIntegration;
import io.undertow.servlet.ServletExtension;
import io.undertow.servlet.api.DeploymentInfo;
import jakarta.servlet.ServletContext;
import java.sql.SQLException;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class ResourceInitializer implements ServletExtension {
    @Override
    public void handleDeployment(DeploymentInfo deploymentInfo, ServletContext servletContext) {
        installDataSource();
        installJta();
    }

    void installDataSource() {
        try {
            var initialContext = new InitialContext();

            var agroalConnectionFactoryConfiguration = new AgroalConnectionFactoryConfigurationSupplier()
                    .jdbcUrl(
                            "jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&connectionCollation=utf8mb4_0900_bin")
                    .credential(new NamePrincipal("kazuhira"))
                    .credential(new SimplePassword("password"));
            var agroalConnectionPoolConfiguration = new AgroalConnectionPoolConfigurationSupplier()
                    .connectionFactoryConfiguration(agroalConnectionFactoryConfiguration)
                    .minSize(5)
                    .maxSize(10)
                    .transactionIntegration(new NarayanaTransactionIntegration(
                            TransactionManager.transactionManager(),
                            jtaPropertyManager.getJTAEnvironmentBean().getTransactionSynchronizationRegistry()));
            var agroalDataSourceConfiguration = new AgroalDataSourceConfigurationSupplier()
                    .connectionPoolConfiguration(agroalConnectionPoolConfiguration);
            var agroalDataSource = AgroalDataSource.from(agroalDataSourceConfiguration);

            initialContext.bind("java:comp/env/datasources/MySqlDs", agroalDataSource);
        } catch (SQLException | NamingException e) {
            throw new RuntimeException(e);
        }
    }

    void installJta() {
        try {
            arjPropertyManager.getCoreEnvironmentBean().setNodeIdentifier("1");

            jtaPropertyManager
                    .getJTAEnvironmentBean()
                    .setTransactionManagerJNDIContext("java:/jboss/TransactionManager");
            jtaPropertyManager
                    .getJTAEnvironmentBean()
                    .setTransactionSynchronizationRegistryJNDIContext("java:/jboss/TransactionSynchronizationRegistry");
            jtaPropertyManager.getJTAEnvironmentBean().setUserTransactionJNDIContext("java:comp/UserTransaction");

            JNDIManager.bindJTAImplementation();
        } catch (CoreEnvironmentBeanException | NamingException e) {
            throw new RuntimeException(e);
        }
    }
}

登録先はSimple-JNDIです。

src/main/resources/jndi.properties

java.naming.factory.initial=org.osjava.sj.SimpleContextFactory
org.osjava.sj.jndi.shared=true
org.osjava.sj.jndi.ignoreClose=true

このあたりも前のエントリーからほとんど変わっていないのですが、どうやって初期化処理に組み込むかはちょっと困りました。

Jakarta DataをJava SE環境で動かしてみる(Hibernate、Weld、Narayana、Agroal) - CLOVER🍀

UndertowはDeploymentInfoを使わないとServletContainerInitializerやListenerを登録できないので、今回はUndertowの
ServletExtensionを使うことにしました。

Undertow / Undertow Servlet / Creating a Servlet Deployment

src/main/resources/META-INF/services/io.undertow.servlet.ServletExtension

org.littlewings.jakarta.environment.ResourceInitializer

NarayanaのObjectStoreの設定。完全にテスト用です。

src/main/resources/jbossts-properties.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
    <entry key="ObjectStoreEnvironmentBean.communicationStore.objectStoreType">com.arjuna.ats.internal.arjuna.objectstore.VolatileStore</entry>
    <entry key="ObjectStoreEnvironmentBean.objectStoreDir">${java.io.tmpdir}/ObjectStore</entry>
</properties>

最後にmainクラスです。

src/main/java/org/littlewings/jakarta/App.java

package org.littlewings.jakarta;

import jakarta.ws.rs.SeBootstrap;
import java.util.concurrent.ExecutionException;
import org.jboss.logging.Logger;

public class App {
    static void main(String... args) throws ExecutionException, InterruptedException {
        var logger = Logger.getLogger(App.class);

        var configuration =
                SeBootstrap.Configuration.builder().host("0.0.0.0").port(8080).build();

        var instance = SeBootstrap.start(new RestApplication(), configuration)
                .toCompletableFuture()
                .get();

        logger.info("server startup.");
        System.console().readLine("> Enter stop.");

        instance.stop().toCompletableFuture().get();
    }
}

これで、先ほどのコードを動かせる準備ができました。

動かしてみる

では、動作確認してみましょう。

ビルドして実行。

$ mvn package
$ java -jar target/app-0.0.1-SNAPSHOT.jar

ターミナルはEnterの入力待ちで停止します。

INFO: server startup.
> Enter stop.

データを登録します。

$ curl localhost:8080/books --json '{"isbn": "978-4774183169", "title": "パーフェクト Java EE", "price": 3520}'

MySQLのログを確認すると、ちゃんとコミットしています。

2026-08-01T15:46:31.136781Z        11 Query     SET autocommit=0
2026-08-01T15:46:31.137807Z        11 Query     insert into book (isbn,price,title) values ('978-4774183169',3520,'パーフェクト Java EE') as tr  on duplicate key update price = tr.price,title = tr.title
2026-08-01T15:46:31.138953Z        11 Query     COMMIT
2026-08-01T15:46:31.156265Z        11 Query     SET autocommit=1

データの確認。

$ curl -s localhost:8080/books/978-4774183169 | jq
{
  "isbn": "978-4774183169",
  "price": 3520,
  "title": "パーフェクト Java EE"
}


$ curl -s localhost:8080/books | jq
[
  {
    "isbn": "978-4774183169",
    "price": 3520,
    "title": "パーフェクト Java EE"
  }
]

OKですね。

ロールバックするパターン。

$ curl localhost:8080/books/fail --json '{"isbn": "978-4297156589", "title": "作って学ぶ Spring Boot入門", "price": 2970}'
Oops!!

MySQL側ではロールバックしていることが確認できます。

2026-08-01T15:48:26.289316Z        12 Query     SET autocommit=0
2026-08-01T15:48:26.290223Z        12 Query     insert into book (isbn,price,title) values ('978-4297156589',2970,'作って学ぶ Spring Boot入門') as tr  on duplicate key update price = tr.price,title = tr.title
2026-08-01T15:48:26.291606Z        12 Query     ROLLBACK
2026-08-01T15:48:26.303047Z        12 Query     SET autocommit=1

アプリケーション側のスタックトレースも見てみます。

8月 02, 2026 12:48:26 午前 org.jboss.resteasy.core.providerfactory.DefaultExceptionMapper process
ERROR: RESTEASY002375: Error processing request POST /books/fail - org.littlewings.jakarta.book.BookResource.fail
java.lang.RuntimeException: Oops!!
        at org.littlewings.jakarta.book.BookResource.fail(BookResource.java:47)
        at org.littlewings.jakarta.book.BookResource$Proxy$_$$_WeldSubclass.fail$$super(Unknown Source)
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
        at java.base/java.lang.reflect.Method.invoke(Method.java:565)
        at org.jboss.weld.interceptor.proxy.TerminalAroundInvokeInvocationContext.proceedInternal(TerminalAroundInvokeInvocationContext.java:55)
        at org.jboss.weld.interceptor.proxy.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:86)
        at com.arjuna.ats.jta.cdi.transactional.TransactionalInterceptorBase.invokeInOurTx(TransactionalInterceptorBase.java:181)
        at com.arjuna.ats.jta.cdi.transactional.TransactionalInterceptorBase.invokeInOurTx(TransactionalInterceptorBase.java:169)
        at com.arjuna.ats.jta.cdi.transactional.TransactionalInterceptorRequired.doIntercept(TransactionalInterceptorRequired.java:37)
        at com.arjuna.ats.jta.cdi.transactional.TransactionalInterceptorBase.intercept(TransactionalInterceptorBase.java:74)
        at com.arjuna.ats.jta.cdi.transactional.TransactionalInterceptorRequired.intercept(TransactionalInterceptorRequired.java:31)
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
        at java.base/java.lang.reflect.Method.invoke(Method.java:565)
        at org.jboss.weld.interceptor.reader.SimpleInterceptorInvocation$SimpleMethodInvocation.invoke(SimpleInterceptorInvocation.java:75)
        at org.jboss.weld.interceptor.proxy.InterceptorMethodHandler.executeAroundInvoke(InterceptorMethodHandler.java:90)
        at org.jboss.weld.interceptor.proxy.InterceptorMethodHandler.executeInterception(InterceptorMethodHandler.java:73)
        at org.jboss.weld.interceptor.proxy.InterceptorMethodHandler.invoke(InterceptorMethodHandler.java:56)
        at org.jboss.weld.bean.proxy.CombinedInterceptorAndDecoratorStackMethodHandler.invoke(CombinedInterceptorAndDecoratorStackMethodHandler.java:80)
        at org.jboss.weld.bean.proxy.CombinedInterceptorAndDecoratorStackMethodHandler.invoke(CombinedInterceptorAndDecoratorStackMethodHandler.java:68)
        at org.littlewings.jakarta.book.BookResource$Proxy$_$$_WeldSubclass.fail(Unknown Source)
        at org.littlewings.jakarta.book.BookResource$Proxy$_$$_WeldClientProxy.fail(Unknown Source)
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
        at java.base/java.lang.reflect.Method.invoke(Method.java:565)
        at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:154)
        at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:118)
        at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:560)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:452)
        at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$0(ResourceMethodInvoker.java:413)
        at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:333)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:415)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:378)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:356)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:70)
        at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:434)
        at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$0(SynchronousDispatcher.java:245)
        at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:159)
        at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:333)
        at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:162)
        at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:234)
        at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:222)
        at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:55)
        at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:51)
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:614)
        at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
        at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
        at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
        at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
        at io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68)
        at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:117)
        at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
        at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
        at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
        at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
        at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
        at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:75)
        at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
        at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
        at io.undertow.servlet.handlers.SendErrorPageHandler.handleRequest(SendErrorPageHandler.java:52)
        at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
        at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:278)
        at io.undertow.servlet.handlers.ServletInitialHandler$1.call(ServletInitialHandler.java:131)
        at io.undertow.servlet.handlers.ServletInitialHandler$1.call(ServletInitialHandler.java:128)
        at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
        at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
        at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:258)
        at io.undertow.servlet.handlers.ServletInitialHandler.lambda$new$1(ServletInitialHandler.java:100)
        at io.undertow.server.Connectors.executeRootHandler(Connectors.java:418)
        at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:938)
        at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
        at org.jboss.threads.EnhancedQueueExecutor$Task.doRunWith(EnhancedQueueExecutor.java:2691)
        at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2670)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1615)
        at org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1282)
        at java.base/java.lang.Thread.run(Thread.java:1474)

ちゃんとNarayanaのインターセプターが入っていることが確認できます。

        at org.jboss.weld.interceptor.proxy.TerminalAroundInvokeInvocationContext.proceedInternal(TerminalAroundInvokeInvocationContext.java:55)
        at org.jboss.weld.interceptor.proxy.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:86)
        at com.arjuna.ats.jta.cdi.transactional.TransactionalInterceptorBase.invokeInOurTx(TransactionalInterceptorBase.java:181)
        at com.arjuna.ats.jta.cdi.transactional.TransactionalInterceptorBase.invokeInOurTx(TransactionalInterceptorBase.java:169)
        at com.arjuna.ats.jta.cdi.transactional.TransactionalInterceptorRequired.doIntercept(TransactionalInterceptorRequired.java:37)
        at com.arjuna.ats.jta.cdi.transactional.TransactionalInterceptorBase.intercept(TransactionalInterceptorBase.java:74)
        at com.arjuna.ats.jta.cdi.transactional.TransactionalInterceptorRequired.intercept(TransactionalInterceptorRequired.java:31)

データも増えていませんね。

$ curl -s localhost:8080/books | jq
[
  {
    "isbn": "978-4774183169",
    "price": 3520,
    "title": "パーフェクト Java EE"
  }
]

OKです。

おわりに

SeBootstrapで起動したRESTEasy上で、Jakarta Data(Hibernate)とJTA(Narayana CDI)を動かしてみました。

前のエントリーでJava SE環境でJakarta Dataを動かせていたので、あまり苦労はしないだろうと思っていたのですが、
ほぼ予想どおりでした。

唯一予定外だったのは、JNDIリソースの登録をServletContainerInitializerでやろうと思っていたのに、Undertowでは
動かせなかったことですね。

CDIのPortable Extensionを使うという案もあったのですが、ちょっと違うかなと…。

ひとまず、やりたいことはできたので良しとしましょう。ふつうは使うことはないと思いますけど(笑)。