微服务框架(3)----基于SpringBoot2.0版本框架-多数据源分布式事务管理(jta+atomikos)

SpringBoot整合MyBatis

  • 可以不在mapper层加上@Mapper,直接加@MapperScan(basePackages={})扫包,若有多个包,用逗号隔开
  • 推荐使用@MapperScan的方式
  • 代码演示:
    (1)pom文件
	<parent>
		<groupId>org.springframework.bootgroupId>
		<artifactId>spring-boot-starter-parentartifactId>
		<version>2.0.0.RELEASEversion>
	parent>
	<dependencies>
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starterartifactId>
		dependency>
		
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-testartifactId>
			<scope>testscope>
		dependency>
		<dependency>
			<groupId>org.mybatis.spring.bootgroupId>
			<artifactId>mybatis-spring-boot-starterartifactId>
			<version>1.1.1version>
		dependency>
		
		<dependency>
			<groupId>mysqlgroupId>
			<artifactId>mysql-connector-javaartifactId>
		dependency>
		
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-webartifactId>
		dependency>
	dependencies>

(2)application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

(3)Mapper代码:

public interface UserMapper {
	@Select("SELECT * FROM USERS WHERE NAME = #{name}")
	User findByName(@Param("name") String name);
	@Insert("INSERT INTO USERS(NAME, AGE) VALUES(#{name}, #{age})")
	int insert(@Param("name") String name, @Param("age") Integer age);
}

(4)启动类,这里我们直接用的是@MapperScan而没有在每一个接口上用@Mapper

@MapperScan("com.itmayiedu.mapper")
@SpringBootApplication
public class MybatisApp {

	public static void main(String[] args) {
		SpringApplication.run(MybatisApp.class, args);
	}

}

SpringBoot的事务管理,整合@transactional

  • 单数据源的情况下,SpringBoot默认集成事务,只要在方法上或者类的上面加上@Transactional即可,默认就是开启的
  • 事务分为声明事务和编程事务
  • 声明事务是根据编程事务来的(编程事务就是指的是手动commit等)
  • 事务的原理就是使用AOP的环绕通知进行拦截
  • 使用Spring事务的注意事项就是不要进行try-catch,需要将异常抛出给外层,让异常的AOP可以捕获到,进行rollback
  • 推荐使用注解版本的事务,即@transactional

SpringBoot多数据源

  • 在一个项目中有不同库的JDBC连接,理论上可以有无限个连接,具体的实际情况依赖于内存的大小
  • 垂直拆分和水平拆分:
  • 垂直拆分属于分库,水平拆分属于分表
  • 多数据源应该如何去划分?
  • 根据包来进行划分,就是说包名不同,数据源不同,分包(根据业务来的)
  • 使用注解的方式划分多数据源
  • 一般情况下根据分包用的比较多:
    (1)com.xiyou.test01----datasource01
    (2)com.xiyou.test02----datasource02
    有点类似于多个不同的jar包,多个不同的业务需求,存放在同一个项目中
  • 不推荐使用注解的方式实现多数据源,太麻烦

使用分包的方式实现拆分数据源

  • 不同的数据源建立不同的包
  • application.properties中也要自己去实现数据库的连接,不能使用默认的数据库连接的key
  • @Configuration表示是一个配置信息,将其注册到Spring中
  • 如果我们要加载mapping文件的时候需要加上
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/test1/*.xml"));
  • 不同数据源的xml文件(mapping文件)写在不同的数据源配置方法中

微服务框架(3)----基于SpringBoot2.0版本框架-多数据源分布式事务管理(jta+atomikos)_第1张图片

  • 重要)SpringBoot的2.0版本,即使我们不写@Primary也不会报错。在SpringBoot1.5等版本的时候,如果不指定主数据源(默认数据源)的时候就会报错

  • java代码实现:
    (1)代码架构:
    微服务框架(3)----基于SpringBoot2.0版本框架-多数据源分布式事务管理(jta+atomikos)_第2张图片
    这里以不同的包进行区分,不同的包名下链接不同的数据源
    (2)application.properties

server.port=8081

###datasource1 数据源1
spring.datasource.test1.driver-class-name = com.mysql.jdbc.Driver
spring.datasource.test1.jdbc-url = jdbc:mysql://localhost:3307/test?useUnicode=true&characterEncoding=utf-8
spring.datasource.test1.username = root
spring.datasource.test1.password = root

###datasource2 数据源2
spring.datasource.test2.driver-class-name = com.mysql.jdbc.Driver
spring.datasource.test2.jdbc-url = jdbc:mysql://localhost:3307/test_rc?useUnicode=true&characterEncoding=utf-8
spring.datasource.test2.username = root
spring.datasource.test2.password = root

(3)config配置(重要)

  • DataSourceConfig1:链接一个数据库
package com.xiyou.datasource.config;

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.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
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.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

@Configuration
// 表示mapper1包由test1SqlSessionFactory数据源注入
@MapperScan(basePackages = "com.xiyou.datasource.mapper.mapper1", sqlSessionFactoryRef = "test1SqlSessionFactory")
public class DataSourceConfig1 {

    /**
     * 规定bean的名字,设置注入的数据来源
     * @return
     */
    @Bean(name = "test1DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.test1")
    public DataSource testDataSource1(){
        return DataSourceBuilder.create().build();
    }

    /**
     * 创建sqlSessionFactory,将自定义的数据源进行注入
     * @param dataSource
     * @return
     */
    @Bean("test1SqlSessionFactory")
    @Primary
    public SqlSessionFactory test1SqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        // 如果使用的是xml的形式写sql的话,需要加上下面的配置
        // bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper1/test1/*.xml"));
        return bean.getObject();
    }

    /**
     * 注入事务的管理
     * @param dataSource
     * @return
     */
    @Bean(name = "test1TransactionManager")
    public DataSourceTransactionManager transactionManager(@Qualifier("test1DataSource") DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }

    /**
     * SqlSession模板生成
     * @return
     */
    @Bean(name = "test1SqlSessionTemplate")
    @Primary
    public SqlSessionTemplate test1SqlSessionTemplate(@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception{
        return new SqlSessionTemplate(sqlSessionFactory);
    }

}

  • DataSourceConfig2:链接另一个数据库
package com.xiyou.datasource.config;

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.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

@Configuration
// 表示mapper2包由test2SqlSessionFactory数据源注入
@MapperScan(basePackages = "com.xiyou.datasource.mapper.mapper2", sqlSessionFactoryRef = "test2SqlSessionFactory")
public class DataSourceConfig2 {

    /**
     * 规定bean的名字,设置注入的数据来源
     * @return
     */
    @Bean(name = "test2DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.test2")
    public DataSource testDataSource2(){
        return DataSourceBuilder.create().build();
    }

    /**
     * 创建sqlSessionFactory,将自定义的数据源进行注入
     * @param dataSource
     * @return
     */
    @Bean("test2SqlSessionFactory")
    public SqlSessionFactory test2SqlSessionFactory(@Qualifier("test2DataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        // 如果使用的是xml的形式写sql的话,需要加上下面的配置
        // bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/test2/*.xml"));
        return bean.getObject();
    }

    /**
     * 注入事务的管理
     * @param dataSource
     * @return
     */
    @Bean(name = "test2TransactionManager")
    public DataSourceTransactionManager transactionManager(@Qualifier("test2DataSource") DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }

    /**
     * SqlSession模板生成
     * @return
     */
    @Bean(name = "test2SqlSessionTemplate")
    public SqlSessionTemplate test2SqlSessionTemplate(@Qualifier("test2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception{
        return new SqlSessionTemplate(sqlSessionFactory);
    }

}

  • UserMapper1
package com.xiyou.datasource.mapper.mapper1;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;

/**
 * 数据源1 对数据库进行操作
 */
public interface User1Mapper {

    @Insert("insert into t_user(userName, age) values(#{name}, #{age})")
    public int addUser(@Param("name") String name, @Param("age") Integer age);
}

  • UserMapper2
package com.xiyou.datasource.mapper.mapper2;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;

/**
 * 数据源2 对数据库进行操作
 */
public interface User2Mapper {

    @Insert("insert into user2(NAME, pwd) values(#{name}, #{pwd})")
    public int addUser(@Param("name") String name, @Param("pwd") String pwd);
}

  • UserService1
package com.xiyou.datasource.service;

import org.springframework.transaction.annotation.Transactional;

/**
 * 用户的接口类
 */
@Transactional(transactionManager = "test1TransactionManager")
public interface UserService1 {
    /**
     * 新增用户(数据源1操作)
     * @param name
     * @param age
     */
    public int addUser(String name, Integer age);

}

  • UserService2
package com.xiyou.datasource.service;

import org.springframework.transaction.annotation.Transactional;

/**
 * 用户的接口类
 */
@Transactional(transactionManager = "test2TransactionManager")
public interface UserService2 {
    /**
     * 新增用户(数据源2操作)
     * @param name
     * @param pwd
     * @return
     */
    public int addUser(String name, String pwd);
}

  • UserService1Impl
package com.xiyou.datasource.service.impl;

import com.xiyou.datasource.mapper.mapper1.User1Mapper;
import com.xiyou.datasource.service.UserService1;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


/**
 * 数据源1的实现
 */
@Service
public class UserService1Impl implements UserService1 {

    @Autowired
    private User1Mapper user1Mapper;

    @Override
    public int addUser(String name, Integer age) {
        int i = user1Mapper.addUser(name, age);
        int a = 1/0;
        return i;
    }
}

  • UserService2Impl
package com.xiyou.datasource.service.impl;

import com.xiyou.datasource.mapper.mapper2.User2Mapper;
import com.xiyou.datasource.service.UserService2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 数据源2的实现
 */
@Service
public class UserService2Impl implements UserService2 {

    @Autowired
    private User2Mapper user2Mapper;

    @Override
    public int addUser(String name, String pwd) {
        int i = user2Mapper.addUser(name, pwd);
        return i;
    }
}

  • 启动类
package com.xiyou.datasource;


import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
// 扫描指定包
@MapperScan(basePackages = {"com.xiyou.datasource.mapper"})
public class MyDatasourceApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyDatasourceApplication.class, args);
    }

}

多数据源事务管理机制(代码上面已经实现)

  • 不同的数据库(不同的数据源),如果不使用全局事务管理器(分布式事务),就应该使用多个事务
  • 多数据源的情况下使用事务必须指明使用的是哪一个数据源的事务,否则会因为不知道使用哪一个事务报错。
  • 在多数据的情况下,使用@Transactional注解的时候,应该指明事务的管理者(就是指明使用哪一个数据库事务)
@Transactional(transactionManager = "test2TransactionManager")

多数据源分布式事务

  • 同一个事务下有多个数据源的连接,事务要控制多个数据源的提交与回滚
    微服务框架(3)----基于SpringBoot2.0版本框架-多数据源分布式事务管理(jta+atomikos)_第3张图片
    如上述代码所示的话,因为一个方法中有数据源1和数据源2,但是事务只能管理数据源2,因此数据源1可以成功入库,数据源2则会回滚。

  • 传统的分布式事务解决方案jta+atomikos:将多个数据源事务,注册到同一个全局事务中(几乎不用,了解下行了,性能非常不好,一般就用在多数据源项目,微服务项目中一定不去使用)

使用jta+atomikos解决多数据源下的分布式事务

  • 新增pom依赖
<dependency>
	<groupId>org.springframework.bootgroupId>
	<artifactId>spring-boot-starter-jta-atomikosartifactId>
dependency>

  • 配置文件
# Mysql 1
mysql.datasource.test1.url = jdbc:mysql://localhost:3306/test01?useUnicode=true&characterEncoding=utf-8
mysql.datasource.test1.username = root
mysql.datasource.test1.password = root

mysql.datasource.test1.minPoolSize = 3
mysql.datasource.test1.maxPoolSize = 25
mysql.datasource.test1.maxLifetime = 20000
mysql.datasource.test1.borrowConnectionTimeout = 30
mysql.datasource.test1.loginTimeout = 30
mysql.datasource.test1.maintenanceInterval = 60
mysql.datasource.test1.maxIdleTime = 60



# Mysql 2
mysql.datasource.test2.url =jdbc:mysql://localhost:3306/test02?useUnicode=true&characterEncoding=utf-8
mysql.datasource.test2.username =root
mysql.datasource.test2.password =root

mysql.datasource.test2.minPoolSize = 3
mysql.datasource.test2.maxPoolSize = 25
mysql.datasource.test2.maxLifetime = 20000
mysql.datasource.test2.borrowConnectionTimeout = 30
mysql.datasource.test2.loginTimeout = 30
mysql.datasource.test2.maintenanceInterval = 60
mysql.datasource.test2.maxIdleTime = 60

  • 实体类(DBConfig1)
@Data
@ConfigurationProperties(prefix = "mysql.datasource.test1")
public class DBConfig1 {

	private String url;
	private String username;
	private String password;
	private int minPoolSize;
	private int maxPoolSize;
	private int maxLifetime;
	private int borrowConnectionTimeout;
	private int loginTimeout;
	private int maintenanceInterval;
	private int maxIdleTime;
	private String testQuery;
}

  • 实体类(DBConfig2)
@Data
@ConfigurationProperties(prefix = "mysql.datasource.test2")
public class DBConfig2 {

	private String url;
	private String username;
	private String password;
	private int minPoolSize;
	private int maxPoolSize;
	private int maxLifetime;
	private int borrowConnectionTimeout;
	private int loginTimeout;
	private int maintenanceInterval;
	private int maxIdleTime;
	private String testQuery;
}

  • MybatisConfig1
@Configuration
// basePackages 最好分开配置 如果放在同一个文件夹可能会报错
@MapperScan(basePackages = "com.itmayiedu.test01", sqlSessionTemplateRef = "testSqlSessionTemplate")
public class MyBatisConfig1 {

	// 配置数据源
	@Primary
	@Bean(name = "testDataSource")
	public DataSource testDataSource(DBConfig1 testConfig) throws SQLException {
		MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
		mysqlXaDataSource.setUrl(testConfig.getUrl());
		mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
		mysqlXaDataSource.setPassword(testConfig.getPassword());
		mysqlXaDataSource.setUser(testConfig.getUsername());
		mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

		AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
		xaDataSource.setXaDataSource(mysqlXaDataSource);
		xaDataSource.setUniqueResourceName("testDataSource");

		xaDataSource.setMinPoolSize(testConfig.getMinPoolSize());
		xaDataSource.setMaxPoolSize(testConfig.getMaxPoolSize());
		xaDataSource.setMaxLifetime(testConfig.getMaxLifetime());
		xaDataSource.setBorrowConnectionTimeout(testConfig.getBorrowConnectionTimeout());
		xaDataSource.setLoginTimeout(testConfig.getLoginTimeout());
		xaDataSource.setMaintenanceInterval(testConfig.getMaintenanceInterval());
		xaDataSource.setMaxIdleTime(testConfig.getMaxIdleTime());
		xaDataSource.setTestQuery(testConfig.getTestQuery());
		return xaDataSource;
	}

	@Primary
	@Bean(name = "testSqlSessionFactory")
	public SqlSessionFactory testSqlSessionFactory(@Qualifier("testDataSource") DataSource dataSource)
			throws Exception {
		SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
		bean.setDataSource(dataSource);
		return bean.getObject();
	}

	@Primary
	@Bean(name = "testSqlSessionTemplate")
	public SqlSessionTemplate testSqlSessionTemplate(
			@Qualifier("testSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
		return new SqlSessionTemplate(sqlSessionFactory);
	}
}

  • MybatisConfig2

@Configuration
@MapperScan(basePackages = "com.itmayiedu.test02", sqlSessionTemplateRef = "test2SqlSessionTemplate")
public class MyBatisConfig2 {

	// 配置数据源
	@Bean(name = "test2DataSource")
	public DataSource testDataSource(DBConfig2 testConfig) throws SQLException {
		MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
		mysqlXaDataSource.setUrl(testConfig.getUrl());
		mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
		mysqlXaDataSource.setPassword(testConfig.getPassword());
		mysqlXaDataSource.setUser(testConfig.getUsername());
		mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

		AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
		xaDataSource.setXaDataSource(mysqlXaDataSource);
		xaDataSource.setUniqueResourceName("test2DataSource");

		xaDataSource.setMinPoolSize(testConfig.getMinPoolSize());
		xaDataSource.setMaxPoolSize(testConfig.getMaxPoolSize());
		xaDataSource.setMaxLifetime(testConfig.getMaxLifetime());
		xaDataSource.setBorrowConnectionTimeout(testConfig.getBorrowConnectionTimeout());
		xaDataSource.setLoginTimeout(testConfig.getLoginTimeout());
		xaDataSource.setMaintenanceInterval(testConfig.getMaintenanceInterval());
		xaDataSource.setMaxIdleTime(testConfig.getMaxIdleTime());
		xaDataSource.setTestQuery(testConfig.getTestQuery());
		return xaDataSource;
	}

	@Bean(name = "test2SqlSessionFactory")
	public SqlSessionFactory testSqlSessionFactory(@Qualifier("test2DataSource") DataSource dataSource)
			throws Exception {
		SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
		bean.setDataSource(dataSource);
		return bean.getObject();
	}

	@Bean(name = "test2SqlSessionTemplate")
	public SqlSessionTemplate testSqlSessionTemplate(
			@Qualifier("test2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
		return new SqlSessionTemplate(sqlSessionFactory);
	}
}

  • 启动的时候需要给启动类加上
@EnableConfigurationProperties(value = {DBConfig1.class. DBConfig2.class})

你可能感兴趣的:(蚂蚁课堂的视频笔记)