SpringBoot學習小結之多數據源
本文針對在Springboot使用多數據源的情況下, 滿足分佈式事務進行總結
一、多數據源
1. 數據庫
使用mysql數據庫,這裏有分別位於兩個數據庫的兩張表student.student, teacher.teacher, SQL語句如下
create database student;
use student;
-- ----------------------------
-- Table structure for student
-- ----------------------------
CREATE TABLE `student` (
`id` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
create database teacher;
use teacher;
-- ----------------------------
-- Table structure for teacher
-- ----------------------------
CREATE TABLE `teacher` (
`id` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.aabond</groupId>
<artifactId>mutidatasource</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mutidataresource</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<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>2.0.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.application.yml
spring:
datasource:
student:
url: jdbc:mysql://localhost:3306/student?useUnicode=true&characterEncoding=utf-8&useSSL=false
user: root
password:
driver-class-name: com.mysql.jdbc.Driver
teacher:
url: jdbc:mysql://localhost:3306/teacher?useUnicode=true&characterEncoding=utf-8&useSSL=false
user: root
password:
driver-class-name: com.mysql.jdbc.Driver
4. 實體類
package com.aabond.mutidatasource.enity;
/**
* @className: Student
* @description: TODO
* @author: root
* @date: 2019/12/10 19:49
* @version: v1.0.0
**/
public class Student {
private Integer id;
private String name;
// 省略get set toString方法
}
package com.aabond.mutidatasource.enity;
/**
* @className: Teacher
* @description: TODO
* @author: root
* @date: 2019/12/10 19:50
* @version: v1.0.0
**/
public class Teacher {
private Integer id;
private String name;
// 省略get set toString方法
}
5.dao層
需要dao目錄下建兩個包,分別存儲兩個數據庫的dao文件
package com.aabond.mutidatasource.dao.student;
import com.aabond.mutidatasource.enity.Student;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @className: StudentDao
* @description: TODO
* @author: root
* @date: 2019/12/10 19:51
* @version: v1.0.0
**/
@Repository
public interface StudentDao {
@Delete("delete from student where id = #{id}")
void deleteById(Integer id);
@Insert("insert into student (`id`, `name`) values (#{id}, #{name})")
void insert(Student student);
@Select("select id, name from student")
List<Student> findAll();
}
package com.aabond.mutidatasource.dao.teacher;
import com.aabond.mutidatasource.enity.Teacher;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @className: TeacherDao
* @description: TODO
* @author: root
* @date: 2019/12/10 19:52
* @version: v1.0.0
**/
@Repository
public interface TeacherDao {
@Delete("delete from teacher where id = #{id}")
void deleteById(Integer id);
@Insert("insert into teacher (`id`, `name`) values (#{id}, #{name})")
void insert(Teacher teacher);
@Select("select id, name from teacher")
List<Teacher> findAll();
}
6.Service層
package com.aabond.mutidatasource.service;
import com.aabond.mutidatasource.enity.Student;
import com.aabond.mutidatasource.enity.Teacher;
/**
* @className: TestService
* @description: TODO
* @author: root
* @date: 2019/12/10 19:53
* @version: v1.0.0
**/
public interface TestService {
void normalDelete(Integer id);
void exceptionDelete(Integer id) throws Exception;
void normalInsert(Student student, Teacher teacher);
void exceptionInsert(Student student, Teacher teacher) throws Exception;
}
package com.aabond.mutidatasource.service.impl;
import com.aabond.mutidatasource.dao.student.StudentDao;
import com.aabond.mutidatasource.dao.teacher.TeacherDao;
import com.aabond.mutidatasource.enity.Student;
import com.aabond.mutidatasource.enity.Teacher;
import com.aabond.mutidatasource.service.TestService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @className: TestServiceImpl
* @description: TODO
* @author: root
* @date: 2019/12/10 19:56
* @version: v1.0.0
**/
@Service
public class TestServiceImpl implements TestService {
private static final Logger logger = LoggerFactory.getLogger(TestServiceImpl.class);
@Autowired
private StudentDao studentDao;
@Autowired
private TeacherDao teacherDao;
@Override
public void normalDelete(Integer id) {
studentDao.deleteById(id);
teacherDao.deleteById(id);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void exceptionDelete(Integer id) throws Exception {
studentDao.deleteById(id);
teacherDao.deleteById(id);
int i = 1 / 0;
}
@Override
public void normalInsert(Student student, Teacher teacher) {
studentDao.insert(student);
teacherDao.insert(teacher);
}
@Override
public void exceptionInsert(Student student, Teacher teacher) {
normalInsert(student, teacher);
int i = 1 / 0;
}
}
7. Config配置類
package com.aabond.mutidatasource.config;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
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 javax.sql.DataSource;
/**
* @className: StudentDataSourceConfig
* @description: TODO
* @author: root
* @date: 2019/12/10 19:48
* @version: v1.0.0
**/
@Configuration
@MapperScan(basePackages = "com.aabond.mutidatasource.dao.student", sqlSessionTemplateRef = "studentSqlSessionTemplate")
public class StudentDataSourceConfig {
private static final Logger logger = LoggerFactory.getLogger(StudentDataSourceConfig.class);
@Bean
@ConfigurationProperties(prefix = "spring.datasource.student")
public MysqlXADataSource studentXADataSource() {
return new MysqlXADataSource();
}
@Bean
public DataSource studentDataSource(@Qualifier("studentXADataSource") MysqlXADataSource druidXADataSource) {
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(druidXADataSource);
xaDataSource.setUniqueResourceName("studentDataSource");
return xaDataSource;
}
@Bean
public SqlSessionFactory studentSqlSessionFactory(@Qualifier("studentDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
// bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/student/*Mapper.xml"));//掃描指定目錄的xml
return bean.getObject();
}
@Bean
public SqlSessionTemplate studentSqlSessionTemplate(@Qualifier("studentSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
package com.aabond.mutidatasource.config;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
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 javax.sql.DataSource;
/**
* @className: TeacherDataSourceConfig
* @description: TODO
* @author: root
* @date: 2019/12/10 20:01
* @version: v1.0.0
**/
@Configuration
@MapperScan(basePackages = "com.aabond.mutidatasource.dao.teacher", sqlSessionTemplateRef = "teacherSqlSessionTemplate")
public class TeacherDataSourceConfig {
private static final Logger logger = LoggerFactory.getLogger(TeacherDataSourceConfig.class);
@Bean
@ConfigurationProperties(prefix = "spring.datasource.teacher")
public MysqlXADataSource teacherXADataSource() {
return new MysqlXADataSource();
}
@Bean
public DataSource teacherDataSource(@Qualifier("teacherXADataSource") MysqlXADataSource druidXADataSource) {
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(druidXADataSource);
xaDataSource.setUniqueResourceName("teacherDataSource");
return xaDataSource;
}
@Bean
public SqlSessionFactory teacherSqlSessionFactory(@Qualifier("teacherDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
// bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/teacher/*Mapper.xml"));//掃描指定目錄的xml
return bean.getObject();
}
@Bean
public SqlSessionTemplate teacherSqlSessionTemplate(@Qualifier("teacherSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
8.測試類
包含兩個測試方法,testNormal測試了兩個數據源的方法可以正常插入、刪除,testTransaction測試了發生異常情況下,兩個數據源事務能否正常運行
package com.aabond.mutidatasource;
import com.aabond.mutidatasource.dao.student.StudentDao;
import com.aabond.mutidatasource.dao.teacher.TeacherDao;
import com.aabond.mutidatasource.enity.Student;
import com.aabond.mutidatasource.enity.Teacher;
import com.aabond.mutidatasource.service.TestService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
public class MutidataresourceApplicationTests {
private static final Logger logger = LoggerFactory.getLogger(MutidataresourceApplicationTests.class);
@Autowired
private TestService testService;
@Autowired
private StudentDao studentDao;
@Autowired
private TeacherDao teacherDao;
@Test
public void testTransaction() {
testService.normalDelete(100);
Student student = new Student();
student.setId(100);
student.setName("studnet100");
Teacher teacher = new Teacher();
teacher.setId(100);
teacher.setName("teacher100");
testService.normalInsert(student, teacher);
print();
try {
testService.exceptionDelete(100);
} catch (Exception e) {
logger.info("testTransaction發生異常,{}", e);
}
print();
Assert.assertNotNull(studentDao.findAll());
Assert.assertNotNull(teacherDao.findAll());
testService.normalDelete(100);
}
public void print() {
List<Student> students = studentDao.findAll();
List<Teacher> teachers = teacherDao.findAll();
logger.info("{},{}", students, teachers);
}
@Test
public void testNormal() {
Student student = new Student();
student.setId(100);
student.setName("studnet100");
Teacher teacher = new Teacher();
teacher.setId(100);
teacher.setName("teacher100");
testService.normalInsert(student, teacher);
print();
Assert.assertNotNull(studentDao.findAll());
Assert.assertNotNull(teacherDao.findAll());
testService.normalDelete(100);
}
}