首页 > 单点登录, 解决方案 > cas整合Atomikos分布式事务源码示例
201208月13

cas整合Atomikos分布式事务源码示例

背景

最近专注sso的研究,其中涉及到多个应用之间的数据同步问题,现阶段采用数据库方式进行融合(也就是分布式事务),也许是心理阴影(两年前使用过但是不是特别文档)使然,放弃了JOTM 这种方案,在google一下并且对比了一下,最后采用Atomikos ,理由如下:
1、兼容标准的SUN公司JTA API
2、嵌套事务
3、为XA和非XA提供内置的JDBC适配器
4、内置的JMS适配器XA-capable JMS队列连接器
5、通过XA API兼容第三方适配器
6、更好的整合您的项目(spring集成方式相当简洁)
7、集成Hibernate

列举一下Atomikos在SSO使用场景

涉及到三个系统:sso、商城、discuz论坛,用户信息以sso为中心然后同步到相应的子系统 ,涉及到注册、修改密码这些功能的地方都要需要做分布式事务保障。

技术实现

spring+aspectj+Atomikos+JDBC

配置文件

  • applicationContext-datasource-jta.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
	http://www.springframework.org/schema/aop
	http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
	http://www.springframework.org/schema/tx
	http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
	<!--配置三个数据源 分别是sso服务器、 商城、discuz论坛 begin-->
	<bean id="dataSourceSSO" class="com.atomikos.jdbc.AtomikosDataSourceBean"
		init-method="init" destroy-method="close">
		<property name="uniqueResourceName">
			<value>mysql/sso</value>
		</property>
		<property name="xaDataSourceClassName">
			<!--
		使用Mysql XADataSource(mysql>=5.0, Connector/J>=5.0才可以支持XADatasource)
			-->
			<value>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</value>
		</property>
		<property name="xaProperties">
			<props>
				<prop key="user">root</prop>
				<prop key="password">root</prop>
				<prop key="URL">jdbc:mysql://localhost:3306/sso
					?useUnicode=true&amp;characterEncoding=utf-8
				</prop>
				<prop key="pinGlobalTxToPhysicalConnection">true</prop>
				<prop key="autoReconnect">true</prop>
			</props>
		</property>
		<property name="poolSize">
			<value>10</value>
		</property>
		<property name="testQuery" value="SELECT 1" />
	</bean>
	<bean id="dataSourceTshop" class="com.atomikos.jdbc.AtomikosDataSourceBean"
		init-method="init" destroy-method="close">
		<property name="uniqueResourceName">
			<value>mysql/tshop</value>
		</property>
		<property name="xaDataSourceClassName">
			<!--
		使用Mysql XADataSource(mysql>=5.0, Connector/J>=5.0才可以支持XADatasource)
			-->
			<value>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</value>
		</property>
		<property name="xaProperties">
			<props>
				<prop key="user">root</prop>
				<prop key="password">root</prop>
				<prop key="URL">jdbc:mysql://localhost:3306/tshop
					?useUnicode=true&amp;characterEncoding=utf-8
				</prop>
				<prop key="pinGlobalTxToPhysicalConnection">true</prop>
				<prop key="autoReconnect">true</prop>
			</props>
		</property>
		<property name="poolSize">
			<value>10</value>
		</property>
		<property name="testQuery" value="SELECT 1" />
	</bean>
	<bean id="dataSourceDiscuz" class="com.atomikos.jdbc.AtomikosDataSourceBean"
		init-method="init" destroy-method="close">
		<property name="uniqueResourceName">
			<value>mysql/Discuz</value>
		</property>
		<property name="xaDataSourceClassName">
			<!--
		使用Mysql XADataSource(mysql>=5.0, Connector/J>=5.0才可以支持XADatasource)
			-->
			<value>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</value>
		</property>
		<property name="xaProperties">
			<props>
				<prop key="user">root</prop>
				<prop key="password">root</prop>
				<prop key="URL">jdbc:mysql://localhost:3306/discuz
				?useUnicode=true&amp;characterEncoding=utf-8
				</prop>
				<prop key="pinGlobalTxToPhysicalConnection">true</prop>
				<prop key="autoReconnect">true</prop>
			</props>
		</property>
		<property name="poolSize">
			<value>10</value>
		</property>
		<property name="testQuery" value="SELECT 1" />
	</bean>
</beans>
  • applicationContext-jdbc-jta.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
	http://www.springframework.org/schema/aop
	http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
	http://www.springframework.org/schema/tx
	http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
	<bean id="ssoJDBC" class="casside.jdbc.SSOCenterJDBCImpl">
		<property name="dataSource" ref="dataSourceSSO" />
		<property name="sql" value="select upwd from muser where uname = ? " />
		<property name="passwordEncoder" ref="passwordEncoder" />
	</bean>
	<bean id="tshopJDBC" class="casside.jdbc.thirdpart.TshopJDBCSupport">
		<property name="dataSource">
			<ref bean="dataSourceTshop" />
		</property>
	</bean>
	<bean id="discuzJDBC" class="casside.jdbc.thirdpart.DiscuzJDBCSupport">
		<property name="dataSource">
			<ref bean="dataSourceDiscuz" />
		</property>
	</bean>
</beans>
  • applicationContext-tx-jta.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
	http://www.springframework.org/schema/aop
	http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
	http://www.springframework.org/schema/tx
	http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
	<!--Atomikos JTA 事务 begin-->
	<aop:aspectj-autoproxy />
	<bean id="userTransactionService" 
		class="com.atomikos.icatch.config.UserTransactionServiceImp"
		init-method="init" destroy-method="shutdownForce">
		<constructor-arg>
			<!-- IMPORTANT: specify all Atomikos properties here -->
			<props>
				<prop key="com.atomikos.icatch.service">
					com.atomikos.icatch.standalone.UserTransactionServiceFactory
 				</prop>
			</props>
		</constructor-arg>
	</bean>
	<!--
		Construct Atomikos UserTransactionManager, needed to configure Spring
	-->
	<bean id="AtomikosTransactionManager" 
		class="com.atomikos.icatch.jta.UserTransactionManager"
		init-method="init" destroy-method="close" depends-on="userTransactionService">
		<!--
			IMPORTANT: disable startup because the userTransactionService above
			does this
		-->
		<property name="startupTransactionService" value="false" />
		<!--
			when close is called, should we force transactions to terminate or
			not?
		-->
		<property name="forceShutdown" value="false" />
	</bean>
	<!--
		Also use Atomikos UserTransactionImp, needed to configure Spring
	-->
	<bean id="AtomikosUserTransaction" 
		class="com.atomikos.icatch.jta.UserTransactionImp"
		depends-on="userTransactionService">
		<property name="transactionTimeout" value="300" />
	</bean>
	<!--
		Configure the Spring framework to use JTA transactions from Atomikos
	-->
	<bean id="JtaTransactionManager"
		class="org.springframework.transaction.jta.JtaTransactionManager"
		depends-on="userTransactionService">
		<property name="transactionManager" ref="AtomikosTransactionManager" />
		<property name="userTransaction" ref="AtomikosUserTransaction" />
	</bean>
	<tx:annotation-driven transaction-manager="JtaTransactionManager" />
	<!-- 设置userService -->
	<bean id="userService" class="casside.service.UserServiceImpl">
		<property name="ssoJDBC" ref="ssoJDBC" />
		<property name="tshopJDBC" ref="tshopJDBC" />
		<property name="discuzJDBC" ref="discuzJDBC" />
		<property name="mailService" ref="mailService" />
	</bean>
</beans>

java代码

package casside.service;

import java.util.Calendar;
import java.util.Map;

import org.apache.commons.lang.RandomStringUtils;
import org.springframework.transaction.annotation.Transactional;

import casside.exception.CassideException;
import casside.exception.MailSendException;
import casside.exception.UserValidationException;
import casside.jdbc.SSOCenterJDBC;
import casside.jdbc.SSORegisterJDBCHandler;
import casside.tools.mail.TouserFindPasswordVO;
import casside.util.MD5;

public class UserServiceImpl implements UserService {
	private SSORegisterJDBCHandler tshopJDBC;
	private SSORegisterJDBCHandler discuzJDBC;
	private SSOCenterJDBC ssoJDBC;
	private IMailService mailService;

	@Transactional(readOnly = false)
	public void registerUser(Map params)
			throws CassideException {
		String username = (String) params.get("username");
		if (existUser(username)) {
			throw new UserValidationException();
		}
		ssoJDBC.registerUser(params);
		discuzJDBC.registerUser(params);
		tshopJDBC.registerUser(params);
	}

	public SSORegisterJDBCHandler getTshopJDBC() {
		return tshopJDBC;
	}

	public void setTshopJDBC(SSORegisterJDBCHandler tshopJDBC) {
		this.tshopJDBC = tshopJDBC;
	}

	public SSORegisterJDBCHandler getDiscuzJDBC() {
		return discuzJDBC;
	}

	public void setDiscuzJDBC(SSORegisterJDBCHandler discuzJDBC) {
		this.discuzJDBC = discuzJDBC;
	}

	public SSOCenterJDBC getSsoJDBC() {
		return ssoJDBC;
	}

	public void setSsoJDBC(SSOCenterJDBC ssoJDBC) {
		this.ssoJDBC = ssoJDBC;
	}

	@Transactional(readOnly = true)
	public boolean existUser(String username) {
		int count = ssoJDBC.existUser(username);
		if (count == 0) {
			return false;
		}
		return true;
	}

	@Transactional(readOnly = true)
	public boolean existEmail(String email) {
		int count = ssoJDBC.existEmail(email);
		if (count == 0) {
			return false;
		}
		return true;
	}

	@Transactional(readOnly = false)
	public void updatePwd(String userName, String email)
			throws CassideException {
		if (existUser(userName)) {
			Map map = findUserByUserName(userName);
			if (map.get("email").equals(email)) {
				String newPwd = RandomStringUtils.randomAlphanumeric(6);
				String destPwd = MD5.MD5(newPwd);
				ssoJDBC.updatePwd(userName, destPwd);
				discuzJDBC.updatePwd(userName, destPwd);
				tshopJDBC.updatePwd(userName, destPwd);

				TouserFindPasswordVO vo = new TouserFindPasswordVO();
				vo.setNewPassword(newPwd);
				vo.setSendTime(Calendar.getInstance().getTime());
				vo.setUserName(userName);
				vo.setSiteName("Tshop");
				vo.setToMail(email);
				try {
					mailService.sendeMailTouserFindPassword(vo);
				} catch (Exception e) {
					e.printStackTrace();
					throw new MailSendException();
				}
			} else {
				throw new UserValidationException();
			}
		} else {
			throw new UserValidationException();
		}
	}

	@Transactional(readOnly = true)
	public Map findUserByUserName(String userName)
			throws CassideException {
		return ssoJDBC.findUserByUserName(userName);
	}

	public IMailService getMailService() {
		return mailService;
	}

	public void setMailService(IMailService mailService) {
		this.mailService = mailService;
	}
}

期间遇到的问题

1、Mysql版本太低不支持XA,必须使用Mysql XADataSource(mysql>=5.0, Connector/J>=5.0才可以支持XADatasource<
2、cas 内部使用的是注解事务配置,我开始没有了解到,使用TransactionProxyFactoryBean作为UserService的配置处理,一个spring控制领域内出现了两种不同的 声音,让我纠结了好久,最后在翻阅源码的时候才惊醒,特此记下。

文章作者: iitshare
本文地址:http://www.iitshare.com/cas-integration-atomikos-distributed-transaction.html
版权所有 © 转载时必须以链接形式注明作者和原始出处!

更多

2 Responses to “cas整合Atomikos分布式事务源码示例”

  1. #1 Noerhil 回复 | 引用 Post:2012-10-20 23:13

    haha AND me. $AuthenticationProviderCacheResolver#0 : Instantiation of bean failed; ntseed exception is java.security.AccessControlException: access denied (java.lang.reflect.ReflectPermission suppressAccessChecks)Permission,Permission again I know,i gotta update the version too.

发表评论