CLOVER🍀

That was when it all began.

コンテナ管理トランザクションずEJB Lite

前にJava EE 6のずっかかりずいうこずで、JAX-RSずEJBずJPAを぀なげお遊びたしたが、この時はReadが粟䞀杯でした。

で、今回は少し芖点を倉えおコンテナ管理トランザクションを芋おいこうかなず思いたす。

ちょうどこちらに、ピタリなテヌマが。

EJBで複数テーブル保存時の自動ロールバック挙動を確認してみました - Challenge Java EE !

すいたせん、お借りしたす。

たあ、そのたたではないですが、蚀語をScalaに、JSFをJAX-RSに眮き換えお、自分なりに気になるずころを確認しようかなず。

テヌブル定矩は、こんな感じで。

CREATE TABLE opportunity (
  opportunity_id VARCHAR(3),
  opportunity_name VARCHAR(10),
  PRIMARY KEY(opportunity_id)
);

CREATE TABLE quote (
  quote_id VARCHAR(3),
  opportunity_id VARCHAR(3),
  quote_name VARCHAR(10),
  PRIMARY KEY(quote_id)
);

元のブログに盎接定矩があるわけではないですが、ずりあえずこんなずころかなず。

氞続性ナニットの定矩。
src/main/resources/META-INF/persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
             version="2.0">
  <persistence-unit name="javaee6.web.pu" transaction-type="JTA">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jta-data-source>java:jboss/datasources/mysqlXaDs</jta-data-source>
    <properties>
      <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
      <property name="hibernate.show_sql" value="true" />
      <property name="hibernate.format_sql" value="true" />
    </properties>
  </persistence-unit>
</persistence>

CDIも䜿うので、

src/main/webapp/WEB-INF/beans.xml

も空ファむルで䜜成したす。これ、Java EE 7だず、特に曞くこずがなければいらなくなるんですか 。たあ、今はEE 6だからいいんですけど、忘れないようにしおおきたしょう。

゚ンティティの定矩。
src/main/scala/javaee6/web/entity/Entities.scala

package javaee6.web.entity

import scala.beans.BeanProperty

import javax.persistence.{Column, Entity, Id, JoinColumn, OneToOne, Table}

object Opportunity {
  def apply(opportunityId: String, opportunityName: String): Opportunity = {
    val opp = new Opportunity
    opp.opportunityId = opportunityId
    opp.opportunityName = opportunityName
    opp
  }
}

@SerialVersionUID(1L)
@Entity
@Table(name = "opportunity")
class Opportunity extends Serializable {
  @Id
  @Column(name = "opportunity_id")
  @BeanProperty
  var opportunityId: String = _

  @Column(name = "opportunity_name")
  @BeanProperty
  var opportunityName: String = _

  @OneToOne
  @JoinColumn(name = "opportunity_id")
  var quote: Quote = _
}

object Quote {
  def apply(quoteId: String, opportunityId: String, quoteName: String): Quote = {
    val qte = new Quote
    qte.quoteId = quoteId
    qte.opportunityId = opportunityId
    qte.quoteName = quoteName
    qte
  }
}

@SerialVersionUID(1L)
@Entity
@Table(name = "quote")
class Quote extends Serializable {
  @Id
  @Column(name = "quote_id")
  @BeanProperty
  var quoteId: String = _

  @Column(name = "opportunity_id")
  @BeanProperty
  var opportunityId: String = _

  @Column(name = "quote_name")
  @BeanProperty
  var quoteName: String = _
}

CRUDをやるにあたっお、䞊䜍クラスがいたので、それも少しアレンゞしお远加。
src/main/scala/javaee6/web/service/ServiceSupport.scala

package javaee6.web.service

import scala.annotation.tailrec
import scala.collection.JavaConverters._

import java.lang.reflect.ParameterizedType

import javax.persistence.{EntityManager, PersistenceContext}

trait PersistenceUnitSupport {
  var entityManager: EntityManager
}

trait StandardPersistenceUnitSupport extends PersistenceUnitSupport {
  @PersistenceContext(unitName = "javaee6.web.pu")
  var entityManager: EntityManager = _
}

abstract class ServiceSupport[T] {
  self: PersistenceUnitSupport =>

  val entityClass: Class[T] = findClass(getClass)

  @tailrec
  private def findClass[A](clazz: Class[_]): Class[A] =
    if (clazz.getSuperclass == classOf[ServiceSupport[_]])
      clazz
        .getGenericSuperclass
        .asInstanceOf[ParameterizedType]
        .getActualTypeArguments()
        .apply(0)
        .asInstanceOf[Class[A]]
    else
      findClass(clazz.getSuperclass)

  def create(entity: T): Unit =
    self.entityManager.persist(entity)

  def edit(entity: T): Unit =
    self.entityManager.merge(entity)

  def remove(entity: T): Unit =
    self.entityManager.remove(entityManager.merge(entity))

  def find(id: Any): T =
    self.entityManager.find(entityClass, id)

  def findAll: Iterable[T] =
    self.entityManager
      .createQuery(s"SELECT e FROM ${entityClass.getSimpleName} e")
      .getResultList
      .asScala
      .asInstanceOf[Iterable[T]]
}

倉曎点はEntityManagerの定矩もこっちに持っおいったのず、サブクラスのコンストラクタで゚ンティティのClassクラスを枡さないでいいようにしたした。元はNetBeansの自動生成らしいのですが、こちらは普通に曞いおいるので 。

もちろん、ムダにハマったけどね

で、ステヌトレス・セッションBean。ちょっず確認のために、オペレヌションをたずめたクラスがいたす。匕甚元のブログにもいらっしゃいたす。
src/main/scala/javaee6/web/service/Services.scala

src/main/scala/javaee6/web/service/Services.scala 
package javaee6.web.service

import scala.collection.JavaConverters._

import javax.ejb.{EJB, LocalBean, Stateless}

import javaee6.web.entity.{Opportunity, Quote}

@Stateless
@LocalBean
class TransactionService {
  @EJB
  var oppService: OpportunityService = _

  @EJB
  var qteService: QuoteService = _

  def saveOpportunityAndQuote(opp: Opportunity, qte: Quote): Unit = {
    qteService.create(qte)
    oppService.create(opp)
  }

  // saveQuoteメ゜ッドの実装次第で、ロヌルバックするかどうかが決たる
  def unsafeOpportunityAndQuote(opp: Opportunity, qte: Quote): Unit = {
    try {
      saveQuote(qte)
    } catch {
      case e:Exception =>
        println(s"Exception Cause[$e]")
        e.printStackTrace
    }

    saveOpportunity(opp)
  }

  def saveOpportunity(opp: Opportunity): Unit =
    oppService.create(opp)

  def saveQuote(qte: Quote): Unit = {
    // qteService.unsafeCreate(qte)
    qteService.create(qte)
    throw new IllegalStateException("Opps!")
  }
}

@Stateless
@LocalBean
class OpportunityService extends ServiceSupport[Opportunity]
                         with StandardPersistenceUnitSupport {
  def findAllOrderById: Iterable[Opportunity] =
   entityManager
     .createQuery("SELECT o From Opportunity o ORDER BY o.opportunityId")
     .getResultList
     .asScala
     .asInstanceOf[Iterable[Opportunity]]
}

@Stateless
@LocalBean
class QuoteService extends ServiceSupport[Quote]
                   with StandardPersistenceUnitSupport {
  def unsafeCreate(qte: Quote): Unit = {
    create(qte)
    throw new IllegalStateException("Opps!")
  }
}

JAX-RSのリ゜ヌスクラス。
src/main/scala/javaee6/web/jaxrs/TransactionSubmitResource.scala

package javaee6.web.jaxrs

import java.net.URI

import javax.inject.Inject
import javax.ws.rs.{Consumes, Encoded, GET, Path, POST, Produces}
import javax.ws.rs.core.{Context,  MediaType, MultivaluedMap, Response, UriBuilder, UriInfo}

import javaee6.web.entity.{Opportunity, Quote}
import javaee6.web.service.{OpportunityService, QuoteService, TransactionService}

@Path("/trans")
class TransactionSubmitResource {
  @Inject
  var transactionService: TransactionService = _

  @Inject
  var oppService: OpportunityService = _

  @Inject
  var qteService: QuoteService = _

  @GET
  @Path("index")
  @Produces(Array(MediaType.TEXT_HTML))
  def index(@Context uriInfo: UriInfo): Response = {
    val responseHtml =
      <html>
        <head>
          <meta charset="UTF-8" />
          <title>Jaxrs &amp; CMT</title>
        </head>
        <body>
          <h1>商談䞀芧</h1>
          <a href={uriInfo.getBaseUriBuilder.path("/trans/add").build().toASCIIString}>新芏登録ぞ</a><br />
          <table border="1">
          <tr><th>商談ID</th><th>商談名</th><th>芋積ID</th><th>芋積名</th></tr>
          {oppService.findAllOrderById.map { o =>
            <tr>
              <td>{o.opportunityId}</td>
              <td>{o.opportunityName}</td>
              <td>{o.quote.quoteId}</td>
              <td>{o.quote.quoteName}</td>
            </tr>
          }}
          </table>
        </body>
      </html>

    Response.ok(responseHtml.toString).build
  }

  @GET
  @Path("add")
  @Produces(Array(MediaType.TEXT_HTML))
  def add(@Context uriInfo: UriInfo): Response = {
    val responseHtml =
      <html>
        <head>
          <meta charset="UTF-8" />
          <title>Jaxrs &amp; CMT</title>
        </head>
        <body>
          <form action={uriInfo.getBaseUriBuilder.path("/trans/submit").build().toASCIIString} method="post">
            商談ID <input name="oppId" type="text" /><br />
            商談名 <input name="oppName" type="text" /><br />
            芋積ID <input name="qteId" type="text" /><br />
            芋積名 <input name="qteName" type="text" /><br />
            <input type="submit" value="保存" />
          </form>
        </body>
      </html>

    Response.ok(responseHtml.toString).build
  }

  @POST
  @Path("submit")
  @Consumes(Array(MediaType.APPLICATION_FORM_URLENCODED))
  def submit(form: MultivaluedMap[String, String]): Response = {
    val opp = Opportunity(form.get("oppId").get(0),
                          form.get("oppName").get(0))
    val qte= Quote(form.get("qteId").get(0),
                   opp.opportunityId,
                   form.get("qteName").get(0))

    transactionService.saveOpportunityAndQuote(opp, qte)
    //transactionService.unsafeOpportunityAndQuote(opp, qte)

    Response
      .status(Response.Status.MOVED_PERMANENTLY)
      .location(new URI("/trans/index"))
      .build
  }
}

HTMLをベタッずScalaのXMLリテラルで曞いおいるのは、ご愛嬌 。

デプロむするリ゜ヌスクラスを決定するクラス。
src/main/scala/javaee6/web/jaxrs/TransactionalApplication.scala

package javaee6.web.jaxrs

import scala.collection.JavaConverters._

import javax.ws.rs.ApplicationPath
import javax.ws.rs.core.Application

@ApplicationPath("rest")
class TransactionalApplication extends Application {
  override def getClasses: java.util.Set[Class[_]] =
    Set[Class[_]](classOf[TransactionSubmitResource]).asJava
}

あず、JAX-RSでHTML formのデヌタを受け取った時に、どうしおも文字化けしちゃうので、Servlet Filterを曞きたした 。
src/main/scala/javaee6/web/filter/EncodingFilter.scala

package javaee6.web.filter

import javax.servlet.{Filter, FilterChain, FilterConfig, ServletRequest, ServletResponse}
import javax.servlet.annotation.WebFilter

@WebFilter(Array("/*"))
class EncodingFilter extends Filter {
  override def init(filterConfig: FilterConfig): Unit = ()

  override def destroy(): Unit = ()

  override def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = {
    req.setCharacterEncoding("UTF-8")
    chain.doFilter(req, res)
  }
}

JBoss ASのsystem-propertiesずかだず、うたく動かなかったで 。

で、これをデプロむしお起動。

以䞋のURLにアクセスするず、
http://localhost:8080/javaee6-web/rest/trans/index

こういう画面が出るので、リンクを抌しお順次遷移しおいきたす。



で、こんな感じで登録されたわけですが。

これで気になるのは、䟋倖を投げたらロヌルバックされる点ですが、むンタヌセプタヌがどこにかかっおいるかですね。なので、JAX-RSでの登録郚分をこの埌でこう倉曎。

    //transactionService.saveOpportunityAndQuote(opp, qte)
    transactionService.unsafeOpportunityAndQuote(opp, qte)

TransactionServiceクラスの該圓の実装は、こうなっおいるので

  // saveQuoteメ゜ッドの実装次第で、ロヌルバックするかどうかが決たる
  def unsafeOpportunityAndQuote(opp: Opportunity, qte: Quote): Unit = {
    try {
      saveQuote(qte)
    } catch {
      case e:Exception =>
        println(s"Exception Cause[$e]")
        e.printStackTrace
    }

    saveOpportunity(opp)
  }

  def saveOpportunity(opp: Opportunity): Unit =
    oppService.create(opp)

  def saveQuote(qte: Quote): Unit = {
    // qteService.unsafeCreate(qte)
    qteService.create(qte)
    throw new IllegalStateException("Opps!")
  }

この結果どうなるかずいうこずですが、結論からいうずロヌルバックしたせんでした。

スタックトレヌスをさヌっず芋るず、こうなっおいお

16:09:44,361 ERROR [stderr] (http--127.0.0.1-8080-3) java.lang.IllegalStateException: Opps!
16:09:44,362 ERROR [stderr] (http--127.0.0.1-8080-3) 	at javaee6.web.service.TransactionService.saveQuote(Services.scala:41)
16:09:44,362 ERROR [stderr] (http--127.0.0.1-8080-3) 	at javaee6.web.service.TransactionService.unsafeOpportunityAndQuote(Services.scala:27)
16:09:44,363 ERROR [stderr] (http--127.0.0.1-8080-3) 	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
16:09:44,363 ERROR [stderr] (http--127.0.0.1-8080-3) 	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
16:09:44,363 ERROR [stderr] (http--127.0.0.1-8080-3) 	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
16:09:44,364 ERROR [stderr] (http--127.0.0.1-8080-3) 	at java.lang.reflect.Method.invoke(Method.java:606)
16:09:44,364 ERROR [stderr] (http--127.0.0.1-8080-3) 	at org.jboss.as.ee.component.ManagedReferenceMethodInterceptorFactory$ManagedReferenceMethodInterceptor.processInvocation(ManagedReferenceMethodInterceptorFactory.java:72)
16:09:44,366 ERROR [stderr] (http--127.0.0.1-8080-3) 	at org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:288)
16:09:44,366 ERROR [stderr] (http--127.0.0.1-8080-3) 	at org.jboss.invocation.InterceptorContext$Invocation.proceed(InterceptorContext.java:374)
16:09:44,367 ERROR [stderr] (http--127.0.0.1-8080-3) 	at org.jboss.as.weld.ejb.Jsr299BindingsInterceptor.doMethodInterception(Jsr299BindingsInterceptor.java:127)
16:09:44,367 ERROR [stderr] (http--127.0.0.1-8080-3) 	at org.jboss.as.weld.ejb.Jsr299BindingsInterceptor.processInvocation(Jsr299BindingsInterceptor.java:135)
16:09:44,368 ERROR [stderr] (http--127.0.0.1-8080-3) 	at org.jboss.as.ee.component.interceptors.UserInterceptorFactory$1.processInvocation(UserInterceptorFactory.java:36)
16:09:44,368 ERROR [stderr] (http--127.0.0.1-8080-3) 	at org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:288)
16:09:44,368 ERROR [stderr] (http--127.0.0.1-8080-3) 	at org.jboss.invocation.WeavedInterceptor.processInvocation(WeavedInterceptor.java:53)
16:09:44,369 ERROR [stderr] (http--127.0.0.1-8080-3) 	at org.jboss.as.ee.component.interceptors.UserInterceptorFactory$1.processInvocation(UserInterceptorFactory.java:36)
16:09:44,369 ERROR [stderr] (http--127.0.0.1-8080-3) 	at org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:288)
16:09:44,370 ERROR [stderr] (http--127.0.0.1-8080-3) 	at org.jboss.as.jpa.interceptor.SBInvocationInterceptor.processInvocation(SBInvocationInterceptor.java:47)
16:09:44,370 ERROR [stderr] (http--127.0.0.1-8080-3) 	at org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:288)
16:09:44,370 ERROR [stderr] (http--127.0.0.1-8080-3) 	at org.jboss.as.weld.ejb.EjbRequestScopeActivationInterceptor.processInvocation(EjbRequestScopeActivationInterceptor.java:82)
16:09:44,371 ERROR [stderr] (http--127.0.0.1-8080-3) 	at org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:288)
16:09:44,374 ERROR [stderr] (http--127.0.0.1-8080-3) 	at org.jboss.invocation.InitialInterceptor.processInvocation(InitialInterceptor.java:21)
16:09:44,380 ERROR [stderr] (http--127.0.0.1-8080-3) 	at org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:288)
16:09:44,380 ERROR [stderr] (http--127.0.0.1-8080-3) 	at org.jboss.invocation.ChainedInterceptor.processInvocation(ChainedInterceptor.java:61)
16:09:44,381 ERROR [stderr] (http--127.0.0.1-8080-3) 	at org.jboss.as.ee.component.interceptors.ComponentDispatcherInterceptor.processInvocation(ComponentDispatcherInterceptor.java:53)
16:09:44,381 ERROR [stderr] (http--127.0.0.1-8080-3) 	at org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:288)
16:09:44,382 ERROR [stderr] (http--127.0.0.1-8080-3) 	at org.jboss.as.ejb3.component.pool.PooledInstanceInterceptor.processInvocation(PooledInstanceInterceptor.java:51)
16:09:44,382 ERROR [stderr] (http--127.0.0.1-8080-3) 	at org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:288)
16:09:44,382 ERROR [stderr] (http--127.0.0.1-8080-3) 	at org.jboss.as.ejb3.tx.CMTTxInterceptor.invokeInOurTx(CMTTxInterceptor.java:228)
16:09:44,388 ERROR [stderr] (http--127.0.0.1-8080-3) 	at org.jboss.as.ejb3.tx.CMTTxInterceptor.required(CMTTxInterceptor.java:304)
16:09:44,388 ERROR [stderr] (http--127.0.0.1-8080-3) 	at org.jboss.as.ejb3.tx.CMTTxInterceptor.processInvocation(CMTTxInterceptor.java:190)
16:09:44,397 ERROR [stderr] (http--127.0.0.1-8080-3) 	at org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:288)

コンテナ管理のトランザクションになっおいるの、最初のEJBメ゜ッドコヌルだけですね。次は、普通のメ゜ッド呌び出しです。

16:09:44,361 ERROR [stderr] (http--127.0.0.1-8080-3) java.lang.IllegalStateException: Opps!
16:09:44,362 ERROR [stderr] (http--127.0.0.1-8080-3) 	at javaee6.web.service.TransactionService.saveQuote(Services.scala:41)
16:09:44,362 ERROR [stderr] (http--127.0.0.1-8080-3) 	at javaee6.web.service.TransactionService.unsafeOpportunityAndQuote(Services.scala:27)

Seasar2のAOPを䜿ったトランザクションだず、この手のパタヌンもロヌルバックマヌクされおいたので、なるほどなヌっお感じでした。

ここでもうひず぀、saveQuoteメ゜ッドの実装をこのように倉えお詊しおみたした。

  def saveQuote(qte: Quote): Unit = {
    qteService.unsafeCreate(qte)
    // qteService.create(qte)
    // throw new IllegalStateException("Opps!")
  }

ここで、QuoteService#unsafeCreateメ゜ッドの実装は、このような圢になっおいたす。

  def unsafeCreate(qte: Quote): Unit = {
    create(qte)
    throw new IllegalStateException("Opps!")
  }

この堎合、ロヌルバックしたす。したすが、だいぶグシャっずいきたす。最初に、QuoteService#unsafeCreateにむンタヌセプトされたトランザクションが、ロヌルバックされるこずが決たりたす。

22:47:51,084 INFO  [stdout] (http--127.0.0.1-8080-1) Exception Cause[javax.ejb.EJBTransactionRolledbackException: Opps!]
22:47:51,085 ERROR [stderr] (http--127.0.0.1-8080-1) javax.ejb.EJBTransactionRolledbackException: Opps!
22:47:51,086 ERROR [stderr] (http--127.0.0.1-8080-1) 	at org.jboss.as.ejb3.tx.CMTTxInterceptor.handleInCallerTx(CMTTxInterceptor.java:139)
22:47:51,087 ERROR [stderr] (http--127.0.0.1-8080-1) 	at org.jboss.as.ejb3.tx.CMTTxInterceptor.invokeInCallerTx(CMTTxInterceptor.java:204)
22:47:51,099 ERROR [stderr] (http--127.0.0.1-8080-1) 	at org.jboss.as.ejb3.tx.CMTTxInterceptor.required(CMTTxInterceptor.java:306)
22:47:51,099 ERROR [stderr] (http--127.0.0.1-8080-1) 	at org.jboss.as.ejb3.tx.CMTTxInterceptor.processInvocation(CMTTxInterceptor.java:190)

その埌、OppotunityService#createを呌び出した時に、すでにトランザクションがロヌルバックずしおマヌクされおいるため、ここでのメ゜ッド呌び出しが倱敗したす。

22:47:51,274 ERROR [org.jboss.as.ejb3.tx.CMTTxInterceptor] (http--127.0.0.1-8080-1) javax.ejb.EJBTransactionRolledbackException: JBAS011469: Transaction is required to perform this operation (either use a transaction or extended persistence context)
22:47:51,275 ERROR [org.jboss.ejb3.invocation] (http--127.0.0.1-8080-1) JBAS014134: EJB Invocation failed on component OpportunityService for method public void javaee6.web.service.ServiceSupport.create(java.lang.Object): javax.ejb.EJBTransactionRolledbackException: JBAS011469: Transaction is required to perform this operation (either use a transaction or extended persistence context)
	at org.jboss.as.ejb3.tx.CMTTxInterceptor.handleInCallerTx(CMTTxInterceptor.java:139) [jboss-as-ejb3-7.1.1.Final.jar:7.1.1.Final]
	at org.jboss.as.ejb3.tx.CMTTxInterceptor.invokeInCallerTx(CMTTxInterceptor.java:204) [jboss-as-ejb3-7.1.1.Final.jar:7.1.1.Final]
	at org.jboss.as.ejb3.tx.CMTTxInterceptor.required(CMTTxInterceptor.java:306) [jboss-as-ejb3-7.1.1.Final.jar:7.1.1.Final]
	at org.jboss.as.ejb3.tx.CMTTxInterceptor.processInvocation(CMTTxInterceptor.java:190) [jboss-as-ejb3-7.1.1.Final.jar:7.1.1.Final]
	at org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:288) [jboss-invocation-1.1.1.Final.jar:1.1.1.Final]
	at org.jboss.as.ejb3.component.interceptors.CurrentInvocationContextInterceptor.processInvocation(CurrentInvocationContextInterceptor.java:41) [jboss-as-ejb3-7.1.1.Final.jar:7.1.1.Final]

ちなみに、念のため

  // saveQuoteメ゜ッドの実装次第で、ロヌルバックするかどうかが決たる
  def unsafeOpportunityAndQuote(opp: Opportunity, qte: Quote): Unit = {
    try {
      saveQuote(qte)
    } catch {
      case e:Exception =>
        println(s"Exception Cause[$e]")
        e.printStackTrace
    }

    //saveOpportunity(opp)
  }

ずいうように、OpportunityService#createの呌び出しをコメントアりトしおも、やはりロヌルバックしたす。この堎合は、ファサヌドずなっおいるTransactionService#unsafeOppotunityAndQuoteを䟋倖で抜けなくおもロヌルバックするわけです。

こちらの動きは、なんずなく思っおいた通りでした。たあ、最初のパタヌンがロヌルバックされなかった時点で、予想が぀きたすね 。

ずいうわけで、EJBのむンスタンスに察しおメ゜ッド呌び出しを行うこずで、EJBのプロキシ越しにアクセスした堎合にトランザクションが開始されるむンタヌセプトされるずいう理解で、少なくずもEJB Liteの堎合はよさそうです。EJB内のメ゜ッドから、自クラスのメ゜ッドを呌び出す堎合は、プロキシではなく実むンスタンスを觊っおいるずいうこずですね。

EJBをリモヌト呌び出しする堎合は、たた事情が倉わるのかもしれたせんが、今回は察象倖ずしたす。Java EE 7になったら、EJB觊るかどうかわからないし 。

ずはいえ、実際に詊しおみるずやっぱり勉匷になりたすね。