在我们的实际项目中,有可能是使用多个数据库(也就是多数据源)的场景,那么在多数据源的场景下,需要怎么配置以及解决事务问题呢?
话不多说,直接上代码:
pom.xml文件配置
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4 <modelVersion>4.0.0</modelVersion> 5 <parent> 6 <groupId>org.springframework.boot</groupId> 7 <artifactId>spring-boot-starter-parent</artifactId> 8 <version>2.2.10.RELEASE</version> 9 <relativePath/> 10 </parent> 11 <groupId>com.wuwu</groupId> 12 <artifactId>cboot</artifactId> 13 <version>0.0.1-SNAPSHOT</version> 14 <name>cboot</name> 15 <description>this project for Spring Boot</description> 16 17 <properties> 18 <java.version>1.8</java.version> 19 </properties> 20 21 <dependencies> 22 <dependency> 23 <groupId>org.springframework.boot</groupId> 24 <artifactId>spring-boot-starter-data-jdbc</artifactId> 25 </dependency> 26 <dependency> 27 <groupId>org.springframework.boot</groupId> 28 <artifactId>spring-boot-starter-web</artifactId> 29 </dependency> 30 <!-- mybatis --> 31 <dependency> 32 <groupId>org.mybatis.spring.boot</groupId> 33 <artifactId>mybatis-spring-boot-starter</artifactId> 34 <version>2.0.0</version> 35 </dependency> 36 <dependency> 37 <groupId>org.projectlombok</groupId> 38 <artifactId>lombok</artifactId> 39 <version>1.18.12</version> 40 <scope>provided</scope> 41 </dependency> 42 43 <dependency> 44 <groupId>mysql</groupId> 45 <artifactId>mysql-connector-java</artifactId> 46 <scope>runtime</scope> 47 </dependency> 48 49 <dependency> 50 <groupId>ch.qos.logback</groupId> 51 <artifactId>logback-core</artifactId> 52 <version>1.1.3</version> 53 </dependency> 54 <dependency> 55 <groupId>ch.qos.logback</groupId> 56 <artifactId>logback-access</artifactId> 57 <version>1.1.3</version> 58 </dependency> 59 <dependency> 60 <groupId>ch.qos.logback</groupId> 61 <artifactId>logback-classic</artifactId> 62 <version>1.1.3</version> 63 </dependency> 64 65 <dependency> 66 <groupId>org.springframework.boot</groupId> 67 <artifactId>spring-boot-starter-test</artifactId> 68 <scope>test</scope> 69 <exclusions> 70 <exclusion> 71 <groupId>org.junit.vintage</groupId> 72 <artifactId>junit-vintage-engine</artifactId> 73 </exclusion> 74 </exclusions> 75 </dependency> 76 </dependencies> 77 78 <build> 79 <plugins> 80 <plugin> 81 <groupId>org.springframework.boot</groupId> 82 <artifactId>spring-boot-maven-plugin</artifactId> 83 </plugin> 84 </plugins> 85 </build> 86 87 </project>
application.properties文件配置
1 server.port=8090 2 server.servlet.context-path=/ 3 4 #单数据源 5 #spring.datasource.driverClassName=com.mysql.jdbc.Driver 6 #spring.datasource.url=jdbc:mysql://localhost:3306/cboot?useUnicode=true&amp;characterEncoding=UTF-8 7 #spring.datasource.username=root 8 #spring.datasource.password=root 9 10 #多数据源-订单 11 spring.datasource.order.driverClassName=com.mysql.jdbc.Driver 12 spring.datasource.order.jdbc-url=jdbc:mysql://localhost:3306/cboot?useUnicode=true&amp;characterEncoding=UTF-8 13 spring.datasource.order.username=root 14 spring.datasource.order.password=root 15 16 #多数据源-会员 17 spring.datasource.member.driverClassName=com.mysql.jdbc.Driver 18 spring.datasource.member.jdbc-url=jdbc:mysql://localhost:3306/cboot1?useUnicode=true&amp;characterEncoding=UTF-8 19 spring.datasource.member.username=root 20 spring.datasource.member.password=root
注意:多数据源的情况下在连接数据源地址的时,将常用的spring.datasource.url换成spring.datasource.jdbc-url,否则会报jdbcUrl is required with driverClassName的错误,如下图:
会员数据源配置
1 package com.wuwu.cboot.config; 2 3 import lombok.SneakyThrows; 4 import org.apache.ibatis.session.SqlSessionFactory; 5 import org.mybatis.spring.SqlSessionFactoryBean; 6 import org.mybatis.spring.SqlSessionTemplate; 7 import org.mybatis.spring.annotation.MapperScan; 8 import org.springframework.beans.factory.annotation.Qualifier; 9 import org.springframework.boot.context.properties.ConfigurationProperties; 10 import org.springframework.boot.jdbc.DataSourceBuilder; 11 import org.springframework.context.annotation.Bean; 12 import org.springframework.context.annotation.Configuration; 13 import org.springframework.jdbc.datasource.DataSourceTransactionManager; 14 15 import javax.sql.DataSource; 16 17 /** 18 * @Description:会员数据源配置 19 * @author: wph 20 * @Version: 1.0 21 * @date: 2021/1/12 22 */ 23 24 @Configuration 25 @MapperScan(basePackages = "com.wuwu.cboot.member.mapper",sqlSessionTemplateRef = "memberSqlSessionTemplate") 26 public class MemberDataSourceConfig { 27 28 /** 29 * @Description: 配置会员数据源 30 * @author: wph 31 * @date: 2021/1/15 32 * @return javax.sql.DataSource 33 */ 34 @Bean("memberDataSource") 35 @ConfigurationProperties("spring.datasource.member") 36 public DataSource memberDataSource(){ 37 return DataSourceBuilder.create().build(); 38 } 39 40 /** 41 * @Description: 创建会员事务管理器 42 * @author: wph 43 * @date: 2021/1/21 44 * @param dataSource 45 * @return org.springframework.jdbc.datasource.DataSourceTransactionManager 46 */ 47 @Bean("memberTransactionManager") 48 public DataSourceTransactionManager memberTransactionManager(@Qualifier("memberDataSource") DataSource dataSource){ 49 return new DataSourceTransactionManager(dataSource); 50 } 51 52 /** 53 * @Description: 将会员sqlSessionFactory注入到容器中 54 * @author: wph 55 * @date: 2021/1/13 56 * @param dataSource 57 * @return org.apache.ibatis.session.SqlSessionFactory 58 */ 59 @SneakyThrows(Exception.class) 60 @Bean("memberSqlSessionFactory") 61 public SqlSessionFactory memberSqlSessionFactory(@Qualifier("memberDataSource")DataSource dataSource){ 62 SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); 63 sqlSessionFactoryBean.setDataSource(dataSource); 64 return sqlSessionFactoryBean.getObject(); 65 } 66 67 /** 68 * @Description: 创建SqlSessionTemplate模板 69 * @author: wph 70 * @date: 2021/1/18 71 * @param sqlSessionFactory 72 * @return org.mybatis.spring.SqlSessionTemplate 73 */ 74 @Bean("memberSqlSessionTemplate") 75 public SqlSessionTemplate memberSqlSessionTemplate(@Qualifier("memberSqlSessionFactory") SqlSessionFactory sqlSessionFactory){ 76 return new SqlSessionTemplate(sqlSessionFactory); 77 } 78 79 }
订单数据源配置
1 package com.wuwu.cboot.config; 2 3 import lombok.SneakyThrows; 4 import org.apache.ibatis.session.SqlSessionFactory; 5 import org.mybatis.spring.SqlSessionFactoryBean; 6 import org.mybatis.spring.SqlSessionTemplate; 7 import org.mybatis.spring.annotation.MapperScan; 8 import org.springframework.beans.factory.annotation.Qualifier; 9 import org.springframework.boot.context.properties.ConfigurationProperties; 10 import org.springframework.boot.jdbc.DataSourceBuilder; 11 import org.springframework.context.annotation.Bean; 12 import org.springframework.context.annotation.Configuration; 13 import org.springframework.jdbc.datasource.DataSourceTransactionManager; 14 15 import javax.sql.DataSource; 16 17 /** 18 * @Description:订单数据源配置 19 * @author: wph 20 * @Version: 1.0 21 * @date: 2021/1/15 22 */ 23 @Configuration 24 @MapperScan(basePackages = "com.wuwu.cboot.order.mapper",sqlSessionFactoryRef = "orderSqlSessionFactory") 25 public class OrderDataSourceConfig { 26 27 /** 28 * @Description: 配置订单数据源 29 * @author: wph 30 * @date: 2021/1/15 31 * @param orderConfig 32 * @return javax.sql.DataSource 33 */ 34 @Bean("orderDataSource") 35 @ConfigurationProperties(prefix = "spring.datasource.order") 36 public DataSource orderDataSource(){ 37 return DataSourceBuilder.create().build(); 38 } 39 40 /** 41 * @Description: 创建会员事务管理器 42 * @author: wph 43 * @date: 2021/1/21 44 * @param dataSource 45 * @return org.springframework.jdbc.datasource.DataSourceTransactionManager 46 */ 47 @Bean("orderTransactionManager") 48 public DataSourceTransactionManager orderTransactionManager(@Qualifier("orderDataSource") DataSource dataSource){ 49 return new DataSourceTransactionManager(dataSource); 50 } 51 52 /** 53 * @Description: 配置session工厂 54 * @author: wph 55 * @date: 2021/1/18 56 * @param dataSource 57 * @return org.apache.ibatis.session.SqlSessionFactory 58 */ 59 @SneakyThrows 60 @Bean("orderSqlSessionFactory") 61 public SqlSessionFactory orderSqlSessionFactory(@Qualifier("orderDataSource") DataSource dataSource){ 62 SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); 63 sqlSessionFactoryBean.setDataSource(dataSource); 64 return sqlSessionFactoryBean.getObject(); 65 } 66 67 /** 68 * @Description: 创建SqlSessionTemplate模板 69 * @author: wph 70 * @date: 2021/1/18 71 * @param sqlSessionFactory 72 * @return org.mybatis.spring.SqlSessionTemplate 73 */ 74 @Bean("orderSqlSessionTemplate") 75 public SqlSessionTemplate orderSqlSessionTemplate(@Qualifier("orderSqlSessionFactory") SqlSessionFactory sqlSessionFactory){ 76 return new SqlSessionTemplate(sqlSessionFactory); 77 } 78 79 }
Controller层
1 package com.wuwu.cboot.controller; 2 3 import com.wuwu.cboot.service.MemberService; 4 import org.springframework.beans.factory.annotation.Autowired; 5 import org.springframework.web.bind.annotation.RequestMapping; 6 import org.springframework.web.bind.annotation.RestController; 7 8 /** 9 * @Description:会员控制层 10 * @author: wph 11 * @Version: 1.0 12 * @date: 2020/10/26 13 */ 14 @RestController 15 public class MemberController { 16 17 @Autowired 18 private MemberService memberService; 19 20 /** 21 * @Description: 新增会员用户 22 * @author: wph 23 * @date: 2021/1/20 24 * @param age 25 * @param name 26 * @return java.lang.Object 27 */ 28 @RequestMapping("/saveMember") 29 public Object saveMember(int age,String name){ 30 return memberService.saveMember(age,name); 31 } 32 }
service层实现
1 package com.wuwu.cboot.service.impl; 2 3 import com.wuwu.cboot.member.mapper.MemberMapper; 4 import com.wuwu.cboot.order.mapper.OrderMemberMapper; 5 import com.wuwu.cboot.service.MemberService; 6 import org.springframework.stereotype.Service; 7 import org.springframework.transaction.annotation.Transactional; 8 9 import javax.annotation.Resource; 10 11 /** 12 * @Description: 13 * @author: wph 14 * @Version: 1.0 15 * @date: 2021/1/12 16 */ 17 @Service 18 public class MemberServiceImpl implements MemberService { 19 20 @Resource 21 private OrderMemberMapper orderMemberMapper; 22 @Resource 23 private MemberMapper memberMapper; 24 25 @Transactional("memberTransactionManager") 26 public String saveMember(int age,String name){ 27 //调用会员接口 28 int i = memberMapper.saveMember(age, name); 29 return i == 1 ? "true" : "false"; 30 } 31 }
如果需要事务的控制,只需要在方法上方加上@Transactional("对应的事务管理器")注解即可(如代码所示),看到这里你可能会有疑问:如果我一个service方法内调用了不同的数据源,事务怎么解决呢?
别急,请继续往下看
mapper层
1 package com.wuwu.cboot.member.mapper; 2 3 import org.apache.ibatis.annotations.Insert; 4 import org.apache.ibatis.annotations.Mapper; 5 6 @Mapper 7 public interface MemberMapper { 8 9 @Insert("insert into member (age,name) values(#{age},#{name})") 10 int saveMember(int age, String name); 11 }
mapper接口采用了分包结构的形式,分别对应会员数据源MemberDataSourceConfig以及订单数据源OrderDataSourceConfig中的注解MapperScan地址
最后再附上项目结构以及启动类的代码截图
关于多数据源的配置,文章到此就已结束了。
但是文章提到一个疑问:如果一个service方法内调用了不同的数据源,事务怎么解决呢?我们修改一下MemberServiceImpl类里面的代码
这种情况下呢,就会出现:多数据源分布式事务问题
A服务中调用了B服务的接口,如果A服务执行时报错,那么A服务对应的事务会回滚,B服务不会回滚,那这样子是肯定不行的。
那如何解决“多数据源分布式事务问题”呢?请移步下一篇文章:springboot2.0 解决多数据源分布式事务问题