Tomcat 6 Integration with Atomikos 3.5.2+ with lifecycle support
It is possible to fully integrate the Atomikos transaction manager into Tomcat. Doing it this way makes the transaction manager shared across all web applications exactly like with any full-blown J2EE server. Additional efforts have to be made if the resource are configured using JNDI. Tomcat's JNDI does not include a lifecycle management. Therefore it has to be implemented.
A feature like this is needed to be able to start and stop a webapp without running into duplicate resource exceptions.
This implementation is also webapp aware. This is useful to access the same JNDI resource from more than one webapp.
- Important note
- Installation
- Create server lifecycle listener
- Create context lifecycle listener
- Create lifecycle manager
- Create JNDI factory for Atomikos
- Compile and copying the extensions
- Copying TransactionsEssentials libraries
- Copying Atomikos configuration file
- Edit server.xml
- Edit global context.xml
- Edit webapp context.xml
- Using WebSphere MQ
Important note
When the Atomikos transaction manager is installed globally in Tomcat, you now must also install your JDBC driver at the same global location (ie: into the TOMCAT_HOME/lib
folder). If you dont do that, you will get a NoClassDefFoundErrors or a ClassNotFoundException or even a ClassCastException during your web application deployment.
This is not a limitation of Atomikos nor of Tomcat but of the J2EE class loading design that both Tomcat and Atomikos must follow.
Installation
Installation is quite simple, it just involves compling source code into JAR files and copying them, create a property file and editing some Tomcat configuration files.
Create server lifecycle listener
The LifecycleListener has to be changed since release 3.5.2. The first class which callsUserTransactionManager.init() is the master for UserTransactionManager. It is not the first class which calls newUserTransactionManager(). Only the master closes UserTransactionManager with its close() method. ThereforeUserTransactionManager.init() has to be called after the new operator. The master UserTransactionManager has be created by a lifecycle listener to have the manager available independent of the availability of webapps.
AtomikosLifecycleListener.java
package com.atomikos.tomcat; import org.apache.catalina.Lifecycle; import org.apache.catalina.LifecycleEvent; import org.apache.catalina.LifecycleListener; import com.atomikos.icatch.jta.UserTransactionManager; import com.atomikos.icatch.system.Configuration; public class AtomikosLifecycleListener implements LifecycleListener { private UserTransactionManager utm; public void lifecycleEvent(LifecycleEvent event) { try { if (Lifecycle.START_EVENT.equals(event.getType())) { if (utm == null) { utm = new UserTransactionManager(); } utm.init(); } else if (Lifecycle.AFTER_STOP_EVENT.equals(event.getType())) { if (utm != null) { utm.close(); } } } catch (Exception e) { System.err.println(e.getMessage()); } } }
Create context lifecycle listener
Here starts the the lifecycle management. This listener informs the lifecycle manager when a webapp starts or stops.
ContextLifecycleListener.java
package com.atomikos.tomcat; import org.apache.catalina.Lifecycle; import org.apache.catalina.LifecycleEvent; import org.apache.catalina.LifecycleListener; public class ContextLifecycleListener implements LifecycleListener { private String webappName = null; public void setWebappName(String name) { webappName = name; } public void lifecycleEvent(LifecycleEvent event) { try { if (Lifecycle.START_EVENT.equals(event.getType())) { AtomikosLifecycleManager.getInstance().startWebApp(webappName); } else if (Lifecycle.STOP_EVENT.equals(event.getType())) { AtomikosLifecycleManager.getInstance().stopWebApp(webappName); } } catch (Exception e) { System.err.println(e.getMessage()); } } }
Create lifecycle manager
This manager maintains a list of Atomikos resources for every webapp. Atomikos resources of a named webapp are closed on request from context lifecycle listener.
AtomikosLifecycleManager.java
package com.atomikos.tomcat; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import com.atomikos.jdbc.AtomikosDataSourceBean; import com.atomikos.jms.AtomikosConnectionFactoryBean; public class AtomikosLifecycleManager { private static AtomikosLifecycleManager manager = null; private String webappName = null; final private ConcurrentMap<String, List<Object>> atomikosObjectMap = new ConcurrentHashMap<String, List<Object>>(); private AtomikosLifecycleManager() { } public synchronized static AtomikosLifecycleManager getInstance() { if (manager == null) { manager = new AtomikosLifecycleManager(); } return (manager); } public void startWebApp(String name) { webappName = name; atomikosObjectMap.put(name, new ArrayList<Object>()); } public String getWebappName() { return (webappName); } public void addResource(Object obj) { if (atomikosObjectMap.containsKey(webappName)) { atomikosObjectMap.get(webappName).add(obj); } } public void stopWebApp(String name) { if (atomikosObjectMap.containsKey(name)) { List<Object> list = atomikosObjectMap.get(name); for (Object obj : list) { if (obj instanceof AtomikosConnectionFactoryBean) { ((AtomikosConnectionFactoryBean) obj).close(); } else if (obj instanceof AtomikosDataSourceBean) { ((AtomikosDataSourceBean) obj).close(); } } list.clear(); } } }
Create JNDI factory for Atomikos
JNDI factory to create connection and data source beans. The beans are initialized after creation.
AtomikosTomcatFactoryFactory.java
package com.atomikos.tomcat; import java.util.Enumeration; import java.util.Hashtable; import javax.jms.JMSException; import javax.naming.Context; import javax.naming.Name; import javax.naming.NamingException; import javax.naming.RefAddr; import javax.naming.Reference; import javax.naming.spi.ObjectFactory; import org.apache.naming.ResourceRef; import com.atomikos.beans.PropertyException; import com.atomikos.beans.PropertyUtils; import com.atomikos.jdbc.AtomikosDataSourceBean; import com.atomikos.jdbc.AtomikosSQLException; import com.atomikos.jdbc.nonxa.AtomikosNonXADataSourceBean; import com.atomikos.jms.AtomikosConnectionFactoryBean; public class AtomikosTomcatFactoryFactory implements ObjectFactory { public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception { if (obj instanceof ResourceRef) { try { Reference ref = (Reference) obj; String beanClassName = ref.getClassName(); Class<?> beanClass = null; ClassLoader tcl = Thread.currentThread().getContextClassLoader(); if (tcl != null) { try { beanClass = tcl.loadClass(beanClassName); } catch (ClassNotFoundException e) { throw new NamingException("Could not load class " + beanClassName); } } else { try { beanClass = Class.forName(beanClassName); } catch (ClassNotFoundException e) { throw new NamingException("Could not load class " + beanClassName); } } if (beanClass == null) { throw new NamingException("Class not found: " + beanClassName); } if (AtomikosDataSourceBean.class.isAssignableFrom(beanClass)) { return createDataSourceBean(ref, (AtomikosDataSourceBean) beanClass.newInstance()); } else if (AtomikosNonXADataSourceBean.class.isAssignableFrom(beanClass)) { return createNonXADataSourceBean(ref, (AtomikosNonXADataSourceBean) beanClass.newInstance()); } else if (AtomikosConnectionFactoryBean.class.isAssignableFrom(beanClass)) { return createConnectionFactoryBean(ref, (AtomikosConnectionFactoryBean) beanClass.newInstance()); } else { throw new NamingException( "Class is neither an AtomikosDataSourceBean nor an AtomikosConnectionFactoryBean: " + beanClassName); } } catch (InstantiationException e) { throw (NamingException) new NamingException( "error creating AtomikosDataSourceBean or AtomikosConnectionFactoryBean").initCause(e); } catch (IllegalAccessException e) { throw (NamingException) new NamingException( "error creating AtomikosDataSourceBean or AtomikosConnectionFactoryBean").initCause(e); } catch (JMSException e) { throw (NamingException) new NamingException("error creating AtomikosConnectionFactoryBean").initCause(e); } catch (AtomikosSQLException e) { throw (NamingException) new NamingException("error creating AtomikosDataSourceBean").initCause(e); } catch (PropertyException e) { throw (NamingException) new NamingException( "error creating AtomikosDataSourceBean or AtomikosConnectionFactoryBean").initCause(e); } } return (null); } private Object createConnectionFactoryBean(Reference ref, AtomikosConnectionFactoryBean bean) throws JMSException { Enumeration<RefAddr> en = ref.getAll(); int i = 0; while (en.hasMoreElements()) { RefAddr ra = (RefAddr) en.nextElement(); String propName = ra.getType(); String value = (String) ra.getContent(); /** * uniqueResourceName is only unique per webapp but has to made * unique globally. */ if (propName.equals("uniqueResourceName")) { value = AtomikosLifecycleManager.getInstance().getWebappName() + "/" + value; } try { PropertyUtils.setProperty(bean, propName, value); i++; } catch (PropertyException pe) { System.out.println("Property " + propName + "could not be set. " + pe.getMessage()); } } bean.init(); AtomikosLifecycleManager.getInstance().addResource(bean); return (bean); } private Object createNonXADataSourceBean(Reference ref, AtomikosNonXADataSourceBean bean) throws AtomikosSQLException, PropertyException { if (logger.isDebugEnabled()) { logger.debug("instanciating bean of class " + bean.getClass().getName()); } Enumeration<RefAddr> en = ref.getAll(); int i = 0; while (en.hasMoreElements()) { RefAddr ra = (RefAddr) en.nextElement(); String propName = ra.getType(); String value = (String) ra.getContent(); /** * uniqueResourceName is only unique per webapp but has to made * unique globally. */ if (propName.equals("uniqueResourceName")) { value = AtomikosLifecycleManager.getInstance().getWebappName() + "/" + value; } if (logger.isDebugEnabled()) { logger.debug("setting property '" + propName + "' to '" + value + "'"); } try { PropertyUtils.setProperty(bean, propName, value); i++; } catch (PropertyException pe) { System.out.println("Property " + propName + "could not be set. " + pe.getMessage()); } } bean.init(); AtomikosLifecycleManager.getInstance().addResource(bean); return (bean); } private Object createDataSourceBean(Reference ref, AtomikosDataSourceBean bean) throws AtomikosSQLException { if (logger.isDebugEnabled()) { logger.debug("instanciating bean of class " + bean.getClass().getName()); } Enumeration<RefAddr> en = ref.getAll(); int i = 0; while (en.hasMoreElements()) { RefAddr ra = (RefAddr) en.nextElement(); String propName = ra.getType(); String value = (String) ra.getContent(); /** * uniqueResourceName is only unique per webapp but has to made * unique globally. */ if (propName.equals("uniqueResourceName")) { value = AtomikosLifecycleManager.getInstance().getWebappName() + "/" + value; } if (logger.isDebugEnabled()) { logger.debug("setting property '" + propName + "' to '" + value + "'"); } try { PropertyUtils.setProperty(bean, propName, value); i++; } catch (PropertyException pe) { System.out.println("Property " + propName + "could not be set. " + pe.getMessage()); } } bean.init(); AtomikosLifecycleManager.getInstance().addResource(bean); return (bean); } }
Compile and copying the extensions
- Compile the source code into a library
- Copy the library to
TOMCAT_HOME/lib
Copying TransactionsEssentials libraries
- Drop the following JARs from the Atomikos distribution to
TOMCAT_HOME/lib
folder:- transactions.jar
- transactions-api.jar
- transactions-jta.jar
- transactions-jdbc.jar
- atomikos-util.jar.jar
- jta.jar
You should also copy the transactions-hibernate3.jar and/or transactions-hibernate2.jar at the same location if you're planning to use Hibernate.
Copying Atomikos configuration file
- Drop the following properties file into the
TOMCAT_HOME/lib
folder: jta.properties
Edit server.xml
Then edit the TOMCAT_HOME/conf/server.xml
file. At the beginning of the file you should see these four lines:
<Listener className="org.apache.catalina.core.AprLifecycleListener" /> <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" /> <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" /> <Listener className="org.apache.catalina.storeconfig.StoreConfigLifecycleListener"/>
Right after the last one, add this fifth one:
<Listener className="com.atomikos.tomcat.AtomikosLifecycleListener" />
Edit global context.xml
Then edit the TOMCAT_HOME/conf/context.xml
file. At the beginning of the file you should see this line:
<WatchedResource>WEB-INF/web.xml</WatchedResource>
Right after it, add that one:
<Transaction factory="com.atomikos.icatch.jta.UserTransactionFactory" />
Edit webapp context.xml
Then edit META-INF/context.xml
from a war file. Add at the beginning of section Context.
<?xml version="1.0" encoding="UTF-8"?> <Context> <Listener className="com.atomikos.tomcat.ContextLifecycleListener" webappName="test" /> ... </Context>
Using MySQL
Notice: testQuery is very important for connection management.
<?xml version="1.0" encoding="UTF-8"?> <Context reloadable="true" crossContext="true"> <Resource name="jdbc/myDB" auth="Container" type="com.atomikos.jdbc.AtomikosDataSourceBean" factory="com.atomikos.tomcat.AtomikosTomcatFactoryFactory" uniqueResourceName="jdbc/myDB" xaDataSourceClassName="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" xaProperties.databaseName="test" xaProperties.serverName="localhost" xaProperties.port="3306" xaProperties.user="USER" xaProperties.password="PASSWORD" xaProperties.url="jdbc:mysql://localhost:3306/test" testQuery="select 1" /> </Context>
Remember to change the parameter values to your specific environment...
Using WebSphere MQ
MQ client must operate in bind mode to be able use MQ in global transactions.
<?xml version="1.0" encoding="UTF-8"?> <Context reloadable="true" crossContext="true"> <Resource name="jms/myQCF" auth="Container" type="com.atomikos.jms.AtomikosConnectionFactoryBean" factory="com.atomikos.tomcat.AtomikosTomcatFactoryFactory" uniqueResourceName="QCF_MQSeries_XA" xaConnectionFactoryClassName="com.ibm.mq.jms.MQXAQueueConnectionFactory" xaProperties.queueManager="XXX" maxPoolSize="3" minPoolSize="1" /> <Resource name="jms/myQ" auth="Container" type="com.ibm.mq.jms.MQQueue" factory="com.ibm.mq.jms.MQQueueFactory" description="JMS Queue for reading messages" QU="MYQ.IN" /> </Context>
相关推荐
Atomikos的`atomikos-tomcat-beanfactory.jar`和`atomikos-integration-extension-3.7.1-20120529.jar`是用来整合Tomcat与Atomikos的,使得Tomcat能够处理JTA事务。 6. **JTA相关库**: `transactions-api-3.9.1.jar...
SpringBoot+Mybatis+Atomikos+Mysql+Oracle 多数据源分布式事物后台搭建 完整demo包,直接下下来解压,数据库配成自己的库,表自己的表,修改下脚本直接跑,网上大把资料,没一个能直接用的,这里花了点时间稍做...
atomikos 集成 Tomcat6Integration33
Spring Boot:mybatis-plus + atomikos + druid 实现不同实例数据库的多数据源配置和分布式事务管理(demo项目),想到工作上可能会用到多数据源,但是自己在这方面并不是很熟悉,于是在网上查阅了很多文章,结果...
2. **集成Atomikos**:添加Atomikos依赖后,我们需要配置Atomikos的事务管理器。在Spring Boot的配置文件(application.properties或yaml)中,设置Atomikos的属性,如事务超时时间、回滚策略等。同时,需要创建一个...
使用spring + atomikos+druid配置的分布式事务demo,两种数据源配置方式都可以,使用junit测试没问题,案例中使用的mysql数据库是8.0.11版本,版本不同请自行修改pom.xml和jdbc.properties
在Spring Boot中,我们可以轻松集成各种数据库连接池,如HikariCP、Tomcat JDBC Pool和Druid。 Druid是一个功能强大的数据库连接池,提供了监控、SQL解析、拦截器等功能。在多数据源配置中,Druid可以作为一个高效...
6. 编写业务代码,根据需求调用两个数据源的DAO层方法。 通过上述步骤,我们能够在Spring Boot应用中成功地实现一个基于Atomikos的分布式事务系统,保证在不同数据库间的操作一致性。这种解决方案在处理涉及多个...
本项目使用Spring Boot、Atomikos、JTA(Java Transaction API)、Hibernate和MySQL来实现分布式事务处理和多数据源管理,以确保在多个数据库操作之间保持事务的ACID特性。 首先,Spring Boot作为微服务开发的主流...
本文将详细讲解如何利用Spring Boot、Atomikos、JPA(Java Persistence API)以及MySQL来实现JTA(Java Transaction API)分布式事务。 首先,Spring Boot是一个轻量级的框架,它简化了基于Spring的应用程序开发...
标题 "spring + hibernate + atomikos + mysql (diff database)" 涉及的是一个集成开发环境,其中Spring是Java企业级应用的核心框架,Hibernate是一个流行的ORM(对象关系映射)解决方案,Atomikos是一个开源的JTA...
本示例"spring+atomikos+druid分布式事务Demo"聚焦于如何在Spring框架中利用Atomikos和Druid来处理分布式事务。接下来,我们将深入探讨这三个组件以及它们在实现分布式事务中的作用。 Spring是一个广泛使用的Java...
将基于Spring4.1.7+atomikos+mybaits 实现两阶段的分布式事务处理,通过AOP面向切面实现动态实现数据源的切换 http://www.dczou.com/viemall/407.html
本文将深入探讨如何利用Spring 4.1.7、Atomikos以及MyBatis实现两阶段的分布式事务(2PC,Two-Phase Commit)。这是一套强大的解决方案,特别适用于处理跨多个数据库或服务的复杂业务逻辑。 首先,我们需要理解什么...
jta Springboot + Atomikos + Druid + Mysql 实现JTA分布式事务 问题一:Druid 和 Mysql 版本的问题 问题二:Springtest 默认情况下事务回滚 Mysql对XA协议支持不好,有待看
Atomikos 3.7 是一个著名的开源事务处理解决方案,尤其在Java企业级应用中,它扮演着重要的角色。这个项目整合的jar包是针对Java Transaction API (JTA) 的扩展,提供了分布式事务管理功能,使得应用程序能够在多个...
Spring 中使用 Atomikos+Druid 实现经典分布式事务的方法 在分布式系统中,经典分布式事务是相对互联网中的柔性分布式事务而言,其特性为 ACID 原则,包括原子性(Atomictiy)、一致性(Consistency)、隔离性...
Spring Boot、MyBatis 和 Atomikos 的结合提供了一种解决方案,用于处理多数据源的分布式事务管理。以下是对这个主题的详细阐述。 首先,Spring Boot 是一个基于 Spring 框架的轻量级开发工具,它简化了配置过程并...
SpringBoot集成Atomikos使用Oracle数据库mybatisSpringBoot集成Atomikos使用Oracle数据库mybatisSpringBoot集成Atomikos使用Oracle数据库mybatisSpringBoot集成Atomikos使用Oracle数据库mybatis