首先,介绍一些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.closeqs.closeqc.close}
我已经通过编写MySQL并将JMS消息发送给JBoss(HornetQ)进行了测试,它似乎运行良好(除了让hornetQ与Bitronix一起玩是个bit子-参见此处: https ://community.jboss.org/ 线程/ 206180?tstart = 0 )。
XA支持的scala代码为:
package ch.maxant.scalabook.play20.plugins.xasupportimport 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.ConnectionFactoryclass 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 = falsedef 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.tmcase 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 asynclyval t = tm.getCurrentTransactionLogger("XASupport").info("Started XA transaction " + t.getGtrid())val ctx = new XAContext()var completed = falsetry{val result = f(ctx)completed = trueif(!ctx.rollbackOnly){Logger("XASupport").info("committing " + t.getGtrid() + "...")t.commitLogger("XASupport").info("committed " + t.getGtrid())}result}finally{if(!completed || ctx.rollbackOnly){//in case of exception, or in case of set rollbackOnly = trueLogger("XASupport").warn("rolling back (completed=" + completed + "/ctx.rollbackOnly=" + ctx.rollbackOnly)t.rollback}}}
}class XASupportPlugin(app: play.Application) extends Plugin {protected[plugins] var tm: BitronixTransactionManager = nulloverride def onStart {//TODO how about getting config out of jar!val file = new File(".", "app/bitronix-default-config.properties").getAbsolutePathLogger("XASupport").info("Using Bitronix config at " + file)val prop = System.getProperty("bitronix.tm.configuration", file) //defaultSystem.setProperty("bitronix.tm.configuration", prop) //override with default, if not set//start the TMtm = TransactionManagerServices.getTransactionManagerLogger("XASupport").info("Started TM with resource config " + TransactionManagerServices.getConfiguration.getResourceConfigurationFilename)}override def onStop {//on graceful shutdown, we want to shutdown the TM tooLogger("XASupport").info("Shutting down TM")tm.shutdownLogger("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