1、項目上遇到的問題
最近在做一個項目,需要同時用到oracle和mysql兩個數據庫,那么問題就來了,怎么實現多數據源呢?數據源之間是怎么切換呢?多數據源事務怎么控制呢?
以下demo都是基於springboot。
2、其實實現多數據源還是很簡單的,主要是以下步驟
1)配置application.yml,把之前的單數據源配置成多個
2)手動配置每個數據源,包含sqlsessionfactory , transactionmanager,datasource, sqlsessiontemplate
3、那數據源怎么切換呢?
我采用的分包的方法來實現的,找過網上很多資料,有通過注解切換的,有興趣的可以網上找找。
4、那多數據源事務怎么控制?
因為是多數據源,所以是跨庫操作,單數據源不能保證兩個事務都回滾,這里可以說是分布式事務。
關於分布式事務,XA ----->> 分布式事務協議 ,這里暫不多說
開始寫代碼吧
application.yml
spring: datasource: druid: m1: #數據源1 driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false&serverTimezone=UTC username: root password: 123456 #初始化時建立物理連接的個數 initialSize: 1 #池中最大連接數 maxActive: 20 #最小空閑連接 minIdle: 1 #獲取連接時最大等待時間,單位毫秒 maxWait: 60000 #有兩個含義: #1) Destroy線程會檢測連接的間隔時間,如果連接空閑時間大於等於minEvictableIdleTimeMillis則關閉物理連接。 #2) testWhileIdle的判斷依據,詳細看testWhileIdle屬性的說明 timeBetweenEvictionRunsMillis: 60000 #連接保持空閑而不被驅逐的最小時間,單位是毫秒 minEvictableIdleTimeMillis: 300000 #使用該SQL語句檢查鏈接是否可用。如果validationQuery=null,testOnBorrow、testOnReturn、testWhileIdle都不會起作用。 validationQuery: SELECT 1 FROM DUAL #建議配置為true,不影響性能,並且保證安全性。申請連接的時候檢測,如果空閑時間大於timeBetweenEvictionRunsMillis,執行validationQuery檢測連接是否有效。 testWhileIdle: true #申請連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能。 testOnBorrow: false #歸還連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能。 testOnReturn: false # 配置監控統計攔截的filters,去掉后監控界面sql無法統計,'wall'用於防火牆 filters: stat,wall # 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄 #connectionProperties.druid.stat.mergeSql: true #connectionProperties.druid.stat.slowSqlMillis: 5000 # 合並多個DruidDataSource的監控數據 #useGlobalDataSourceStat: true #default-auto-commit: true 默認 #default-auto-commit: false m2: #數據源2 driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false&serverTimezone=UTC username: root password: 123456 #初始化時建立物理連接的個數 initialSize: 1 #池中最大連接數 maxActive: 20 #最小空閑連接 minIdle: 1 #獲取連接時最大等待時間,單位毫秒 maxWait: 60000 #有兩個含義: #1) Destroy線程會檢測連接的間隔時間,如果連接空閑時間大於等於minEvictableIdleTimeMillis則關閉物理連接。 #2) testWhileIdle的判斷依據,詳細看testWhileIdle屬性的說明 timeBetweenEvictionRunsMillis: 60000 #連接保持空閑而不被驅逐的最小時間,單位是毫秒 minEvictableIdleTimeMillis: 300000 #使用該SQL語句檢查鏈接是否可用。如果validationQuery=null,testOnBorrow、testOnReturn、testWhileIdle都不會起作用。 validationQuery: SELECT 1 FROM DUAL #建議配置為true,不影響性能,並且保證安全性。申請連接的時候檢測,如果空閑時間大於timeBetweenEvictionRunsMillis,執行validationQuery檢測連接是否有效。 testWhileIdle: true #申請連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能。 testOnBorrow: false #歸還連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能。 testOnReturn: false # 配置監控統計攔截的filters,去掉后監控界面sql無法統計,'wall'用於防火牆 filters: stat,wall # 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄 #connectionProperties.druid.stat.mergeSql: true #connectionProperties.druid.stat.slowSqlMillis: 5000 # 合並多個DruidDataSource的監控數據 #useGlobalDataSourceStat: true #default-auto-commit: true 默認
m1 數據庫1
m2 數據庫2
pom
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--狀態監控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- spring web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.vesion}</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.15.0-GA</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.boot.starter.vesion}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.vesion}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis.spring.vesion}</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- mybatis 分頁 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2</version>
</dependency>
<!-- swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger.vesion}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.vesion}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
<!-- XA協議 支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.typesafe.akka/akka-actor -->
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor_2.13</artifactId>
<version>2.6.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
數據庫1的配置:
@Component @ConfigurationProperties(prefix = "spring.datasource.druid.m1") public class M1DataSourceProperties { private String driverClassName; private String url; private String username; private String password; private Integer initialSize; private Integer maxActive; private Integer minIdle; private Integer maxWait; private Integer timeBetweenEvictionRunsMillis; private Integer minEvictableIdleTimeMillis; private String validationQuery; private Boolean testWhileIdle; private Boolean testOnBorrow; private Boolean testOnReturn; private String filters; }
數據庫1的datasource配置:
import com.alibaba.druid.pool.xa.DruidXADataSource; import com.atomikos.icatch.jta.UserTransactionImp; import com.atomikos.icatch.jta.UserTransactionManager; import com.atomikos.jdbc.AtomikosDataSourceBean; import com.github.pagehelper.PageInterceptor; import com.test.maven.user.common.BeanUtils; 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.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.transaction.jta.JtaTransactionManager; import javax.sql.DataSource; import javax.transaction.UserTransaction; import java.util.Properties; @Configuration @MapperScan(basePackages = M1DataSourceConfig.PACKAGE, sqlSessionFactoryRef = "m1SqlSessionFactory",sqlSessionTemplateRef = "m1SqlSessionTemplate") public class M1DataSourceConfig { static final String PACKAGE = "com.test.maven.user.dao.m1"; static final String MAPPER_LOCATION = "classpath:mapper/m1/*.xml"; @Autowired private M1DataSourceProperties m1DataSourceProperties; @Bean(name = "m1DataSource") @Primary public DataSource m1DataSource() { DruidXADataSource datasource = new DruidXADataSource(); BeanUtils.copyProperties(m1DataSourceProperties,datasource); AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean(); xaDataSource.setXaDataSource(datasource); xaDataSource.setUniqueResourceName("m1DataSource"); System.out.println("數據源1初始化完成================"); return xaDataSource; } @Bean(name = "transactionManager") public JtaTransactionManager transactionManager() { UserTransactionManager userTransactionManager = new UserTransactionManager(); UserTransaction userTransaction = new UserTransactionImp(); return new JtaTransactionManager(userTransaction, userTransactionManager); } @Bean(name = "m1SqlSessionFactory") @Primary public SqlSessionFactory m1SqlSessionFactory(@Qualifier("m1DataSource") DataSource m1DataSource) throws Exception { final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(m1DataSource); sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources(M1DataSourceConfig.MAPPER_LOCATION)); //分頁插件 Interceptor interceptor = new PageInterceptor(); Properties properties = new Properties(); //數據庫 properties.setProperty("helperDialect", "mysql"); //是否將參數offset作為PageNum使用 properties.setProperty("offsetAsPageNum", "true"); //是否進行count查詢 properties.setProperty("rowBoundsWithCount", "true"); //是否分頁合理化 properties.setProperty("reasonable", "false"); interceptor.setProperties(properties); sessionFactory.setPlugins(new Interceptor[] {interceptor}); return sessionFactory.getObject(); } @Bean(name = "m1SqlSessionTemplate") @Primary public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("m1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } }
數據庫2的屬性配置:
@Component @ConfigurationProperties(prefix = "spring.datasource.druid.m2") public class M2DataSourceProperties { private String driverClassName; private String url; private String username; private String password; private Integer initialSize; private Integer maxActive; private Integer minIdle; private Integer maxWait; private Integer timeBetweenEvictionRunsMillis; private Integer minEvictableIdleTimeMillis; private String validationQuery; private Boolean testWhileIdle; private Boolean testOnBorrow; private Boolean testOnReturn; private String filters; }
數據庫2的DataSource配置:
import com.alibaba.druid.pool.xa.DruidXADataSource; import com.atomikos.jdbc.AtomikosDataSourceBean; import com.github.pagehelper.PageInterceptor; import com.test.maven.user.common.BeanUtils; 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.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import javax.sql.DataSource; import java.util.Properties; @Configuration @MapperScan(basePackages = M2DataSourceConfig.PACKAGE, sqlSessionFactoryRef = "m2SqlSessionFactory",sqlSessionTemplateRef = "m2SqlSessionTemplate") public class M2DataSourceConfig { static final String PACKAGE = "com.test.maven.user.dao.m2"; static final String MAPPER_LOCATION = "classpath:mapper/m2/*.xml"; @Autowired private M2DataSourceProperties m2DataSourceProperties; @Bean(name = "m2DataSource") public DataSource m2DataSource() { DruidXADataSource datasource = new DruidXADataSource(); BeanUtils.copyProperties(m2DataSourceProperties,datasource); AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean(); xaDataSource.setXaDataSource(datasource); xaDataSource.setUniqueResourceName("m2DataSource"); System.out.println("數據源2初始化完成================"); return xaDataSource; } @Bean(name = "m2SqlSessionFactory") public SqlSessionFactory m2SqlSessionFactory(@Qualifier("m2DataSource") DataSource m2DataSource) throws Exception { final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(m2DataSource); sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(M2DataSourceConfig.MAPPER_LOCATION)); //分頁插件 Interceptor interceptor = new PageInterceptor(); Properties properties = new Properties(); //數據庫 properties.setProperty("helperDialect", "mysql"); //是否將參數offset作為PageNum使用 properties.setProperty("offsetAsPageNum", "true"); //是否進行count查詢 properties.setProperty("rowBoundsWithCount", "true"); //是否分頁合理化 properties.setProperty("reasonable", "false"); interceptor.setProperties(properties); sessionFactory.setPlugins(new Interceptor[]{interceptor}); return sessionFactory.getObject(); } @Bean(name = "m2SqlSessionTemplate") public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("m2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } }
druid配置(狀態監控頁面):
@Configuration public class DruidConfig { /** * 注冊一個StatViewServlet * * @return servlet registration bean */ @Bean public ServletRegistrationBean druidStatViewServlet() { ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean( new StatViewServlet(), "/druid/*"); servletRegistrationBean.addInitParameter("loginUsername", "admin"); servletRegistrationBean.addInitParameter("loginPassword", "123456"); servletRegistrationBean.addInitParameter("resetEnable", "false"); return servletRegistrationBean; } /** * 注冊一個:filterRegistrationBean * * @return filter registration bean */ @Bean public FilterRegistrationBean druidStatFilter() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean( new WebStatFilter()); // 添加過濾規則. filterRegistrationBean.addUrlPatterns("/*"); // 添加不需要忽略的格式信息. filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); return filterRegistrationBean; } }
項目結構:

這樣就基本完事了,可以實現數據源和事務控制了
上面m1的事務管理器只有一個,而在m2中是沒有配置的,因為是交給了atomikos,由它來管理事務,他實現了JTA/XA規范中的事務管理器,這里也是簡單的實現了功能.
以上是借鑒了網上資料和自己整理的,如有侵權,敬請諒解,謝謝。
