15《Spring Boot 入门教程》多数据源与分布式事务

15《Spring Boot 入门教程》多数据源与分布式事务,第1张

一个项目中使用多个数据源的需求,我们在日常工作中时常会遇到。

以商城系统为例,有一个 MySQL 的数据库负责存储交易数据。公司还有一套 ERP 企业信息化管理系统,要求订单信息同步录入 ERP 数据库,便于公司统一管理,而该 ERP 系统采用的数据库为 SQL Server 。

此时,就可以在 Spring Boot 项目中配置多个数据源。另外,使用多数据源后,需要采用分布式事务来保持数据的完整性。

本小节我们使用 Spring Boot 开发一个商城系统的订单生成功能,订单信息同时进入 MySQL 与 SQL Server 数据库。

首先创建 MySQL 数据库 shop ,并新建订单表 order ,表结构如下:

order 表结构

然后创建 SQL Server 数据库 erpshop ,并新建订单表 erp_order ,表结构如下。注意 id 是自增长的唯一标识,out_id 是对应订单在 MySQL 数据库中的唯一标识,以便在两个库中比对订单。

erp_order 结构

接下来,我们开始实现 Spring Boot 后端项目,数据持久层采用 MyBatis 框架,同时访问两个数据源。

Spring Boot 版本选择 2.2.5 ,Group 为 com.imooc , Artifact 为 spring-boot-multidb,生成项目后导入 Eclipse 开发环境。

我们引入热部署依赖、 Web 依赖、数据库访问相关依赖及测试相关依赖,具体如下:

实例:

由于我们要同时访问两个数据库,所以需要在配置文件中添加两个数据源的配置信息。注意配置多数据源时, url 配置需要使用spring.datasource.db1.jdbc-url=xxx的形式。

实例:

多个数据源的情况下, 我们需要通过配置类,将数据源注册为组件放入 Spring 容器中。

实例:

通过这个配置类, Spring 容器中就有两个数据源组件,这两个组件分别采用spring.datasource.db1和spring.datasource.db2开头的配置信息。所以通过这两个组件,就能分别 *** 作 MySQL 数据源 1 和 SQL Sever 数据源 2 。

多数据源情况下, MyBatis 中的关键组件 SqlSessionFactory 和 SqlSessionTemplate 也需要单独配置,我们需要为两个数据源分别配置一套组件。

实例:

通过上面的配置类,com.imooc.springbootmultidb.mapper1包中的 DAO 数据访问接口会自动调用 sqlSessionTemplate1 组件实现具体数据库 *** 作,而 sqlSessionTemplate1 *** 作的数据源已经通过配置类设置为 db1 。同时, DAO 数据访问接口对应的映射文件已经指定到classpath:mapper1/目录去寻找。这样数据源 – DAO 数据访问接口 – 映射文件三者的对应关系就建立起来了。

数据源 2 的配置方法是一样的, com.imooc.springbootmultidb.mapper2包中的 DAO 数据访问接口会自动调用 sqlSessionTemplate2 组件,其 *** 作的数据源即为 db2 ,其对应的映射文件指定到classpath:mapper2/目录去寻找。

实例:

数据访问接口的位置已经在配置类指定,首先在com.imooc.springbootmultidb.mapper1创建 OrderDao , *** 作的是数据源 1 中的 order 表。

实例:

然后在com.imooc.springbootmultidb.mapper2创建 ErpOrderDao , *** 作的是数据源 2 中的 erporder 表。

实例:

这两个接口中使用的数据对象比较简单,代码如下:

实例:

分别针对 OrderDao 、 ErpOrderDao 编写对应的映射文件,然后按照配置类指定的位置,两个文件分别放到resources/mapper1和resources/mapper2目录下。

实例:

实例:

数据 *** 作接口与对应的映射文件均已编写完毕,现在可以通过测试类进行多数据源测试了,我们在测试类中同时向两个库插入记录。

实例:

运行测试方法后,两个数据库表中均新增数据成功,这样我们就成功的使用 Spring Boot 同时 *** 作了两个数据源。

采用多数据源之后,事务的实现方式也随之发生变化。当某个数据源 *** 作出现异常时,该数据源和其他数据源的事务都需要回滚。这种涉及多个数据源的事务,称为分布式事务,接来下我们就来具体实现一下。

在 pom.xml 引入 Atomikos 事务管理器相关的依赖项, Atomikos 是一个开源的事务管理器,支持分布式事务。

实例:

需要将默认的数据源更换为支持分布式事务的数据源, MySQL 对应的数据源为 MysqlXADataSource , SQL Server 对应的数据源为 SQLServerXADataSource 。

实例:

继续修改 DataSourceConfig 类,在其中配置分布式事务管理器组件。当项目中使用事务时,会通过配置的分布式事务管理器管理分布式事务 *** 作。

实例:

在测试方法上添加@Transactional开启事务,然后在两个数据源 *** 作中间模拟抛出异常。

实例:

此时运行测试类,可以发现数据源 1 的事务已回滚,验证成功!

在开发 Spring Boot 项目时,如果默认配置满足不了我们的需求,可以通过手工配置组件实现我们需要的功能。这些组件可能是各个公司提供的,我们根据相应文档,为其配置各个属性即可。

同一个项目有时会涉及到多个数据库,这时我们就要配置多个数据源。配置多数据源的常见情况有以下两种:

1)同一个项目中涉及两个或多个业务数据库,它们之间相互独立,这种情况也可以作为两个或多个项目来开发

2)两个或多个数据库之间是主从关系,主库负责写,从库负责读

1、pom.xml配置

在pom.xml中增加MyBatis-Plus多数据源依赖:

2、配置文件配置

在配置文件application.yml中配置我们需要连接的数据库:blog和user,默认为blog

3、启动类配置

在@SpringBootApplication注解上增加exclude = DruidDataSourceAutoConfigure.class配置:

这个配置的作用是去掉对DruidDataSourceAutoConfigure的自动配置,否则程序会报错:

原因:

DruidDataSourceAutoConfigure在DynamicDataSourceAutoConfiguration之前,其会注入一个DataSourceWrapper,会在原生的spring.datasource下找url, username, password等,而我们动态数据源的配置路径是变化的。

4、实体类和dao层配置

在po文件夹下创建blog和user文件夹,分别用于存储blog数据库和user数据库的实体:

注解:

@TableName: 表名注解,标识实体类对应的表

@TableId: 主键注解,当type = IdType.AUTO时,表示这个主键是自增主键

在dao文件夹下创建blog和user文件夹,分别用于存储blog和user的dao:

注解:

@Repository: 将数据访问层(DAO层)的类标识为Spring Bean

@DS: 配置非默认数据源,本示例中blog为默认数据源,user为非默认数据源,在使用@DS注解时,有如下注意事项:

1)不能使用事务,否则数据源不会切换,使用的还是第一次加载的数据源

2)第一次加载数据源之后,第二次,第三次…… *** 作其他数据源,如果数据源不存在,使用的还是第一次加载的数据源

3)数据源名称不要包含下划线,否则不能切换

5、测试验证

编写ArticleController和UserInfoController:

注 : 业务逻辑复杂时,Controller和Mapper中间会有Service层来处理业务逻辑,现在我们就简单的测试一下多数据源,所以直接使用Controller调用Mapper了

1、配置分页插件

2、分页方法

1)使用MyBatis-Plus的selectPage方法

使用MyBatis-Plus的selectPage方法,返回了IPage,示例:

2)sql分页

有时候有些分页需要关联多张表,使用LambdaQueryWrapper不太方便,这时候可以自己写sql来实现分页,主要有两种:纯sql自己实现分页和使用IPage实现分页

注 : 这里的sql示例就使用单表查询了,具体的可根据业务场景使用多表查询

A、纯sql自己实现分页

分页的数据list和总条数单独调用方法返回 :

B、使用IPage实现分页(常用)

返回IPage,返回值的数据结构见“ 1)使用MyBatis-Plus的selectPage方法

本文简单介绍了一下MyBatis-Plus的多数据源和分页,本文示例代码, 详见https://gitee.com/tunan222/spring-boot-demo

若您觉得还可以,请帮忙点个 “赞” ,谢谢

1、首先配置多个datasource

<!-- 主数据库的数据据源 -->

<bean id="masterDataSource" class="org.apache.commons.dbcp.BasicDataSource"

destroy-method="close">

<property name="driverClassName" value="oracle.jdbc.OracleDriver" />

<property name="url" value="jdbc:oracle:thin:@192.168.10.11:1521:trew" />

<property name="username" value="poi" />

<property name="password" value="poi" />

</bean>

<!-- 备份库的数据据源 -->

<bean id="slaveDataSource" class="org.apache.commons.dbcp.BasicDataSource"

destroy-method="close">

<property name="driverClassName" value="oracle.jdbc.OracleDriver" />

<property name="url" value="jdbc:oracle:thin:@192.168.10.12:1521:trew" />

<property name="username" value="poi2" />

<property name="password" value="poi2" />

</bean>

2、写一个DynamicDataSource类继承AbstractRoutingDataSource,并实现determineCurrentLookupKey方法

?

public class DynamicDataSource extends AbstractRoutingDataSource {

@SuppressWarnings("unused")

private Log logger = LogFactory.getLog(getClass())

@Override

protected Object determineCurrentLookupKey() {

return DbContextHolder.getDbType()

}

}

public class DbContextHolder {

@SuppressWarnings("rawtypes")

private static final ThreadLocal contextHolder = new ThreadLocal()

@SuppressWarnings("unchecked")

public static void setDbType(String dbType) {

contextHolder.set(dbType)

}

public static String getDbType() {

return (String) contextHolder.get()

}

public static void clearDbType() {

contextHolder.remove()

}

}

3. 配置动态数据源

<!--将DynamicDataSource Bean加入到Spring的上下文xml配置文件中去,同时配置DynamicDataSource的targetDataSources(多数据源目标)属性的Map映射。-->

<bean id="dataSource" class="cn.com.core.datasource.DynamicDataSource">

<property name="targetDataSources">

<map key-type="java.lang.String">

<entry key="masterDataSource" value-ref="masterDataSource" />

<entry key="slaveDataSource" value-ref="slaveDataSource" />

</map>

</property>

<property name="defaultTargetDataSource" ref="masterDataSource"/>

</bean>

4.使用动态数据源(hibernate)

<bean id="sessionFactory"

class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">

<property name="dataSource" ref="dataSource" />

<property name="lobHandler" ref="lobHandler"/>

<property name="eventListeners">

<map>

<entry key="post-insert">

<ref bean="logListener"/>

</entry>

<entry key="post-update">

<ref bean="logListener"/>

</entry>

<entry key="post-delete">

<ref bean="logListener"/>

</entry>

</map>

</property>

<property name="hibernateProperties">

<props>

<prop key="hibernate.dialect">

org.hibernate.dialect.Oracle10gDialect

<!-- org.hibernate.dialect.OracleDerbyDialect -->

</prop>

<prop key="hibernate.show_sql">true</prop>

<!-- <prop key="hibernate.generate_statistics">true</prop>-->

<prop key="hibernate.connection.release_mode">

auto

</prop>

<prop key="hibernate.autoReconnect">true</prop>

<!--

<prop key="hibernate.hbm2ddl.auto">update</prop>

-->

<prop key="hibernate.cache.use_second_level_cache">false</prop>

<prop key="hibernate.cache.provider_class">

org.hibernate.cache.EhCacheProvider

</prop>

<prop key="hibernate.cache.use_query_cache">false</prop>

</props>

</property>

</bean>

使用Hibernate时的事务管理配置示例:

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">

<property name="sessionFactory" ref="sessionFactory" />

bean>


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/sjk/6460852.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-03-22
下一篇 2023-03-22

发表评论

登录后才能评论

评论列表(0条)

保存