1. 程式人生 > >spring boot學習6之mybatis+PageHelper分頁外掛+jta多資料來源事務整合

spring boot學習6之mybatis+PageHelper分頁外掛+jta多資料來源事務整合

        在專案開發中,隨著業務的擴充套件,api可能會操作多個數據庫。本博文就學習下spring boot下使用spring-boot-starter-jta-atomikos對mybatis+mysql+PageHelper分頁外掛的整合。

  專案檔案結構


 準備兩個資料來源資料庫(如果只有一個數據源,那就新建2個數據庫進行測試也是OK的)

pom.xml

<parent>
	    <groupId>org.springframework.boot</groupId>
	    <artifactId>spring-boot-starter-parent</artifactId>
	    <version>1.5.2.RELEASE</version>
	</parent>   
  
	<dependencies>
	    <dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-starter-web</artifactId>

	    </dependency>
	    
		  <dependency>
   			 <groupId>org.mybatis.spring.boot</groupId>
    		 <artifactId>mybatis-spring-boot-starter</artifactId>
   			 <version>1.3.0</version>
		</dependency> 
		
		<dependency>
    			<groupId>mysql</groupId>
    			<artifactId>mysql-connector-java</artifactId>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jta-atomikos</artifactId>
		</dependency>
		
		<!-- 分頁外掛 -->
		<dependency>
    		<groupId>com.github.pagehelper</groupId>
    		<artifactId>pagehelper</artifactId>
    		<version>4.1.6</version>
		</dependency>

		</dependencies>

準備兩個資料來源資料庫(如果只有一個數據源,那就新建2個數據庫進行測試也是OK的)

application.yml

logging:
  config: classpath:logback.xml
  path: d:/logs
server:
  port: 80
  session-timeout: 60

spring:
    datasource:
        db01:
           url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8
           username: root
           password: root
           minPoolSize: 3
           maxPoolSize: 25
           maxLifetime: 20000
           borrowConnectionTimeout: 30
           loginTimeout: 30
           maintenanceInterval: 60
           test.maxIdleTime: 60
           testQuery: select 1
           mapperLocations: classpath:/com/fei/springboot/dao/db01/*.xml
           configLocation: classpath:/mybatis-config.xml
        db02:
           url: jdbc:mysql://192.168.0.213:3306/test?useUnicode=true&characterEncoding=utf-8
           username: root
           password: root
           minPoolSize: 3
           maxPoolSize: 25
           maxLifetime: 20000
           borrowConnectionTimeout: 30
           loginTimeout: 30
           maintenanceInterval: 60
           test.maxIdleTime: 60
           testQuery: select 1
           mapperLocations: classpath:/com/fei/springboot/dao/db02/*.xml
           configLocation: classpath:/mybatis-config.xml
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE configuration  
     PUBLIC "-//mybatis.org//DTD Config 3.0//EN"  
     "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<settings>
	    <!-- 使全域性的對映器啟用或禁用快取。 -->
		<setting name="cacheEnabled" value="true" />
		<!-- 全域性啟用或禁用延遲載入。當禁用時,所有關聯物件都會即時載入。 -->
		<setting name="lazyLoadingEnabled" value="true" />
		<!-- 當啟用時,有延遲載入屬性的物件在被呼叫時將會完全載入任意屬性。否則,每種屬性將會按需要載入。 -->        
         <setting name="aggressiveLazyLoading" value="true"/>        
         <!-- 是否允許單條sql 返回多個數據集  (取決於驅動的相容性) default:true -->
		<setting name="multipleResultSetsEnabled" value="true" />
		<!-- 是否可以使用列的別名 (取決於驅動的相容性) default:true -->
		<setting name="useColumnLabel" value="true" />
		<!-- 允許JDBC 生成主鍵。需要驅動器支援。如果設為了true,這個設定將強制使用被生成的主鍵,有一些驅動器不相容不過仍然可以執行。  default:false  -->
		<setting name="useGeneratedKeys" value="false" />
		<!-- 指定 MyBatis 如何自動對映 資料基表的列 NONE:不隱射 PARTIAL:部分  FULL:全部  -->
		<setting name="autoMappingBehavior" value="PARTIAL" />
		<!-- 這是預設的執行型別  (SIMPLE: 簡單; REUSE: 執行器可能重複使用prepared statements語句;BATCH: 執行器可以重複執行語句和批量更新)  -->
		<setting name="defaultExecutorType" value="SIMPLE" />
		
		<setting name="defaultStatementTimeout" value="25" />
		
		<setting name="defaultFetchSize" value="100" />
		
		<setting name="safeRowBoundsEnabled" value="false" />
		<!-- 使用駝峰命名法轉換欄位。 -->
		<setting name="mapUnderscoreToCamelCase" value="true" />
		<!-- 設定本地快取範圍 session:就會有資料的共享  statement:語句範圍 (這樣就不會有資料的共享 ) defalut:session -->
		<setting name="localCacheScope" value="SESSION" />
		<!-- 預設為OTHER,為了解決oracle插入null報錯的問題要設定為NULL -->
		<setting name="jdbcTypeForNull" value="NULL" />
		<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString" />
	</settings>
	
</configuration>

第一個資料來源

TestDb01Config.java

package com.fei.springboot.config.dbconfig;

import java.io.IOException;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.github.pagehelper.PageHelper;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;

/**
 * db01 資料庫配置
 * @author Jfei
 *
 */

@ConfigurationProperties(prefix="spring.datasource.db01")
@Configuration
@MapperScan(basePackages="com.fei.springboot.dao.db01", sqlSessionTemplateRef="db01SqlSessionTemplate")
public class TestDb01Config {

   private Logger logger = LoggerFactory.getLogger(TestDb01Config.class);
	
    private String url;
	private String username;
	private String password;

	/** min-pool-size 最小連線數 **/
	private int minPoolSize;
	/** max-pool-size 最大連線數 **/
	private int maxPoolSize;
	/** max-lifetime 連線最大存活時間 **/
	private int maxLifetime;
	/** borrow-connection-timeout 獲取連線失敗重新獲等待最大時間,在這個時間內如果有可用連線,將返回 **/
	private int borrowConnectionTimeout;
	/** login-timeout java資料庫連線池,最大可等待獲取datasouce的時間 **/
	private int loginTimeout;
	/** maintenance-interval 連接回收時間 **/
	private int maintenanceInterval;
	/** max-idle-time 最大閒置時間,超過最小連線池連線的連線將將關閉 **/
	private int maxIdleTime;
	/** test-query 測試SQL **/
	private String testQuery;
    

//  配置mapper的掃描,找到所有的mapper.xml對映檔案
    private String mapperLocations;

//  載入全域性的配置檔案
    private String configLocation;
    
    
 // 配置資料來源
 	@Primary
 	@Bean(name = "db01DataSource")
 	public DataSource db01DataSource() throws SQLException {
 		
 		MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
 		mysqlXaDataSource.setUrl(url);
 		mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
 		mysqlXaDataSource.setPassword(password);
 		mysqlXaDataSource.setUser(username);
 		mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
 		
 		AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
 		xaDataSource.setXaDataSource(mysqlXaDataSource);
 		xaDataSource.setUniqueResourceName("db01DataSource");

 		xaDataSource.setMinPoolSize(minPoolSize);
 		xaDataSource.setMaxPoolSize(maxPoolSize);
 		xaDataSource.setMaxLifetime(maxLifetime);
 		xaDataSource.setBorrowConnectionTimeout(borrowConnectionTimeout);
 		xaDataSource.setLoginTimeout(loginTimeout);
 		xaDataSource.setMaintenanceInterval(maintenanceInterval);
 		xaDataSource.setMaxIdleTime(maxIdleTime);
 		xaDataSource.setTestQuery(testQuery);
        
 		return xaDataSource;
 	}
    
 	
 	@Bean(name = "db01SqlSessionFactory")
	public SqlSessionFactory db01SqlSessionFactory(@Qualifier("db01DataSource") DataSource dataSource)
			throws Exception {
		
		  try {
              SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
              sessionFactoryBean.setDataSource(dataSource);
              
              //設定mapper.xml檔案所在位置 
              Resource[] resources = new PathMatchingResourcePatternResolver().getResources(mapperLocations);
              sessionFactoryBean.setMapperLocations(resources);
           //設定mybatis-config.xml配置檔案位置
              sessionFactoryBean.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));

              //新增分頁外掛、列印sql外掛
              Interceptor[] plugins = new Interceptor[]{pageHelper(),sqlPrintInterceptor()};
              sessionFactoryBean.setPlugins(plugins);
              
              return sessionFactoryBean.getObject();
          } catch (IOException e) {
              logger.error("mybatis resolver db01 mapper*xml is error",e);
              throw e;
          } catch (Exception e) {
              logger.error("mybatis db01sqlSessionFactoryBean create error",e);
              throw e;
          }
	}

	@Bean(name = "db01SqlSessionTemplate")
	public SqlSessionTemplate db01SqlSessionTemplate(
			@Qualifier("db01SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
		return new SqlSessionTemplate(sqlSessionFactory);
	}
 	
	  /**
     * 分頁外掛
     * @param dataSource
     * @return
     */
    

    public PageHelper pageHelper() {
        PageHelper pageHelper = new PageHelper();
        Properties p = new Properties();
        p.setProperty("offsetAsPageNum", "true");
        p.setProperty("rowBoundsWithCount", "true");
        p.setProperty("reasonable", "true");
        p.setProperty("returnPageInfo", "check");
        p.setProperty("params", "count=countSql");
        pageHelper.setProperties(p);
        return pageHelper;
    }
    
    //將要執行的sql進行日誌列印(不想攔截,就把這方法註釋掉)
    public SqlPrintInterceptor sqlPrintInterceptor(){
    	return new SqlPrintInterceptor();
    }

// ---  get set 自行補充
  
}
第二個資料來源

TestDb02Config.java

package com.fei.springboot.config.dbconfig;

import java.io.IOException;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.github.pagehelper.PageHelper;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;

/**
 * db02 資料庫配置
 * @author Jfei
 *
 */

@ConfigurationProperties(prefix="spring.datasource.db02")
@Configuration
@MapperScan(basePackages="com.fei.springboot.dao.db02",sqlSessionTemplateRef="db02SqlSessionTemplate")
public class TestDb02Config {

   private Logger logger = LoggerFactory.getLogger(TestDb02Config.class);
	
    private String url;
	private String username;
	private String password;

	/** min-pool-size 最小連線數 **/
	private int minPoolSize;
	/** max-pool-size 最大連線數 **/
	private int maxPoolSize;
	/** max-lifetime 連線最大存活時間 **/
	private int maxLifetime;
	/** borrow-connection-timeout 獲取連線失敗重新獲等待最大時間,在這個時間內如果有可用連線,將返回 **/
	private int borrowConnectionTimeout;
	/** login-timeout java資料庫連線池,最大可等待獲取datasouce的時間 **/
	private int loginTimeout;
	/** maintenance-interval 連接回收時間 **/
	private int maintenanceInterval;
	/** max-idle-time 最大閒置時間,超過最小連線池連線的連線將將關閉 **/
	private int maxIdleTime;
	/** test-query 測試SQL **/
	private String testQuery;
    

//  配置mapper的掃描,找到所有的mapper.xml對映檔案
    private String mapperLocations;

//  載入全域性的配置檔案
    private String configLocation;
    
    
 // 配置資料來源
 //	@Primary  //db01那邊配置使用Primary了,這裡不能再用了,否則報錯
 	@Bean(name = "db02DataSource")
 	public DataSource db02DataSource() throws SQLException {
 		
 		MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
 		mysqlXaDataSource.setUrl(url);
 		mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
 		mysqlXaDataSource.setPassword(password);
 		mysqlXaDataSource.setUser(username);
 		mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
 		
 		AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
 		xaDataSource.setXaDataSource(mysqlXaDataSource);
 		xaDataSource.setUniqueResourceName("db02DataSource");

 		xaDataSource.setMinPoolSize(minPoolSize);
 		xaDataSource.setMaxPoolSize(maxPoolSize);
 		xaDataSource.setMaxLifetime(maxLifetime);
 		xaDataSource.setBorrowConnectionTimeout(borrowConnectionTimeout);
 		xaDataSource.setLoginTimeout(loginTimeout);
 		xaDataSource.setMaintenanceInterval(maintenanceInterval);
 		xaDataSource.setMaxIdleTime(maxIdleTime);
 		xaDataSource.setTestQuery(testQuery);
        
 		return xaDataSource;
 	}
    
 	
 	@Bean(name = "db02SqlSessionFactory")
	public SqlSessionFactory db02SqlSessionFactory(@Qualifier("db02DataSource") DataSource dataSource)
			throws Exception {
		
		  try {
              SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
              sessionFactoryBean.setDataSource(dataSource);
              
              //設定mapper.xml檔案所在位置 
              Resource[] resources = new PathMatchingResourcePatternResolver().getResources(mapperLocations);
              sessionFactoryBean.setMapperLocations(resources);
           //設定mybatis-config.xml配置檔案位置
              sessionFactoryBean.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));

              //新增分頁外掛、列印sql外掛
              Interceptor[] plugins = new Interceptor[]{pageHelper(),sqlPrintInterceptor()};
              sessionFactoryBean.setPlugins(plugins);
              
              return sessionFactoryBean.getObject();
          } catch (IOException e) {
              logger.error("mybatis resolver db02 mapper*xml is error",e);
              throw e;
          } catch (Exception e) {
              logger.error("mybatis db02sqlSessionFactoryBean create error",e);
              throw e;
          }
	}

	@Bean(name = "db02SqlSessionTemplate")
	public SqlSessionTemplate db02SqlSessionTemplate(
			@Qualifier("db02SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
		return new SqlSessionTemplate(sqlSessionFactory);
	}
 	
	  /**
     * 分頁外掛
     * @param dataSource
     * @return
     */
 
    public PageHelper pageHelper() {
        PageHelper pageHelper = new PageHelper();
        Properties p = new Properties();
        p.setProperty("offsetAsPageNum", "true");
        p.setProperty("rowBoundsWithCount", "true");
        p.setProperty("reasonable", "true");
        p.setProperty("returnPageInfo", "check");
        p.setProperty("params", "count=countSql");
        pageHelper.setProperties(p);
        return pageHelper;
    }
    
    //將要執行的sql進行日誌列印(不想攔截,就把這方法註釋掉)
    public SqlPrintInterceptor sqlPrintInterceptor(){
    	return new SqlPrintInterceptor();
    }

//--  get set 自行補充

}

注意 

@MapperScan(basePackages="com.fei.springboot.dao.db02",sqlSessionTemplateRef="db02SqlSessionTemplate")  
要匹配,否則容易出錯,不同資料來源的mapper介面類和mapper.xml檔案最好都分開

事務類的配置TransactionManagerConfig.java

package com.fei.springboot.config.dbconfig;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.jta.JtaTransactionManager;

import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;

/**
 * 事務配置
 * @author Jfei
 *
 */
@Configuration
@EnableTransactionManagement
public class TransactionManagerConfig {
	
	@Bean(name = "userTransaction")
	public UserTransaction userTransaction() throws Throwable {
		UserTransactionImp userTransactionImp = new UserTransactionImp();
		userTransactionImp.setTransactionTimeout(10000);
		return userTransactionImp;
	}

	@Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
	public TransactionManager atomikosTransactionManager() throws Throwable {
		UserTransactionManager userTransactionManager = new UserTransactionManager();
		userTransactionManager.setForceShutdown(false);
		return userTransactionManager;
	}

	@Bean(name = "transactionManager")
	@DependsOn({ "userTransaction", "atomikosTransactionManager" })
	public PlatformTransactionManager transactionManager() throws Throwable {
		UserTransaction userTransaction = userTransaction();
		JtaTransactionManager manager = new JtaTransactionManager(userTransaction, atomikosTransactionManager());
		return manager;
	}
}
sql列印的攔截器SqlPrintInterceptor.java
package com.fei.springboot.config.dbconfig;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;

/**
 * MyBatis 將mybatis要執行的sql攔截打印出來
 *
 * @since 1.0.0
 */
@Intercepts
({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class SqlPrintInterceptor implements Interceptor {

	private static Log logger = LogFactory.getLog(SqlPrintInterceptor.class);
	
    private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        Object parameterObject = null;
        if (invocation.getArgs().length > 1) {
            parameterObject = invocation.getArgs()[1];
        }

        long start = System.currentTimeMillis();

        Object result = invocation.proceed();
        
        String statementId = mappedStatement.getId();
        BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);
        Configuration configuration = mappedStatement.getConfiguration();
        String sql = getSql(boundSql, parameterObject, configuration);

        long end = System.currentTimeMillis();
        long timing = end - start;
        if(logger.isInfoEnabled()){
        	logger.info("執行sql耗時:" + timing + " ms" + " - id:" + statementId + " - Sql:" );
        	logger.info("   "+sql);
        }
       
        return result;
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

    @Override
    public void setProperties(Properties properties) {
    }

    private String getSql(BoundSql boundSql, Object parameterObject, Configuration configuration) {
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        if (parameterMappings != null) {
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    Object value;
                    String propertyName = parameterMapping.getProperty();
                    if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        value = parameterObject;
                    } else {
                        MetaObject metaObject = configuration.newMetaObject(parameterObject);
                        value = metaObject.getValue(propertyName);
                    }
                    sql = replacePlaceholder(sql, value);
                }
            }
        }
        return sql;
    }

    private String replacePlaceholder(String sql, Object propertyValue) {
        String result;
        if (propertyValue != null) {
            if (propertyValue instanceof String) {
                result = "'" + propertyValue + "'";
            } else if (propertyValue instanceof Date) {
                result = "'" + DATE_FORMAT.format(propertyValue) + "'";
            } else {
                result = propertyValue.toString();
            }
        } else {
            result = "null";
        }
        return sql.replaceFirst("\\?", Matcher.quoteReplacement(result));
    }
}


核心配置OK了。下面寫dao/service/controller進行測試

db01的

package com.fei.springboot.dao.db01;

import java.util.List;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import com.fei.springboot.domain.User;

@Mapper
public interface UserMapper {

	@Insert("insert sys_user(id,user_name) values(#{id},#{userName})")
	void insert(User u);
	
	//注:方法名和要UserMapper.xml中的id一致
	List<User> query(@Param("userName")String userName);
	
}
UserMapper.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.fei.springboot.dao.db01.UserMapper">
 
<select id="query"  resultType="com.fei.springboot.domain.User">
    select id ,user_name 
    from sys_user 
    where 1=1
    <if test="userName != null">
      and user_name like CONCAT('%',#{userName},'%')
    </if>
</select>
</mapper>
Db02UserMapper.java
package com.fei.springboot.dao.db02;

import java.util.List;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import com.fei.springboot.domain.User;

@Mapper
public interface Db02UserMapper {

	@Insert("insert sys_user(id,user_name) values(#{id},#{userName})")
	void insert(User u);
	
	//注:方法名和要UserMapper.xml中的id一致
	List<User> query(@Param("userName")String userName);
	
}
Db02UserMapper.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.fei.springboot.dao.db02.Db02UserMapper">
 
<select id="query"  resultType="com.fei.springboot.domain.User">
    select id ,user_name 
    from sys_user 
    where 1=1
    <if test="userName != null">
      and user_name like CONCAT('%',#{userName},'%')
    </if>
</select>
</mapper>
UserService.java
package com.fei.springboot.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.fei.springboot.dao.db01.UserMapper;
import com.fei.springboot.dao.db02.Db02UserMapper;
import com.fei.springboot.domain.User;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;

@Service
@Transactional(propagation=Propagation.REQUIRED,readOnly=true,rollbackFor=Exception.class)
public class UserService {

	@Autowired
	private UserMapper userMapper;
	
	@Autowired
	private Db02UserMapper db02UserMapper;
	
	//注意:方法的@Transactional會覆蓋類上面宣告的事務
	//Propagation.REQUIRED :有事務就處於當前事務中,沒事務就建立一個事務
	//isolation=Isolation.DEFAULT:事務資料庫的預設隔離級別
	@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=false)
	public void insertUser(User u){
		this.userMapper.insert(u);
		this.db02UserMapper.insert(u);
		//如果類上面沒有@Transactional,方法上也沒有,哪怕throw new RuntimeException,資料庫也會成功插入資料
	//	throw new RuntimeException("測試插入事務");
	}
	
	public PageInfo<User> queryPage(String userName,int pageNum,int pageSize){
		Page<User> page = PageHelper.startPage(pageNum, pageSize);
		//PageHelper會自動攔截到下面這查詢sql
		this.userMapper.query(userName);
		return page.toPageInfo();
	}
	
	

}
UserController.java
package com.fei.springboot.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.fei.springboot.domain.User;
import com.fei.springboot.service.UserService;
import com.github.pagehelper.PageInfo;

@Controller
@RequestMapping("/user")
public class UserController {

	
	@Autowired
	private UserService userService;
	
	@RequestMapping("/hello")
	@ResponseBody
	public String hello(){
		return "hello";
	}
	/**
	 * 測試插入
	 * @return
	 */
	@RequestMapping("/add")
	@ResponseBody
	public String add(String id,String userName){
		User u = new User();
		u.setId(id);
		u.setUserName(userName);
		this.userService.insertUser(u);
		return u.getId()+"    " + u.getUserName();
	}
	/**
	 * 測試分頁外掛
	 * @return
	 */
	@RequestMapping("/queryPage")
	@ResponseBody
	public String queryPage(){
		PageInfo<User> page = this.userService.queryPage("tes", 1, 2);
		System.out.println("總頁數=" + page.getPages());
		System.out.println("總記錄數=" + page.getTotal()) ;
		for(User u : page.getList()){
			System.out.println(u.getId() + " \t " + u.getUserName());
		}
		return "success";
	}
	
}

啟動類,Application.java
package com.fei.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;

@EnableAutoConfiguration
@ComponentScan(basePackages={"com.fei.springboot"})
@SpringBootApplication
public class Application extends SpringBootServletInitializer implements EmbeddedServletContainerCustomizer{

	 @Override  
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {  
        return application.sources(Application.class);  
    }  

	 
	 public static void main(String[] args) throws Exception {
	        SpringApplication.run(Application.class, args);
	    }

	public void customize(ConfigurableEmbeddedServletContainer configurableEmbeddedServletContainer) {
	//	configurableEmbeddedServletContainer.setPort(9090);
	}
}

執行啟動類,瀏覽器執行 
http://127.0.0.1/user/add?id=12345&userName=test12345

到2個數據庫中檢視,會發現都有資料了。

測試事務,把UserService類中的insertUser方法中的異常註釋去掉,然後在瀏覽器執行
http://127.0.0.1/user/add?id=789&userName=test789
發現丟擲了異常,2個數據庫中都沒資料

注意:由於事務JtaTransactionManager,是二階提交,有個缺點就是,第一階段預提交時候發現2個數據庫都沒問題,但是第2階段正真提交時候,第一個資料庫提交完成,第二個資料庫提交的時候失敗了(比如剛好宕機了),丟擲了異常,但是第一個資料庫沒法回滾了,所以可以說產生髒資料了。