spring boot學習6之mybatis+PageHelper分頁外掛+jta多資料來源事務整合
阿新 • • 發佈:2019-01-08
在專案開發中,隨著業務的擴充套件,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
mybatis-config.xmllogging: 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
<?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.javapackage 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階段正真提交時候,第一個資料庫提交完成,第二個資料庫提交的時候失敗了(比如剛好宕機了),丟擲了異常,但是第一個資料庫沒法回滾了,所以可以說產生髒資料了。