二. SpringBoot进阶

二. SpringBoot进阶

Redis集成

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置

# REDIS (RedisProperties)
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接超时时间(毫秒)
spring.redis.timeout=5000

单元测试

@SpringBootTest(classes = {App.class})
@RunWith(SpringRunner.class)
public class SpringRedisTest {
    @Resource
    private RedisTemplate<String,String> redisTemplate;

    @Test
    public void testRedis() throws Exception {
        ValueOperations<String, String> ops = redisTemplate.opsForValue();
        ops.set("name", "enjoy");
        String value = ops.get("name");
        System.out.println(value);
  }
}

RabbitMQ集成

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

配置

## rabbitmq config
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=rabbit
spring.rabbitmq.password=123456

RabbitConfig

import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitConfig {
     @Bean
     public Queue firstQueue() {
       // 创建一个队列,名称为:enjoy
        return new Queue("enjoy");
     }
}

创建消息的生产者

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Component
public class Sender {
     @Resource
     private AmqpTemplate rabbitTemplate;
     public void send() {
      rabbitTemplate.convertAndSend("enjoy", "this a messages !!!");
     }
}

创建消息的消费者

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
//定义该类需要监听的队列
@RabbitListener(queues = "enjoy")
public class Receiver {
    @RabbitHandler  //指定对消息的处理
    public void process(String msg) {
        System.out.println("receive msg : " + msg);
 }
}

新增单元测试

import cn.enjoy.App;
import cn.enjoy.mq.Sender;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = App.class)
public class RabbitmqTest {
    @Resource
    private Sender sender;
     @Test
     public void testRabbitmq() throws Exception {
         sender.send();
     }
}

Actuator监控管理

Actuator是spring boot的一个附加功能,可帮助你在应用程序生产环境时监视和管理应用程序。可以使用HTTP的各种请求来监管,审计,收集应用的运行情况.特别对于微服务管理十分有意义

缺点:没有可视化界面(Spring cloud 还会用到这功能,就可以看到界面了)

pom

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

修改application.properties文件,启动监控端点

# 加载所有的端点/默认只加载了 info / health
management.endpoints.web.exposure.include=*

# 描述信息
info.blog-url=http://hgy.len
info.author=hgy
info.version=@project.version@

重新启动,在地址栏输入

http://localhost:8080/actuator/info

Actuator访问路径

通过actuator/+端点名就可以获取相应的信息。

路径作用
/actuator/beans显示应用程序中所有Spring bean的完整列表。
/actuator/configprops显示所有配置信息。
/actuator/env陈列所有的环境变量。
/actuator/mappings显示所有@RequestMapping的url整理列表。
/actuator/health显示应用程序运行状况信息 up表示成功 down失败
/actuator/info查看自定义应用信息

自定义Starter

不管是集成redis还是RabbitMQ,甚至是前面集成mybatis已经学习了很多starter,这些starter都是springboot为我们提供的一些封装,这些starter能非常方便快捷的增加功能,并不需要很多配置,即使需要配置也就在application.properties稍微配置下就可以了。

怎么创建属于自己的starter

redis-starter插件

前面已经使用过spring-boot-starter-data-redis,这个starter是用来集成redis的,那么接下来完成一个starter,这个starter也就集成下redis

新建一个项目,这个项目不需要web功能

pom

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.2.RELEASE</version>
</parent>
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.0.1</version>
    </dependency>
</dependencies>

创建一个RedisProperties用于加载Redis需要的配置,这里为简单起见,并没有设置密码

import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "redis")
public class RedisProperties {

    private String host;
    private int port;

    public int getPort() {
        return port;
    }
    public void setPort(int port) {
        this.port = port;
    }
    public String getHost() {
        return host;
    }
    public void setHost(String host) {
        this.host = host;
    }
}

创建一个配置类,这个配置类用于加载配置,并实例化Jedis客户端

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.Jedis;

@Configuration //开启配置
@ConditionalOnClass(Jedis.class)
@EnableConfigurationProperties(RedisProperties.class) // 开启使用映射实体对象
@ConditionalOnProperty//存在对应配置信息时初始化该配置类
 (
 prefix = "redis",//存在配置前缀redis
 value = "enabled",//开启
 matchIfMissing = true//缺失检查
 )
public class RedisAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean // bean缺失才创建
    public Jedis jedis(RedisProperties redisProperties){
        return new Jedis(redisProperties.getHost(), redisProperties.getPort());
    }
}

自动化配置代码中有很多我们之前没有用到的注解配置,我们从上开始讲解

@Configuration:这个配置就不用多做解释了,我们一直在使用

@EnableConfigurationProperties:这是一个开启使用配置参数的注解,value值就是我们配置实体参数映射的ClassType,将配置实体作为配置来源。(例如: @EnableConfigurationProperties(RedisProperties.class))

SpringBoot内置条件注解

有关**@ConditionalOnXxx**相关的注解这里要系统的说下,因为这个是我们配置的关键,根据名称我们可以理解为具有Xxx条件,当然它实际的意义也是如此,条件注解是一个系列,下面我们详细做出解释

@ConditionalOnBean:当SpringIoc容器内存在指定Bean的条件

@ConditionalOnClass:当SpringIoc容器内存在指定Class的条件

@ConditionalOnExpression:基于SpEL表达式作为判断条件

@ConditionalOnJava:基于JVM版本作为判断条件

@ConditionalOnMissingBean:当SpringIoc容器内不存在指定Bean的条件

@ConditionalOnMissingClass:当SpringIoc容器内不存在指定Class的条件

@ConditionalOnNotWebApplication:当前项目不是Web项目的条件

@ConditionalOnProperty:指定的属性是否有指定的值

@ConditionalOnResource:类路径是否有指定的值

@ConditionalOnSingleCandidate:当指定Bean在SpringIoc容器内只有一个,或者虽然有多个但是指定首选的Bean

@ConditionalOnWebApplication:当前项目是Web项目的条件

以上注解都是元注解**@Conditional**演变而来的,根据不用的条件对应创建以上的具体条件注解。

到目前为止我们还没有完成自动化配置starter,我们需要了解SpringBoot运作原理后才可以完成后续编码。

Starter自动化运作原理

在注解@SpringBootApplication上存在一个开启自动化配置的注解**@EnableAutoConfiguration**来完成自动化配置,注解源码如下所示:

@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}

EnableAutoConfigurationImportSelector 是一个DeferredImportSelector,由 spring boot autoconfigure 从版本1.3开始,提供用来处理EnableAutoConfiguration自动配置。

EnableAutoConfigurationImportSelector继承自AutoConfigurationImportSelector,从 spring boot 1.5 以后,EnableAutoConfigurationImportSelector已经不再被建议使用,而是推荐使用 AutoConfigurationImportSelector。

在@EnableAutoConfiguration注解内使用到了@import注解来完成导入配置的功能,而AutoConfigurationImportSelector内部则是使用了SpringFactoriesLoader.loadFactoryNames方法进行扫描具有META-INF/spring.factories文件的jar包。我们可以先来看下spring-boot-autoconfigure包内的spring.factories文件内容,如下所示:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
                                                  AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
        getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
    Assert.notEmpty(configurations,
                    "No auto configuration classes found in META-INF/spring.factories. If you "
                    + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}
// SpringFactoriesLoader#loadSpringFactories
public static finaString FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) {
        return result;
    }

    try {
        Enumeration<URL> urls = (classLoader != nul?
                                 classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : // here
                                 ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        while (urls.hasMoreElements()) {
            URur= urls.nextElement();
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryClassName = ((String) entry.getKey()).trim();
                for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    result.add(factoryClassName, factoryName.trim());
                }
            }
        }
        cache.put(classLoader, result);
        return result;
    } catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                                           FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

image-20220127171230133

可以看到配置的结构形式是Key=>Value形式,多个Value时使用,隔开,那我们在自定义starter内也可以使用这种形式来完成,我们的目的是为了完成自动化配置,所以我们这里Key则是需要使用org.springframework.boot.autoconfigure.EnableAutoConfiguration

自定义 spring.factories

我们在src/main/resource目录下创建META-INF目录,并在目录内添加文件spring.factories,具体内容如下所示:

#配置自定义Starter的自动化配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=len.hgy.redis.RedisAutoConfiguration

目前为止自定义的starter已经开发完毕

新建项目测试startser

pom

<?xmversion="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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>len.hgy</groupId>
    <artifactId>testRedisStarter</artifactId>
    <version>1.0-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.2.RELEASE</version>
    </parent>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>len.hgy</groupId>
            <artifactId>redis-starter</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

新建一个springboot启动类

package len.hgy;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

新建application.properties在里面配置redis连接相关信息

redis.port=6379
redis.host=127.0.0.1

测试类

package len.hgy.test;

import javax.annotation.Resource;
import len.hgy.App;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import redis.clients.jedis.Jedis;

@SpringBootTest(classes = App.class)
@RunWith(SpringRunner.class)
public class RedisTest {
    @Resource
    private Jedis jedis;

    @Test
    public void test() {
        jedis.set("hgy", "hgy");
        String enjoy = jedis.get("hgy");
        System.out.println(enjoy);
    }
}

整个bean被拉取到spring容器的过程梳理

image-20220127184312905

SpringBoot CLI

Spring Boot CLI是一个命令行工具,如果想使用Spring进行快速开发可以使用它。它允许你运行Groovy脚本,这意味着你可以使用熟悉的类Java语法,并且没有那么多的模板代码。你可以通过Spring Boot CLI启动新项目,或为它编写命令。

Groovy是个基于JVM(Java虚拟机)的敏捷开发语言,既然是基于jvm,那么在groovy里面使用任何java的组件他都是可以支持识别的,在大概5,6年前Groovy比较火,尤其微信公众刚开放的那段时间,很多微信的后端程序都是基于grails开发的。

解压安装SpringBoot CLI

https://repo.spring.io/release/org/springframework/boot/spring-boot-cli/2.1.2.RELEASE/spring-boot-cli-2.1.2.RELEASE-bin.zip

# 查看版本信息
spring.bat --version
# 使用命令可以启动 
spring.bat run .\hello.groovy
# 构建项目
spring init --build=maven --java-version=1.8 --dependencies=web --packaging=jar --boot-version=1.5.3.RELEASE --groupId=enjoy --artifactId=demo

性能优化

扫描优化

在默认情况下,我们会使用@SpringBootApplication注解来自动获取应用的配置信息,但这样也会带来一些副作用。使用这个注解后,会触发自动配置(auto-configuration)和组件扫描(component scanning),这跟使用@Configuration、@EnableAutoConfiguration和@ComponentScan三个注解的作用是一样的。这样做给开发带来方便的同时,会有以下的一些影响:

  • 会导致项目启动时间变长(原因:加载了我们不需要使用的组件,浪费了cpu资源和内存资源)。当启动一个大的应用程序,或将做大量的集成测试启动应用程序时,影响会特别明显。

  • 会加载一些不需要的多余的实例(beans)。

  • 会增加CPU消耗和内存的占用。

不使用@SpringBootApplication,并且使用带参数的@ComponentScan注解(此注解会自动扫描我们注解了@Controller,@Service的注解的类,加载到Spring IOC容器中),然后我们使用@Configuration和@EnableAutoConfiguration进行配置启动类

//@SpringBootApplication
@EnableAutoConfiguration
@Configuration
@ComponentScan("len.hgy.controller")
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class,args);
    }
}

@EnableAutoConfiguration注解会导入META-INF/spring.factories里面配置的很多Configuration,这些Configuration他都会去扫描

在启动VM参数里面加入 -Ddebug

image-20220127225426358

重新启动,发现在控制台

image-20220127225440591

控制台输入的信息,大概分成四大类

  1. Positive matches:
    匹配(以及匹配的原因)
  2. Negative matches:
    忽略匹配(以及忽略的原因)
  3. Exclusions:
    排除的配置类
  4. Unconditional classes:
    没有带任何条件,肯定要扫描的类

我们只需要在启动的时候,显式地引入这些组件需要的组件=Positive matches+Unconditional classes

可以不使用@EnableAutoConfiguration,转而显示的使用@Import来导入需要的配置类

另外也可以删除一些虽然匹配到了,但是在项目中目前并没有使用到的配置,比如

任务调度:TaskExecutionAutoConfiguration,TaskSchedulingAutoConfiguration

WebSocket: WebSocketServletAutoConfiguration

附件上传:MultipartAutoConfiguration

JMX:JmxAutoConfiguration

等等……

JVM参数调优

启动App,使用jdk里面jvisualvm.exe

image-20220127231012242

这个时候内存分配了2个G,可以根据需要,判断是否需要这么大,一般来说1个G足够,尤其是微服务

另外还发现最大值和最小值两个设置的并不一样,来看下会有什么问题。

设置JVM参数

-XX:+PrintGCDetails -Xmx32M -Xms6M

gc了66次

full gc 了4次

image-20220127230808478

频繁的GC对性能影响是很大的。

频繁调用http://localhost:8080/hi,发现垃圾回收特别频繁

image-20220127230948074

-XX:+PrintGCDetails -Xmx32M -Xms32M

只full gc了一次

gc了50次

image-20220127230550360

频繁调用http://localhost:8080/hi,发现垃圾回收依然特别频繁,这不断的申请内存,释放内存对性能是有不小的影响

image-20220127231034846

置JVM参数,把最大的内存数设置成1024

-XX:+PrintGCDetails -Xmx1024M -Xms1M

-XX:+PrintGCDetails -Xmx1024M -Xms1024M full gc 1次 gc 2次(含full gc)

自己去测试

Undertow容器

默认情况下,Spring Boot 使用 Tomcat 来作为内嵌的 Servlet 容器可以将 Web 服务器切换到 Undertow 来提高应用性能。

Undertow 是一个采用 Java 开发的灵活的高性能 Web 服务器,提供包括阻塞和基于 NIO 的非堵塞机制。Undertow 是红帽公司的开源产品

Tomcat测试

jmeter配置

image-20220127233636478

image-20220127234341377

image-20220127234354585

image-20220127234426141

重复三次,结果为:1796,1761,1720, 可见tomcat在1秒内处理请求数不到2000

Undertow测试

修改pom

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

启动报错:

2022-01-27 23:52:31,422 restartedMain ERROR An exception occurred processing Appender File java.nio.file.InvalidPathException: Illegal char <:> at index 5: ${sys:LOG_PATH}\2022-01

image-20220127235336614

tomcat写的日志格式和undertow格式不兼容,换一个日志文件名称或删除即可

启动成功

image-20220127235459921

重新压测三次

image-20220127235550086

2454,2345 ,2654

性能提升比较明显

多数据源与jta+atomikos分布式事务

SpringBoot默认是集成事务的,只要在方法上加上@Transactional既可, 但某些项目用到了多个数据库,也就代表有多个数据源。

多数据源

数据库

有两个数据库

db1: hgy_user

CREATE TABLE `hgy_user` (
	`id`  int NOT NULL AUTO_INCREMENT ,
	`passwd`  varchar(255) NULL ,
	`username`  varchar(255) NULL ,
	PRIMARY KEY (`id`)
);

db2: hgy_order

CREATE TABLE `hgy_order` (
	`id` INT ( 10 ) NOT NULL AUTO_INCREMENT,
	`name` VARCHAR ( 255 ),
	`user_id` INT ( 11 ) NOT NULL,
	`account` INT ( 255 ) NULL DEFAULT NULL,
	PRIMARY KEY ( `id` ) 
)

pom文件

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>hgy</groupId>
  <artifactId>springbootatomikos</artifactId>
  <version>1.0-SNAPSHOT</version>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.2.RELEASE</version>
  </parent>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>2.2.1</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jta-atomikos</artifactId>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.22</version>
    </dependency>
  </dependencies>
</project>

新增model(Orders,Users)

新增mapper接口

Mapper的XML配置

为了管理方便,xml的配置也根据数据库的不同放置到不同的文件夹中。

image-20220128001809730

application.properties

spring.datasource.spring.driverClassName=com.mysql.jdbc.Driver
spring.datasource.spring.jdbcUrl=jdbc:mysql://127.0.0.1:3306/my_db?serverTimezone=GMT%2B8
spring.datasource.spring.username=root
spring.datasource.spring.password=root

spring.datasource.spring2.driverClassName=com.mysql.jdbc.Driver
spring.datasource.spring2.jdbcUrl=jdbc:mysql://192.168.71.10:3306/len?serverTimezone=GMT%2B8
spring.datasource.spring2.username=root
spring.datasource.spring2.password=root

数据源配置类

db1

@Configuration
@MapperScan(basePackages = "len.hgy.dao.users", sqlSessionFactoryRef = "test1SqlSessionFactory")
public class DataSource1Config {
//    @Bean(name = "test1DataSource")
//    @ConfigurationProperties(prefix = "spring.datasource.spring")
//    @Primary
//    public DataSource testDataSource() {
//        return DataSourceBuilder.create().build();
//    }

    @Bean(name = "test1SqlSessionFactory")
    @Primary
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource)
        throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapping/users/*.xml"));
        return bean.getObject();
    }

    @Bean(name = "test1DataSource")
    @Primary
    public DataSource testDataSource(DBConfig1 config1) {
        MysqlXADataSource mysqlXADataSource = new MysqlXADataSource();
        mysqlXADataSource.setUrl(config1.getJdbcUrl());
        mysqlXADataSource.setPassword(config1.getPassword());
        mysqlXADataSource.setUser(config1.getUsername());

        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        atomikosDataSourceBean.setXaDataSource(mysqlXADataSource);
        atomikosDataSourceBean.setUniqueResourceName("test1Datasource");
        return atomikosDataSourceBean;

    }

//    @Bean(name = "test1TransactionManager")
//    @Primary
//    public DataSourceTransactionManager testTransactionManager(@Qualifier("test1DataSource") DataSource dataSource) {
//        return new DataSourceTransactionManager(dataSource);
//    }

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

db2

@Configuration
@MapperScan(basePackages = "len.hgy.dao.orders", sqlSessionFactoryRef = "test2SqlSessionFactory")
public class DataSource2Config {
//    @Bean(name = "test2DataSource")
//    @ConfigurationProperties(prefix = "spring.datasource.spring2")
//    public DataSource testDataSource() {
//        return DataSourceBuilder.create().build();
//    }

    @Bean(name = "test2SqlSessionFactory")
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("test2DataSource") DataSource dataSource)
        throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapping/orders/*.xml"));
        return bean.getObject();
    }

    @Bean(name = "test2DataSource")
    public DataSource testDataSource(DBConfig2 config2) {
        MysqlXADataSource mysqlXADataSource = new MysqlXADataSource();
        mysqlXADataSource.setUrl(config2.getJdbcUrl());
        mysqlXADataSource.setPassword(config2.getPassword());
        mysqlXADataSource.setUser(config2.getUsername());

        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        atomikosDataSourceBean.setXaDataSource(mysqlXADataSource);
        atomikosDataSourceBean.setUniqueResourceName("test2Datasource");
        return atomikosDataSourceBean;

    }

//    @Bean(name = "test2TransactionManager")
//    public DataSourceTransactionManager testTransactionManager(@Qualifier("test2DataSource") DataSource dataSource) {
//        return new DataSourceTransactionManager(dataSource);
//    }

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

配置了数据源,连接工厂,Mapper扫描的包, mapper xml配置的位置等

@SpringBootTest(classes = {App.class})
@RunWith(SpringRunner.class)
public class UserTest {
    @Resource
    private IOrderService iOrderService;

    @org.junit.Test
    public void test1() {
        Users users = new Users();
        users.setUsername("hgy");
        users.setPasswd("123");
        users.setId(1);

        Orders orders = new Orders();
        orders.setAccount(12);
        orders.setName("娃娃");
        orders.setUserId(1);
        iOrderService.addOrder(orders, users);
    }
}

这个时候以及集成了2套数据源,并且已经测试发现可以同时入库。

jta+atomikos分布式事务

修改pom文件

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>

新增配置类(DBConfig1,DBConfig2)

@ConfigurationProperties(prefix = "spring.datasource.spring")
@Component
@Data
public class DBConfig1 {
    private String driverClassName;
    private String jdbcUrl;
    private String username;
    private String password;
}
@ConfigurationProperties(prefix = "spring.datasource.spring2")
@Component
@Data
public class DBConfig2 {
    private String driverClassName;
    private String jdbcUrl;
    private String username;
    private String password;
}

修改数据源配置类

@Configuration
@MapperScan(basePackages = "len.hgy.dao.users", sqlSessionFactoryRef = "test1SqlSessionFactory")
public class DataSource1Config {
//    @Bean(name = "test1DataSource")
//    @ConfigurationProperties(prefix = "spring.datasource.spring")
//    @Primary
//    public DataSource testDataSource() {
//        return DataSourceBuilder.create().build();
//    }

    @Bean(name = "test1SqlSessionFactory")
    @Primary
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource)
        throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapping/users/*.xml"));
        return bean.getObject();
    }

    @Bean(name = "test1DataSource")
    @Primary
    public DataSource testDataSource(DBConfig1 config1) {
        MysqlXADataSource mysqlXADataSource = new MysqlXADataSource();
        mysqlXADataSource.setUrl(config1.getJdbcUrl());
        mysqlXADataSource.setPassword(config1.getPassword());
        mysqlXADataSource.setUser(config1.getUsername());

        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        atomikosDataSourceBean.setXaDataSource(mysqlXADataSource);
        atomikosDataSourceBean.setUniqueResourceName("test1Datasource");
        return atomikosDataSourceBean;

    }

//    @Bean(name = "test1TransactionManager")
//    @Primary
//    public DataSourceTransactionManager testTransactionManager(@Qualifier("test1DataSource") DataSource dataSource) {
//        return new DataSourceTransactionManager(dataSource);
//    }

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


@Configuration
@MapperScan(basePackages = "len.hgy.dao.orders", sqlSessionFactoryRef = "test2SqlSessionFactory")
public class DataSource2Config {
//    @Bean(name = "test2DataSource")
//    @ConfigurationProperties(prefix = "spring.datasource.spring2")
//    public DataSource testDataSource() {
//        return DataSourceBuilder.create().build();
//    }

    @Bean(name = "test2SqlSessionFactory")
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("test2DataSource") DataSource dataSource)
        throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapping/orders/*.xml"));
        return bean.getObject();
    }

    @Bean(name = "test2DataSource")
    public DataSource testDataSource(DBConfig2 config2) {
        MysqlXADataSource mysqlXADataSource = new MysqlXADataSource();
        mysqlXADataSource.setUrl(config2.getJdbcUrl());
        mysqlXADataSource.setPassword(config2.getPassword());
        mysqlXADataSource.setUser(config2.getUsername());

        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        atomikosDataSourceBean.setXaDataSource(mysqlXADataSource);
        atomikosDataSourceBean.setUniqueResourceName("test2Datasource");
        return atomikosDataSourceBean;

    }

//    @Bean(name = "test2TransactionManager")
//    public DataSourceTransactionManager testTransactionManager(@Qualifier("test2DataSource") DataSource dataSource) {
//        return new DataSourceTransactionManager(dataSource);
//    }

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

修改Service方法

@Override
@Transactional
public void addOrder(Orders orders, Users users) {
    usersMapper.insertSelective(users);
    int i=10/0; // 使得抛出异常
    ordersMapper.insertSelective(orders);
}

单元测试两个数据都不能入库成功

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

岁月玲珑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值