我们有一个后台的项目,是用了好几个数据库链接,以前为了方便就用了多数据源的那种配置方法(不要吐槽呢),但是现在用户多了,经常会遇到不同数据库插入失败的问题。在我们当初使用这种架构的时候,就知道如果用这个框架会存在跨数据库事务无法处理,项目急呢。
现在有时间了,就准备处理一下这个遗留的bug,因为我们现在的框架用的是spring3.2+mybatis3.0,刚开始准备用jta中jotm来实现,结果在junitTest一直报org.springframework.transaction.jta.JotmFactoryBean 这个类找不到,后来查了一下spring的官方文档,原来Spring 3以上版本,去掉了JotmFactoryBean类,不能通过集成Jotm实现Jta功能,哎,只能怪我急攻心切,当时多查查文档也就不至于这样了,有多学了点知识呢,嘿嘿。现在只好用另一种形式了:atomikos,幸亏基本上都差不多呢。
其实在写这个系统的时候,我也在网上找了不少资料,但是大部分的都是只用了spring2,或者mybatis3,于是我就想写一个基于spring3+mybatis3的系统,方便大家互相学习呢,我第一次写博客,语言逻辑有问题的还请大家多多见谅呢。
1.数据库
在这里我用的是mysql的,新建了两个数据库分别为da1,da2
create database da1;
use da1;
CREATE TABLE `table1` (
`id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
create database da2;
use da2;
CREATE TABLE `table2` (
`id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2.系统
2.1项目包截图
2.2基本类
在这里我只展示了其中的一个model实体类Table1.java,Table2和这个差不多
/**
* @文件名称: Tabel1.java
* @类路径: com.kasiait.jta.da1.model
* @描述: TODO
* @作者:kasiait521
* @时间:2014-04-24
*/
package com.kasiait.jta.da1.model;
import com.kasiait.jta.common.model.CommonPage;
public class Tabel1 {
private Integer id;//自增长id
private CommonPage page;//这个是我们系统中用来分页的,大家可以忽略呢
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public CommonPage getPage() {
return page;
}
public void setPage(CommonPage page) {
this.page = page;
}
}
在这里我只展示了其中的一个mapper类Tabel1Mapper.java,Table2Mapper和这个差不多
/**
* @文件名称: Tabel1Mapper.java
* @类路径: com.kasiait.jta.da1.mapper
* @描述: TODO
* @作者:kasiait521
* @时间:2014-04-24
*/
package com.kasiait.jta.da1.mapper;
import java.util.List;
import com.kasiait.jta.da1.model.Tabel1;
public interface Tabel1Mapper {
public void insertTabel1(Tabel1 t);
public List<Tabel1> listPageAll(Tabel1 t);
}
service类Tabel1Service.java
/**
* @文件名称: Tabel1Service.java
* @类路径: com.kasiait.jta.da1.service
* @描述: TODO
* @作者:kasiait521
* @时间:2014-04-24
*/
package com.kasiait.jta.da1.service;
import java.util.List;
import com.kasiait.jta.da1.model.Tabel1;
public interface Tabel1Service {
public void insertTabel1(Tabel1 t);
public List<Tabel1> listPageAll(Tabel1 t);
}
serviceImpl类
/**
* @文件名称: Tabel1ServiceImpl.java
* @类路径: com.kasiait.jta.da1.service.impl
* @描述: TODO
* @作者:kasiait521
* @时间:2014-04-24
*/
package com.kasiait.jta.da1.service.impl;
import java.util.List;
import com.kasiait.jta.da1.mapper.Tabel1Mapper;
import com.kasiait.jta.da1.model.Tabel1;
import com.kasiait.jta.da1.service.Tabel1Service;
public class Tabel1ServiceImpl implements Tabel1Service {
private Tabel1Mapper tabel1Mapper;//spring注入,由于涉及到底层数据源关系,所以我就在配置文件中写的,还希望注解狂
public Tabel1Mapper getTabel1Mapper() {
return tabel1Mapper;
}
public void setTabel1Mapper(Tabel1Mapper tabel1Mapper) {
this.tabel1Mapper = tabel1Mapper;
}
@Override
public void insertTabel1(Tabel1 t) {
// TODO Auto-generated method stub
tabel1Mapper.insertTabel1(t);
}
@Override
public List<Tabel1> listPageAll(Tabel1 t) {
// TODO Auto-generated method stub
return tabel1Mapper.listPageAll(t);
}
}
/**
* @文件名称: Tabel2ServiceImpl.java
* @类路径: com.kasiait.jta.da2.service.impl
* @描述: TODO
* @作者:kasiait521
* @时间:2014-04-24
*/
package com.kasiait.jta.da2.service.impl;
import com.kasiait.jta.da1.mapper.Tabel1Mapper;
import com.kasiait.jta.da1.model.Tabel1;
import com.kasiait.jta.da2.mapper.Tabel2Mapper;
import com.kasiait.jta.da2.model.Tabel2;
import com.kasiait.jta.da2.service.Tabel2Service;
public class Tabel2ServiceImpl implements Tabel2Service {
private Tabel2Mapper tabel2Mapper;// 数据源2
private Tabel1Mapper tabel1Mapper;// 数据源1同时注入到这个impl类
public Tabel1Mapper getTabel1Mapper() {
return tabel1Mapper;
}
public void setTabel1Mapper(Tabel1Mapper tabel1Mapper) {
this.tabel1Mapper = tabel1Mapper;
}
public Tabel2Mapper getTabel21Mapper() {
return tabel2Mapper;
}
public void setTabel2Mapper(Tabel2Mapper tabel2Mapper) {
this.tabel2Mapper = tabel2Mapper;
}
@Override
public void insertTabel2(Tabel2 t) {// 在这里进行处理的都会在一个事物里
// TODO Auto-generated method stub
Tabel2 t2 = new Tabel2();
t2.setId(10);
Tabel1 t1 = new Tabel1();
t1.setId(10);
tabel2Mapper.insertTabel2(t2);// 向数据库da2里插入数据
tabel1Mapper.insertTabel1(t1);// 向数据库da1里插入数据
}
}
controller类
@Controller
@RequestMapping("/kaisiat")
public class FrontIndexController {
@Autowired
private Tabel1Service tabel1Service;
@Autowired
private Tabel2Service tabel2Service;
/**
*
* @描述: 网站入口
* @作者: kasiait521
* @日期:2014-04-24
* @修改内容
* @参数: @return
* @return String
* @throws
*/
@RequestMapping(value = "index", method = RequestMethod.GET)
public ModelAndView index(HttpServletRequest request,
HttpServletResponse response, HttpSession session) {
// 在这里我只是模拟了操作,但并未转到具体的页面中,只是一个测试而已;
// 如果系统配置好了,第一次执行时没有问题的,第二次的时候,你可以把tabel2Service中的实现类的da2改一下插入的数据;
// 因为数据库中默认的都是主键所以都是唯一值,这样如果没有事物的话,da1是插入失败,da2是插入成功的;
// 所以正好测试一下,结果肯定是da1和da2都插入失败了,因为事物回滚了。
ModelAndView mv = new ModelAndView();
Tabel2 t2 = new Tabel2();
tabel2Service.insertTabel2(t2);
return mv;
}
}
3.配置文件
mybatis/da1中的sqlmap-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD SQL Map Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias type="com.kasiait.jta.da1.model.Tabel1"
alias="Tabel1" />
</typeAliases>
<--系统分页,可忽略-->
<plugins>
<plugin interceptor="com.kasiait.framework.plugin.PagePlugin">
<property name="dialect" value="mysql" />
<property name="pageSqlId" value=".*listPage.*" />
</plugin>
</plugins>
<mappers>
<mapper resource="mybatis/da1/Tabel1.xml" />
</mappers>
</configuration>
mybatis/da1中的Tabel1.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kasiait.jta.da1.mapper.Tabel1Mapper">
<!-- 从数据库到java实体对象映射声明 -->
<resultMap type="Tabel1" id="Tabel1ResultMap">
<result column="id" property="id" />
</resultMap>
<insert id="insertTabel1" parameterType="Tabel1">
insert into table1 (id) values (#{id})
</insert>
<select id="listPageAll" resultMap="Tabel1ResultMap">
select * from table1
</select>
</mapper>
ApplicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd"
default-lazy-init="true">
<import resource="ApplicationContext-service.xml" />
<!-- spring atomikos -->
<!-- mysql da1数据源 这里没有从配置文件中获取,大家可以修改一下呢 -->
<bean id="mysqlDA1" class="com.atomikos.jdbc.AtomikosDataSourceBean"
init-method="init" destroy-method="close">
<description>mysql first datasource</description>
<property name="uniqueResourceName">
<value>mysql_da1</value>
</property>
<property name="xaDataSourceClassName">
<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://127.0.0.1:3306/da1
</prop>
</props>
</property>
<property name="poolSize" value="10" />
</bean>
<!-- mysql da2数据源 -->
<bean id="mysqlDA2" class="com.atomikos.jdbc.AtomikosDataSourceBean"
init-method="init" destroy-method="close">
<description>mysql second datasource</description>
<property name="uniqueResourceName">
<value>mysql_da2</value>
</property>
<property name="xaDataSourceClassName">
<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://127.0.0.1:3306/da2
</prop>
</props>
</property>
<property name="poolSize" value="10" />
</bean>
<!-- atomikos事务经管器 -->
<bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"
init-method="init" destroy-method="close">
<description>UserTransactionManager</description>
<property name="forceShutdown">
<value>true</value>
</property>
</bean>
<bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
<property name="transactionTimeout" value="300" />
</bean>
<!-- spring 事务经管器 -->
<bean id="springTransactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager">
<ref bean="atomikosTransactionManager" />
</property>
<property name="userTransaction">
<ref bean="atomikosUserTransaction" />
</property>
</bean>
<!-- 通知配置 -->
<tx:advice id="txAdvice" transaction-manager="springTransactionManager">
<tx:attributes>
<tx:method name="delete*" rollback-for="Exception" />
<tx:method name="save*" rollback-for="Exception" />
<tx:method name="update*" rollback-for="Exception" />
<tx:method name="*" read-only="true" rollback-for="Exception" />
</tx:attributes>
</tx:advice>
<!-- 事务切面配置 -->
<aop:config>
<aop:pointcut id="serviceOperation" expression="execution(* *..service*..*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceOperation" />
</aop:config>
<!-- 第一个数据源 -->
<bean id="sqlSessionFactory1" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="mysqlDA1" />
<property name="configLocation" value="classpath:mybatis/da1/sqlmap-config.xml" />
</bean>
<!-- 第二个数据源 -->
<bean id="sqlSessionFactory2" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="mysqlDA2" />
<property name="configLocation" value="classpath:mybatis/da2/sqlmap-config.xml" />
</bean>
</beans>
ApplicationContext-mvc.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:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!--mvc:annotation-driven/ -->
<context:component-scan
base-package="com.kasiait.jta, com.kasiait.jta.da1, com.kasiait.jta.da2" />
<!-- 使用方法级拦截器 -->
<bean id="methodInvokerIntercepterManager"
class="org.springframework.web.servlet.mvc.annotation.MethodInvokerIntercepterManager">
<property name="intercepters">
<list>
<bean class="com.kasiait.framework.interceptor.PrivilegeIntercepter" />
</list>
</property>
</bean>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="methodInvokerIntercepterManager" ref="methodInvokerIntercepterManager" />
</bean>
<!-- springmvc视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
<!-- 处理JSON数据转换的 -->
<bean id="mappingJacksonHttpMessageConverter"
class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<!-- 为了处理返回的JSON数据的编码,默认是ISO-88859-1的,这里把它设置为UTF-8,解决有乱码的情况 -->
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
</list>
</property>
</bean>
</beans>
ApplicationContext-service.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 因为没有用注解,所以只好在这里配置了,我觉得这样以后维护起来比较方便呢 -->
<bean id="tabel1Mapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="sqlSessionFactory" ref="sqlSessionFactory1" />
<property name="mapperInterface" value="com.kasiait.jta.da1.mapper.Tabel1Mapper" />
</bean>
<bean id="tabel2Mapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="sqlSessionFactory" ref="sqlSessionFactory2" />
<property name="mapperInterface" value="com.kasiait.jta.da2.mapper.Tabel2Mapper" />
</bean>
<bean id="tabel1Service" class="com.kasiait.jta.da1.service.impl.Tabel1ServiceImpl">
<property name="tabel1Mapper" ref="tabel1Mapper" />
</bean>
<bean id="tabel2Service" class="com.kasiait.jta.da2.service.impl.Tabel2ServiceImpl">
<property name="tabel2Mapper" ref="tabel2Mapper" />
<property name="tabel1Mapper" ref="tabel1Mapper" />
</bean>
</beans>
jta.properties jta的配置文件
com.atomikos.icatch.service=com.atomikos.icatch.standalone.UserTransactionServiceFactory
com.atomikos.icatch.console_file_name=tm.out
com.atomikos.icatch.log_base_name=tmlog
com.atomikos.icatch.tm_unique_name=tm
com.atomikos.icatch.console_log_level=INFO
到这里就基本结束了,刚开始写博客,没想到写了好几个小时,不过还是蛮幸福的,大家一起分享,一起进步!
下面是这个项目中atomikos的jar包,其他的spring3.2+mybatis3的包,网上有很多呢,我就不上传了
http://download.csdn.net/detail/kasiait521/7249723