Play 2.0框架和XA交易

XA事务非常有用,而且开箱即用,今天的Play 2.0不支持它们。 在这里,我展示了如何添加该支持:

首先,介绍一些XA有用的示例:

–如果您使用来自两个不同persistence.xml的实体,则JPA使用两个物理连接–这两个连接可能需要在一个事务中提交,因此XA是您唯一的选择
–提交数据库更改,同时向JMS提交消息。 例如,您想保证在成功将订单异步提交到数据库后发送了一封电子邮件。 还有其他方法,但是JMS提供了一种事务性的方法来完成此任务,而不必考虑故障。 –由于多种政治原因(遗留系统,负责不同数据库服务器的不同部门/不同预算)中的任何原因而写入物理上不同的数据库。 –请参阅http://docs.codehaus.org/display/BTM/FAQ#FAQ-WhywouldIneedatransactionmanager

因此,从我的角度来看,XA是Play需要“支持”的东西。

添加支持非常容易。 我创建了一个基于Bitronix的播放插件。 资源是在Bitronix JNDI树中配置的(为什么Play会使用配置文件而不是JNDI ?!无论如何...)您可以使用“ withXaTransaction”启动事务:

def someControllerMethod = Action {
                    withXaTransaction { ctx =>
                        TicketRepository.

addValidation(user.get, bookingRef, ctx)
                        ValidationRepository.addValidation(bookingRef, user.get, ctx)
                    }
                    val tickets = TicketRepository.getByEventUid(eventUid)
                    Ok(views.html.ticketsInEvent(eventUid, getTickets(eventUid), user, eventValidationForm))
    }

ctx对象是XAContext(我自己的类),它使您可以查找资源(如数据源),或在发生故障时设置回滚。 因此,验证存储库使用ScalaQuery(我使用“ withSession”而不是“ withTransaction!”)来完成此操作:

def addValidation(bookingRef: String, validator: User, ctx: XAContext) = {
        val ds = ctx.lookupDS("jdbc/maxant/scalabook_admin")
        Database.forDataSource(ds) withSession { implicit db: Session =>
            Validations.insert(Validation(bookingRef, validator.email, new java.sql.Timestamp(now)))
        }
    }

票务回购使用JMS执行以下操作:

def addValidation(user: User, bookingRef: String, ctx: XAContext) = {

        val xml = 
            
                {bookingRef}
                {user.email}
            

        val qcf = ctx.lookupCF("jms/maxant/scalabook/ticketvalidations")
        val qc = qcf.createConnection("ticketValidation","password")
        val qs = qc.createSession(false, Session.AUTO_ACKNOWLEDGE)
        val q = qs.createQueue("ticketValidationQueue") //val q = ctx.lookup(QUEUE).asInstanceOf[Queue]
        val sender = qs.createProducer(q)
        val m = qs.createTextMessage(xml.toString)
        sender.send(m)
        sender.close
        qs.close
        qc.close
    }

我已经通过编写MySQL并将JMS消息发送给JBoss(HornetQ)进行了测试,它似乎运行良好(除了让hornetQ与Bitronix一起玩是个bit子-参见此处: https ://community.jboss.org/ 线程/ 206180?tstart = 0 )。

XA支持的scala代码为:

package ch.maxant.scalabook.play20.plugins.xasupport

import play.api.mvc.RequestHeader
import play.api.mvc.Results
import play.api.mvc.Request
import play.api.mvc.AnyContent
import play.api.mvc.Result
import play.api.mvc.Action
import play.api.mvc.Security
import play.api._
import play.api.mvc._
import play.api.data._
import play.api.data.Forms._
import ch.maxant.scalabook.persistence.UserRepository
import bitronix.tm.TransactionManagerServices
import java.util.Hashtable
import javax.naming.Context._
import javax.naming.InitialContext
import javax.sql.DataSource
import bitronix.tm.BitronixTransaction
import java.io.File
import org.scalaquery.session.Database
import org.scalaquery.SQueryException
import scala.collection.mutable.ListBuffer
import java.sql.Connection
import java.sql.SQLException
import org.scalaquery.session.Session
import bitronix.tm.BitronixTransactionManager
import javax.jms.ConnectionFactory

class XAContext {

    private val env = new Hashtable[String, String]()
    env.put(INITIAL_CONTEXT_FACTORY, "bitronix.tm.jndi.BitronixInitialContextFactory")
    private val namingCtx = new InitialContext(env);

    var rollbackOnly = false
    
    def lookup(name: String) = {
        namingCtx.lookup(name)
    }
    def lookupDS(name: String) = {
        lookup(name).asInstanceOf[DataSource]
    }
    def lookupCF(name: String) = {
        lookup(name).asInstanceOf[ConnectionFactory]
    }
}

trait XASupport { self: Controller =>

    private lazy val tm = play.api.Play.current.plugin[XASupportPlugin] match {
      case Some(plugin) => plugin.tm
      case None => throw new Exception("There is no XASupport plugin registered. Make sure it is enabled. See play documentation. (Hint: add it to play.plugins)")
    }

    /**
     * Use this flow control to make resources used inside `f` commit with the XA protocol.
     * Conditions: get resources like drivers or connection factories out of the context passed to f.
     * Connections are opened and closed as normal, for example by the withSession flow control offered 
     * by ScalaQuery / SLICK.
     */
    def withXaTransaction[T](f: XAContext => T): T = {
        tm.begin

        //get a ref to the transaction, in case when we want to commit we are no longer on the same thread and TLS has lost the TX.
        //we have no idea what happens inside f!  they might spawn new threads or send work to akka asyncly
        val t = tm.getCurrentTransaction
        Logger("XASupport").info("Started XA transaction " + t.getGtrid())
        val ctx = new XAContext()
        var completed = false
        try{
            val result = f(ctx)
            completed = true
            if(!ctx.rollbackOnly){
                Logger("XASupport").info("committing " + t.getGtrid() + "...")
                t.commit
                Logger("XASupport").info("committed " + t.getGtrid())
            }
            result
        }finally{
            if(!completed || ctx.rollbackOnly){
                //in case of exception, or in case of set rollbackOnly = true
                Logger("XASupport").warn("rolling back (completed=" + completed + "/ctx.rollbackOnly=" + ctx.rollbackOnly)
                t.rollback
            }
        }
    }
}

class XASupportPlugin(app: play.Application) extends Plugin {

    protected[plugins] var tm: BitronixTransactionManager = null
    
    override def onStart {
        //TODO how about getting config out of jar!
        val file = new File(".", "app/bitronix-default-config.properties").getAbsolutePath
        Logger("XASupport").info("Using Bitronix config at " + file)
        val prop = System.getProperty("bitronix.tm.configuration", file) //default
        System.setProperty("bitronix.tm.configuration", prop) //override with default, if not set

        //start the TM
        tm = TransactionManagerServices.getTransactionManager
        
        Logger("XASupport").info("Started TM with resource config " + TransactionManagerServices.getConfiguration.getResourceConfigurationFilename)
    }

    override def onStop {
        //on graceful shutdown, we want to shutdown the TM too
        Logger("XASupport").info("Shutting down TM")
        tm.shutdown
        Logger("XASupport").info("TM shut down")
    }

}

随便使用代码,我免费提供它:-)如果不起作用,请不要抱怨;-)

看到此插件扩展并将其转换成更多生产版本,真是太好了。 Play更好地原生支持事务管理器,包括从JNDI中获取资源。

祝您编程愉快,别忘了分享!

参考:来自Zoo博客The Kitchen的 JCG合作伙伴 Ant Kutschera的Play 2.0框架和XA事务


翻译自: https://www.javacodegeeks.com/2012/10/play-20-framework-and-xa-transactions.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值