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

SpringBoot整合MyBatis

  • 可以不在mapper层加上@Mapper,直接加@MapperScan(basePackages={})扫包,若有多个包,用逗号隔开
  • 推荐使用@MapperScan的方式
  • 代码演示:
    (1)pom文件
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.0.RELEASE</version>
	</parent>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
		<!-- 测试 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.1.1</version>
		</dependency>
		<!-- mysql 依赖 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
		<!-- springboot-web组件 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</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文件)写在不同的数据源配置方法中

在这里插入图片描述

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

  • java代码实现:
    (1)代码架构:
    在这里插入图片描述
    这里以不同的包进行区分,不同的包名下链接不同的数据源
    (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")

多数据源分布式事务

  • 同一个事务下有多个数据源的连接,事务要控制多个数据源的提交与回滚
    在这里插入图片描述
    如上述代码所示的话,因为一个方法中有数据源1和数据源2,但是事务只能管理数据源2,因此数据源1可以成功入库,数据源2则会回滚。

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

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

  • 新增pom依赖
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</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})
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Boot 是一个用于快速构建 Java 应用程序的框架。它可以与多种其他框架和组件进行整合,以实现更丰富的功能。在这里,我们将讨论如何使用 Spring Boot 整合 Druid、MyBatis、JTA 分布式事务以及多数据源,同时使用 AOP 注解实现动态切换。 首先,我们可以在 Spring Boot 中集成 Druid 数据源。Druid 是一个高性能的 JDBC 连接池,可以提供监控和统计功能。我们可以通过在 pom.xml 文件中添加相关的依赖,并在 application.properties 文件中配置数据源信息,来实现 Druid 的集成。 接下来,我们可以整合 MyBatis 框架,它是一种优秀的持久化解决方案。我们可以使用 MyBatis 来操作数据库,并将其与 Druid 数据源进行整合。为此,我们需要在 pom.xml 文件中添加 MyBatis 和 MyBatis-Spring 的依赖,并配置 MyBatis 的相关配置文件。 此外,我们还可以使用 JTA(Java Transaction API)实现分布式事务JTA 可以在分布式环境中协调多个参与者的事务操作。我们可以在 pom.xml 文件中添加 JTA 的依赖,并在 Spring Boot 的配置文件中配置 JTA 的相关属性,以实现分布式事务的支持。 在实现多数据源时,我们可以使用 Spring Boot 的 AbstractRoutingDataSource 来实现动态切换数据源。这个类可以根据当前线程或其他条件选择不同的数据源来进行数据操作。我们可以通过继承 AbstractRoutingDataSource 并实现 determineCurrentLookupKey() 方法来指定当前数据源的 key。然后,在配置文件中配置多个数据源,并将数据源注入到 AbstractRoutingDataSource 中,从而实现动态切换。 最后,我们可以使用 AOP(Aspect Oriented Programming)注解来实现动态切换。AOP 是一种编程范式,可以通过在代码中插入特定的切面(Aspect)来实现横切关注点的处理。我们可以在代码中使用注解来标记需要切换数据源的方法,然后使用 AOP 技术来拦截这些方法,并根据注解中指定的数据源信息来进行数据源的切换。 综上所述,通过整合 Druid、MyBatis、JTA 分布式事务以及多数据源,并使用 AOP 注解实现动态切换,我们可以在 Spring Boot 中实现强大而灵活的应用程序。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值