当前位置: 首页 > 工具软件 > MiniDao > 使用案例 >

MiniDao实现分库事务

呼延运恒
2023-12-01

机缘巧合接触到国产轻量级持久层开源框架MiniDao,了解该框架也有一段时间了,但是一直都没有深入的研究底层代码,最近花了一些时间研究了一下实现原理。MiniDao官方提供的版本不支持分布式事务,我在官方的MiniDao-pe-1.6版本上实现了该功能,满足了现有项目的需求。以下具体分析该功能的实现:

步骤:

一、现有的问题

二、深入解析源码,实现分库事务功能

三、单元测试

一、分析现有的问题
在MiniDao使用的过程中,官方提供的单元测试类中可以实现动态数据源的切换,但是在service层的业务代码中就无法实现动态数据源切换(设置动态数据源不生效),经过分析后发现,该问题出现在了事务上。当为service层的方法设置事务后,就无法在方法的内部动态的修改数据源了,因为,事务在开始执行该方法时开启,到方法执行完成关闭,在这期间动态修改的数据源不生效。问题出现在,在一个事务中无法动态的修改数据源。

二、深入解析源码
MiniDao的核心实现类是MiniDaoHandler,该类是一个代理类,为dao接口生成代理类。
MiniDaoHandler.java类中核心的方法是:
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
1、设置数据源,该功能是我为了实现分布式事务扩展的,官方代码中没有
	setDynamicDataSource(dataSourceType);
2、状态SQL模板所需的参数
	templateSql = installDaoMetaData(pageSetting, method, sqlParamsMap, args);
3、解析SQL模板,返回可执行SQL
	String executeSql = parseSqlTemplate(method, templateSql, sqlParamsMap);
4、组装SQL占位符参数
	Map<String, Object> sqlMap = installPlaceholderSqlParam(executeSql, sqlParamsMap);
5、执行SQL,获取SQL执行返回值
	Object returnObj = getReturnMinidaoResult(dbType, pageSetting, method, executeSql, sqlMap);
该方法的完整代码如下:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	// 获取数据源类型
	DataSourceType dataSourceType = DataSourceContextHolder.getDataSourceType();
	// 返回结果
	Object returnObj = null;
	// SQL模板
	String templateSql = null;
	// SQL模板参数
	Map<String, Object> sqlParamsMap = new HashMap<String, Object>();
	// 分页参数
	MiniDaoPage pageSetting = new MiniDaoPage();
	
	// 设置数据源
	setDynamicDataSource(dataSourceType);
	// Step.0 判断是否是Hiber实体维护方法,如果是执行Hibernate方式实体维护
//	Map<String, Object> rs = new HashMap<String, Object>();

	// Step.1装载SQL模板,所需参数
	templateSql = installDaoMetaData(pageSetting, method, sqlParamsMap, args);

	// Step.3解析SQL模板,返回可执行SQL
	String executeSql = parseSqlTemplate(method, templateSql, sqlParamsMap);

	// Step.4 组装SQL占位符参数
	Map<String, Object> sqlMap = installPlaceholderSqlParam(executeSql, sqlParamsMap);

	// Step.5 获取SQL执行返回值
	try {
		returnObj = getReturnMinidaoResult(dbType, pageSetting, method, executeSql, sqlMap);
	} catch (Exception e) {
		returnObj = null;
		if(e instanceof EmptyResultDataAccessException){
			//数据查询为空,不抛出Spring异常
		}else{
			e.printStackTrace();
			throw e;
		}
	}
	if (showSql) {
//		System.out.println("MiniDao-SQL:\n\n" + executeSql);
		logger.info("MiniDao-SQL:\n\n" + executeSql);
	}
	return returnObj;
}


为了实现在一个事务内动态设置数据源,在MiniDaoHandler类中新增如下代码:
/**
 * 动态设置数据源
 * @param dataSourceType
 */
@SuppressWarnings("static-access")
private void setDynamicDataSource(DataSourceType dataSourceType) {
	if(dataSourceType != null) {
		namedParameterJdbcTemplate.setDataSource((DataSource)appContext.getBean(dataSourceType.toString()));
		jdbcTemplate.setDataSource((DataSource)appContext.getBean(dataSourceType.toString()));
	}
}
该方法的作用是,根据设置数据源的类型,动态的修改namedParameterJdbcTemplate和jdbcTemplate的dataSource值。NamedParameterJdbcTemplate类是重写spring的,内部增加了setDataSource方法,在applicationContext.xml配置文件中也是配置的该bean。
<!-- JDBC配置,在MiniDaoHandler中需要使用该配置,并且根据数据源类型动态修改dataSource -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
	<property name="dataSource">
		<ref bean="dataSource" />
	</property>
</bean>
<!-- JDBC 占位符配置,在MiniDaoHandler中需要使用该配置,并且根据数据源类型动态修改dataSource -->
<bean id="namedParameterJdbcTemplate"
	class="org.jeecgframework.minidao.datasource.NamedParameterJdbcTemplate">
	<constructor-arg ref="dataSource" />
</bean>
WebApplicationContext类也是新扩展的类,用于获取spring容器中创建的bean对象,官方代码中没有。
package org.jeecgframework.minidao.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class WebApplicationContext implements ApplicationContextAware{
	
	protected static ApplicationContext appContext;

	public void setApplicationContext(ApplicationContext app) throws BeansException {
		this.appContext = app;
	}

	public static ApplicationContext getAppContext() {
		return (appContext);
	}
	
	public static Object getBean(String beanName) {
		return appContext.getBean(beanName);
	}


}
该类需要在applicationContext.xml中进行配置:
<!-- spring容器工具类,必须配置 -->
<bean id="appContext" class="org.jeecgframework.minidao.util.WebApplicationContext" />

applicationContext.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:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:util="http://www.springframework.org/schema/util"
	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:task="http://www.springframework.org/schema/task"
	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/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
           http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd
           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd"
	default-autowire="byName" default-lazy-init="false">
	<!-- 使用AspectJ方式配置AOP -->
	<aop:aspectj-autoproxy />

	<!-- 使用annotation 自动注册bean,并检查@Required,@Autowired的属性已被注入 -->
	<context:component-scan base-package="examples.*" />
	<!-- 引入数据库属性文件 -->
	<context:property-placeholder location="classpath:dbconfig.properties" />
	
	<!-- 配置主-从数据源信息 -->
	<!-- com.atomikos.jdbc.nonxa.AtomikosNonXADataSourceBean -->
	<bean id="abstractXADataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean"
		init-method="init" destroy-method="close" abstract="true">
		<!-- SQLErrorCodes loaded: [DB2, Derby, H2, HSQL, Informix, MS-SQL, MySQL, Oracle, PostgreSQL, Sybase, Hana] -->
		<property name="xaDataSourceClassName" value="${jdbc.xaDataSourceClassName}" />
		<property name="poolSize" value="10" />
		<property name="minPoolSize" value="10" />
		<property name="maxPoolSize" value="30" />
		<property name="borrowConnectionTimeout" value="60" />
		<property name="reapTimeout" value="20" />
		<property name="maxIdleTime" value="60" />
		<property name="maintenanceInterval" value="60" />
		<property name="loginTimeout" value="60" />
		<property name="testQuery" value="${validationQuery}" />
	</bean>
	<bean id="dataSource_jeecg" parent="abstractXADataSource">
		<property name="uniqueResourceName" value="masterDB" />
		<property name="xaProperties">
			<props>
				<prop key="driverClassName">${jdbc.driverClassName}</prop>
				<prop key="url">${master.jdbc.url}</prop>
				<prop key="password">${jdbc.password}</prop>
				<!-- <prop key="user">${jdbc.username}</prop> --> <!-- mysql -->
				<prop key="username">${jdbc.username}</prop>   <!-- durid -->
				<prop key="initialSize">2</prop>
				<prop key="maxActive">20</prop> <!-- 若不配置则代码执行"{dataSource-1} inited"此处停止 -->
				<prop key="minIdle">0</prop>
				<prop key="maxWait">60000</prop>
				<prop key="validationQuery">${validationQuery}</prop>
				<prop key="testOnBorrow">false</prop>
				<prop key="testOnReturn">false</prop>
				<prop key="testWhileIdle">true</prop>
				<prop key="removeAbandoned">true</prop>
				<prop key="removeAbandonedTimeout">1800</prop>
				<prop key="logAbandoned">true</prop>
				<prop key="filters">mergeStat</prop>
			</props>
		</property>
	</bean>
	<bean id="mapdataSource" parent="abstractXADataSource">
		<property name="uniqueResourceName" value="slaveDB" />
		<property name="xaProperties">
			<props>
				<prop key="driverClassName">${jdbc.driverClassName}</prop>
				<prop key="url">${slave.jdbc.url}</prop>
				<prop key="password">${jdbc.password}</prop>
				<!-- <prop key="user">${jdbc.username}</prop> -->
				<prop key="username">${jdbc.username}</prop>
				<prop key="initialSize">2</prop>
				<prop key="maxActive">20</prop>
				<prop key="minIdle">0</prop>
				<prop key="maxWait">60000</prop>
				<prop key="validationQuery">${validationQuery}</prop>
				<prop key="testOnBorrow">false</prop>
				<prop key="testOnReturn">false</prop>
				<prop key="testWhileIdle">true</prop>
				<prop key="removeAbandoned">true</prop>
				<prop key="removeAbandonedTimeout">1800</prop>
				<prop key="logAbandoned">true</prop>
				<prop key="filters">mergeStat</prop>
			</props>
		</property>
	</bean>
	
	<!-- JDBC配置,在MiniDaoHandler中需要使用该配置,并且根据数据源类型动态修改dataSource -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource">
			<ref bean="dataSource" />
		</property>
	</bean>

	<!-- JDBC 占位符配置,在MiniDaoHandler中需要使用该配置,并且根据数据源类型动态修改dataSource -->
	<bean id="namedParameterJdbcTemplate"
		class="org.jeecgframework.minidao.datasource.NamedParameterJdbcTemplate">
		<constructor-arg ref="dataSource" />
	</bean>
	
	<!-- atomikos事务管理器 -->  
    <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close">  
        <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="atomikosTransactionManager" />  
        <property name="userTransaction" ref="atomikosUserTransaction" />  
        <!-- 必须设置,否则程序出现异常 JtaTransactionManager does not support custom isolation levels by default -->  
        <property name="allowCustomIsolationLevels" value="true"/>   
    </bean>

	<!-- 数据源集合,可以配置多个数据源,不能在一个事务内动态设置数据源,否则不起作用 -->
	<bean id="dataSource" class="org.jeecgframework.minidao.datasource.DynamicDataSource">
		<property name="targetDataSources">
			<!-- 注意:entry实体的key值与value-ref值要保持一致,否则在动态切换数据源时不起作用 -->
			<map key-type="org.jeecgframework.minidao.datasource.DataSourceType">
				<entry key="dataSource_jeecg" value-ref="dataSource_jeecg" />
				<entry key="mapdataSource" value-ref="mapdataSource" />
			</map>
		</property>
		<!-- 默认数据源 dataSource_jeecg -->
		<property name="defaultTargetDataSource" ref="dataSource_jeecg" />
	</bean>
	
	<!-- 事务管理 -->
	<tx:advice id="springTxAdvice" transaction-manager="springTransactionManager">
		<tx:attributes>
			<tx:method name="save*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
			<tx:method name="add*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
			<tx:method name="create*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
			<tx:method name="insert*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
			<tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
			<tx:method name="delete*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
			<tx:method name="*" propagation="REQUIRED" read-only="true" />
		</tx:attributes>
	</tx:advice>

	<aop:config proxy-target-class="true">
		<aop:advisor advice-ref="springTxAdvice" pointcut="execution(* examples..service..*.*(..))" />
	</aop:config>

	<!-- spring容器工具类,必须配置 -->
	<bean id="appContext" class="org.jeecgframework.minidao.util.WebApplicationContext" />
	<!-- minidao拦截器 -->
	<bean name="minidaoInterceptor" class="org.jeecgframework.minidao.aspect.MinidaoInterceptor"></bean>
	<!-- MiniDao扫描类 -->
	<bean class="org.jeecgframework.minidao.factory.MiniDaoBeanScannerConfigurer">
		<!-- 是使用什么字母做关键字Map的关键字 默认值origin 即和sql保持一致,lower小写(推荐),upper 大写 -->
		<property name="keyType" value="lower"></property>
		<!-- 格式化sql -->
		<property name="formatSql" value="true"></property>
		<!-- 输出sql -->
		<property name="showSql" value="true"></property>
		<!-- 数据库类型 -->
		<property name="dbType" value="mysql"></property>
		<!-- dao地址,配置符合spring方式 -->
		<property name="basePackage" value="examples.dao"></property>
		<!-- 使用的注解,默认是Minidao,推荐 Repository -->
		<property name="annotation" value="org.springframework.stereotype.Repository"></property>
		<!-- Minidao拦截器配置 -->
		<property name="emptyInterceptor" ref="minidaoInterceptor"></property>
	</bean>
	
</beans>

dbconfig.properties配置文件:
#mysql \u6570\u636e\u5e93\u8fde\u63a5\u53c2\u6570
validationQuery=SELECT 1

jdbc.initialSize=5  
jdbc.maxActive=20  
jdbc.maxWait=60000  
jdbc.poolPreparedStatements=false  
jdbc.poolMaximumIdleConnections=0
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.xaDataSourceClassName=com.alibaba.druid.pool.xa.DruidXADataSource
#1.tms business.  2.The db level optimization,data concurrency,desirable.  
master.jdbc.url=jdbc:mysql://localhost:3306/master?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
slave.jdbc.url=jdbc:mysql://localhost:3306/slave?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
jdbc.username=root
jdbc.password=root

#jta startup param
com.atomikos.icatch.service=com.atomikos.icatch.standalone.UserTransactionServiceFactory  
com.atomikos.icatch.console_file_name = tx.out.log  
com.atomikos.icatch.log_base_name = txlog  
com.atomikos.icatch.tm_unique_name = com.atomikos.spring.jdbc.tm  
com.atomikos.icatch.console_log_level=DEBUG


三、单元测试
Employee.java
package examples.entity;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;

/**
 * 实体定义规则 字段不允许存在基本类型,必须都是包装类型(因为基本类型有默认值) 基本数据类型 包装类 byte Byte boolean Boolean
 * short Short char Character int Integer long Long float Float double Double
 * 
 * @author Administrator
 *
 */
public class Employee implements Serializable {
	private static final long serialVersionUID = 1L;
	private String id;
	private String name;
	private String empno;
	private Integer age;
	private BigDecimal salary;
	private Date birthday;
	private Date createDate;
	private String createBy;

	public Date getCreateDate() {
		return createDate;
	}

	public void setCreateDate(Date createDate) {
		this.createDate = createDate;
	}

	public String getCreateBy() {
		return createBy;
	}

	public void setCreateBy(String createBy) {
		this.createBy = createBy;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getEmpno() {
		return empno;
	}

	public void setEmpno(String empno) {
		this.empno = empno;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	public BigDecimal getSalary() {
		return salary;
	}

	public void setSalary(BigDecimal salary) {
		this.salary = salary;
	}

	public Date getBirthday() {
		return birthday;
	}

	public void setBirthday(Date birthday) {
		this.birthday = birthday;
	}

	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + ", empno=" + empno + ", age=" + age + ", salary=" + salary
				+ ", birthday=" + birthday + ", createDate=" + createDate + ", createBy=" + createBy + "]";
	}

}

EmployeeDao.java
package examples.dao;

import java.util.List;
import java.util.Map;

import org.jeecgframework.minidao.annotation.Arguments;
import org.jeecgframework.minidao.annotation.Param;
import org.jeecgframework.minidao.annotation.ResultType;
import org.jeecgframework.minidao.annotation.Sql;
import org.jeecgframework.minidao.pojo.MiniDaoPage;
import org.springframework.stereotype.Repository;

import examples.entity.Employee;

@Repository
public interface EmployeeDao {

	/**
	 * 返回记录总数
	 * @return
	 */
	@Sql("select count(*) from employee")
	Integer getCount();
	
	/**
	 * 插入数据
	 * @param employee
	 * @return
	 */
	@Arguments("employee")
	Integer insertEmployee(Employee employee);
	
	/**
	 * 更新数据
	 * @param employee
	 * @return
	 */
	Integer updateEmployee(@Param("employee") Employee employee);
	
	/**
	 * 删除数据
	 * @param employee
	 * @return
	 */
	@Arguments({"employee"})
	Integer deleteEmployee(Employee employee);
	
	/**
	 * 查询返回单个Java对象
	 * @param id
	 * @return
	 */
	@Arguments({"id"})
	Employee getEmployeeById(String id);
	
	/**
	 * 返回List<Object> 类型的全部数据
	 * @return
	 */
	List<Employee> getAllEmployee();
	
	/**
	 * 查询返回单个Map对象
	 * @param empno
	 * @param name
	 * @return
	 */
	Map<String, Object> getMap(@Param("empno") String empno, @Param("name") String name);
	
	/**
	 * 返回List<Map> 类型全部数据
	 * @return
	 */
	List<Map<String, Object>> getAllListMap();
	
	/**
	 * 通用分页方法,支持(oracle、mysql、SqlServer、postgresql)
	 * @param employee
	 * @param page
	 * @param rows
	 * @return
	 */
	@Arguments({"employee", "page", "rows"})
	@ResultType(Employee.class)
	MiniDaoPage<Employee> getPageEmployees(Employee employee, int page, int rows);
	
}

EmployeeService.java
package examples.service;

import org.jeecgframework.minidao.datasource.DataSourceContextHolder;
import org.jeecgframework.minidao.datasource.DataSourceType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import examples.dao.EmployeeDao;
import examples.entity.Employee;

@Service
public class EmployeeService {

	@Autowired
	private EmployeeDao employeeDao;
	
	public Employee selectEmployeeById(String id) {
		DataSourceContextHolder.setDataSourceType(DataSourceType.mapdataSource);
		return employeeDao.getEmployeeById(id);
	}
	
	public boolean registerMember(Employee employee1, Employee employee2) {
		boolean resRegister = false;
		try {
			employeeDao.insertEmployee(employee1);
			DataSourceContextHolder.setDataSourceType(DataSourceType.mapdataSource);
//			employee2.setId(null);
			employeeDao.insertEmployee(employee2);
			resRegister = true;
		} catch(Exception e) {
			e.printStackTrace();
			throw e;
		}
		return resRegister;
	}
	
}

EmployeeDao_deleteEmployee.sql
delete from employee 
where 1=1
<#if employee.id?exists>
	and id = :employee.id
</#if>
<#if employee.name?exists>
	and name = :employee.name
</#if>
<#if employee.age?exists>
	and age = :employee.age
</#if>
<#if employee.salary?exists>
	and salary = :employee.salary
</#if>
<#if employee.empno?exists>
	and empno = :employee.empno
</#if>
<#if employee.birthday?exists>
	and birthday = :employee.birthday
</#if>

EmployeeDao_getAllEmployee.sql
select * from employee


EmployeeDao_getAllListMap.sql
select * from employee

EmployeeDao_getEmployeeById.sql
select * from employee where id=:id

EmployeeDao_getMap.sql
select * from employee
where 
	empno = :empno 
	and name = :name

EmployeeDao_getPageEmployees_conditions.sql
<#if employee.id?exists>
	and id = '${employee.id}' 
</#if>
<#if employee.name?exists>
	and name = '${employee.name}'
</#if>
<#if employee.age?exists>
	and age = ${employee.age}
</#if>
<#if employee.salary?exists>
	and salary = ${employee.salary}
</#if>
<#if employee.empno?exists>
	and empno = '${employee.empno}'
</#if>
<#if employee.birthday?exists>
	and birthday = :employee.birthday
</#if>

EmployeeDao_getPageEmployees.sql
select * from (SELECT * FROM employee where 1=1
<#include "EmployeeDao_getPageEmployees_conditions.sql">
) a 
where 1=1
<#include "EmployeeDao_getPageEmployees_conditions.sql">

EmployeeDao_insertEmployee.sql
insert into employee(id,name,empno,age,salary,birthday,create_date,create_by)
values(
	'${employee.id}',
	'${employee.name}',
	'${employee.empno}',
	${employee.age},
	${employee.salary},
	:employee.birthday,
	:employee.createDate,
	'${employee.createBy}'
)

EmployeeDao_updateEmployee_conditions.sql
<#if employee.name?exists>
	name = :employee.name,
</#if>
<#if employee.age?exists>
	age = :employee.age,
</#if>
<#if employee.salary?exists>
	salary = :employee.salary,
</#if>
<#if employee.empno?exists>
	empno = :employee.empno,
</#if>
<#if employee.birthday?exists>
	birthday = :employee.birthday,
</#if>
<#if employee.createDate?exists>
	create_date = :employee.createDate,
</#if>
<#if employee.createBy?exists>
	create_by = :employee.createBy,
</#if>

EmployeeDao_updateEmployee.sql
update employee set 
<#include "EmployeeDao_updateEmployee_conditions.sql">
where id = :employee.id

SpringTxTestCase.java
package org.springframework.test.context.junit4;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.runner.RunWith;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.web.ServletTestExecutionListener;

@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({ ServletTestExecutionListener.class, DependencyInjectionTestExecutionListener.class,
		DirtiesContextTestExecutionListener.class })
public abstract class AbstractJUnit4SpringContextTests implements ApplicationContextAware {
	protected final Log logger = LogFactory.getLog(this.getClass());
	protected ApplicationContext applicationContext;

	public final void setApplicationContext(ApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
	}
}

EmployeeDaoJunit.java
package test;

import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.jeecgframework.minidao.pojo.MiniDaoPage;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;

import examples.dao.EmployeeDao;
import examples.entity.Employee;
import examples.service.EmployeeService;
import test.spring.SpringTxTestCase;

public class EmployeeDaoJunit extends SpringTxTestCase {

	@Autowired
	EmployeeDao employeeDao;
	@Autowired
	EmployeeService employeeService;
	
	/**
	 * 测试MiniDao分布式事务,同时向连个数据库中插入数据,当数据库A成功,数据库B失败时,数据库A插入的记录也会被回滚
	 */
	@Test
	public void testRegister() {
		Employee employee = new Employee();
		employee.setId("11");
		employee.setName("王老七2");
		employee.setAge(51);
		employee.setEmpno("010");
		employee.setSalary(new BigDecimal(11200.00));
		employee.setBirthday(new Date());
		boolean result = employeeService.registerMember(employee, employee);
		System.out.println(result);
	}
	
	/**
	 * 测试MiniDao的@Sql标签
	 */
	@Test
	public void testCount() {
		System.out.println("-------------testCount----------------------");
		int count = employeeDao.getCount();
		System.out.println(count);
	}
	
	/**
	 * 测试MiniDao的插入方法
	 */
	@Test
	public void testInsertEmployee() {
		System.out.println("-------------testInsertEmployee----------------------");
		Employee employee = new Employee();
		employee.setId("8");
		employee.setName("谢永强");
		employee.setAge(30);
		employee.setEmpno("010");
		employee.setSalary(new BigDecimal(4500.00));
		employee.setBirthday(new Date());
		int rows = employeeDao.insertEmployee(employee);
		System.out.println(rows);
	}
	
	/**
	 * 测试MiniDao的修改方法
	 */
	@Test
	public void testUpdateEmployee() throws ParseException {
		System.out.println("-------------testUpdateEmployee----------------------");
		Employee employee = employeeDao.getEmployeeById("1");
		Date birthday = new SimpleDateFormat("yyyy-MM-dd").parse("1994-09-03");
		employee.setBirthday(birthday);
		employee.setName("刘彦民");
		int rows = employeeDao.updateEmployee(employee);
		System.out.println(rows);
	}
	
	/**
	 * 测试MiniDao查找返回单个实体对象
	 * 测试在事务中动态修改数据源,service层
	 */
	@Test
	public void testGetEmployeeById() {
		System.out.println("-------------testGetEmployeeById----------------------");
//		DataSourceContextHolder.setDataSourceType(DataSourceType.mapdataSource);
		Employee employee = employeeService.selectEmployeeById("1");
		System.out.println("name: " + employee.getName());
		System.out.println("age: " + employee.getAge());
		System.out.println("salary: " + employee.getSalary());
		System.out.println("empno: " + employee.getEmpno());
	}
	
	/**
	 * 测试MiniDao删除方法
	 */
	@Test
	public void testDeleteById() {
		System.out.println("-------------testDeleteById----------------------");
		Employee employee = new Employee();
		employee.setId("9");
		int rows = employeeDao.deleteEmployee(employee);
		System.out.println(rows);
	}
	
	/**
	 * 测试MiniDao查询返回实体列表
	 */
	@Test
	public void testGetAllEmployee() {
		System.out.println("-------------testGetAllEmployee----------------------");
		List<Employee> employees = employeeDao.getAllEmployee();
		for(Employee employee : employees) {
			System.out.println("name: " + employee.getName() + ",age: " + employee.getAge() + ",salary: " + employee.getSalary());
		}
	}
	
	/**
	 * 测试MiniDao查询返回单个Map对象
	 */
	@Test 
	public void testGetMap() {
		System.out.println("-------------testGetMap----------------------");
		Map<String, Object> map = employeeDao.getMap("005", "王馨");
		System.out.println(map);
	}
	
	/**
	 * 测试MiniDao查询返回Map对象列表
	 */
	@Test
	public void testGetAllListMap() {
		System.out.println("-------------testGetAllListMap----------------------");
		List<Map<String, Object>> list = employeeDao.getAllListMap();
		for(Map<String, Object> l : list) {
			System.out.println(l);
		}
	}
	
	/**
	 * 测试MiniDao分页查询
	 */
	@Test
	public void testGetPageEmployees() {
		System.out.println("-------------testGetPageEmployees----------------------");
		Employee employee = new Employee();
		employee.setAge(20);
		MiniDaoPage<Employee> employeePage = employeeDao.getPageEmployees(employee, 1, 2);
		System.out.println("pages: " + employeePage.getPages() + ", page: " + employeePage.getPage() + ", total: " + employeePage.getTotal() + ", rows: " + employeePage.getRows());
		System.out.println(employeePage.getResults());
	}

}

testRegister方法,MiniDao分布式事务测试,当第一个数据库插入成功,第二个数据库插入失败时,第一条插入的数据也会被回滚。

MiniDao官方源码下载: http://zhangdaiscott.github.io/MiniDao/

 类似资料: