记录一次springboot单数据源mysql到多数据源mysql+clickhouse的接入过程,项目使用seata做事务管理

记录一次springboot单数据源mysql到多数据源mysql+clickhouse的接入过程,项目使用seata做事务管理

  1. 单数据源配置:
spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://192.168.5.1:3306/aaa?characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    initial-size: 10
    min-idle: 3
    max-active: 100
    max-wait: 60000
    time-between-eviction-runs-millis: 60000
    min-evictable-idle-time-millis: 300000
    validation-query: SELECT 1 FROM DUAL
    test-while-idle: true
    test-on-borrow: false
    test-on-return: false
    pool-prepared-statements: true
    max-pool-prepared-statement-per-connection-size: 20
    filters: stat,wall
    use-global-data-source-stat: true
    connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

mybatis:
  type-aliases-package: com.aa.bb.*.dao;com.aa.*.base;com.aa.bb.*.entity;
  mapper-locations: classpath:mapper/*Mapper.xml,classpath:mapper/**/*Mapper.xml

seata:
  enabled: true
  application-id: ${spring.application.name} #服务名
  tx-service-group: xxxxxxxxxxxxx
  #启用自动数据源代理
  enable-auto-data-source-proxy: true
  ..........
  1. 改成多数据源配置
    2.1 pom加入依赖
<!--clickhouse-->
        <dependency>
            <groupId>ru.yandex.clickhouse</groupId>
            <artifactId>clickhouse-jdbc</artifactId>
            <version>0.2.4</version>
        </dependency>
        <!--多数据源-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            <version>3.2.0</version>
        </dependency>
2.2 配置文件
spring:
  autoconfigure:
    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    dynamic:
      seata: true
      primary: mysql
      strict: false
      datasource:
        mysql:
          username: root
          password: zprd
          url: jdbc:mysql://192.168.5.1:3306/aaa?characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
          driver-class-name: com.mysql.cj.jdbc.Driver
        clickhouse:
          url: jdbc:clickhouse://192.168.5.1:8123
          username: default
          password: admin
          driver-class-name: ru.yandex.clickhouse.ClickHouseDriver
      druid:
        initial-size: 10
        min-idle: 3
        max-active: 100
        max-wait: 60000
        time-between-eviction-runs-millis: 60000
        min-evictable-idle-time-millis: 300000
        validation-query: SELECT 1
        test-while-idle: true
        test-on-borrow: false
        test-on-return: false
        pool-prepared-statements: true
        max-pool-prepared-statement-per-connection-size: 20
        filters: stat
        use-global-data-source-stat: true
        connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
mybatis:
  type-aliases-package: com.aa.bb.*.dao;com.aa.*.base;com.aa.bb.*.entity;
  mapper-locations: classpath:mapper/*Mapper.xml,classpath:mapper/**/*Mapper.xml

seata:
  enabled: true
  application-id: ${spring.application.name} #服务名
  tx-service-group: xxxxxxxxxxxxx
  #启用自动数据源代理
  enable-auto-data-source-proxy: false
  ........

-----如果这里没有用到seata,那么到这里就结束了,配置文件去掉seata相关配置------

注: 这里seata关闭了自动代理 enable-auto-data-source-proxy: false
切换数据源: 使用@DS注解

@DS("clickhouse")
public interface EventTrackingDao{

    Integer findCount();
}

然后启动项目,报错:
在这里插入图片描述
这个异常大致情况就是项目使用了seata, 但是clickhouse不支持事务,所以初始化失败,

  1. 继续干, 我先关闭seata,看项目能不能跑起来: seata: false
    结果不行,还是报这个错,按理说不应该,
    然后继续关: enabled: false
    这次成了,项目正常启动,也能正常访问数据库
    其实这里的原因就是: enable-auto-data-source-proxy: false, 这个配置不对,
    更改为: enableAutoDataSourceProxy: false就对了

  2. 集成clickhouse不能影响原有项目运行,seata也不能关闭,还得继续搞
    大概思路就是: 关闭seata自动数据源代理,然后改为手动代理方式,指定mysql使用seata代理,clickhouse不使用代理.
    4.1 配置文件

spring.datasource.master.url=jdbc:mysql://192.168.5.1:3306/aaa?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8&allowMultiQueries=true
spring.datasource.master.username=root
spring.datasource.master.password=root
spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.master.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.master.initialSize=10
spring.datasource.master.minIdle=3
spring.datasource.master.maxActive=100
spring.datasource.master.maxWait=60000
spring.datasource.master.removeAbandoned=true
spring.datasource.master.removeAbandonedTimeout=180
spring.datasource.clickhouse.url=jdbc:clickhouse://192.168.5.1:8123
spring.datasource.clickhouse.username=default
spring.datasource.clickhouse.password=admin
spring.datasource.clickhouse.driver-class-name=ru.yandex.clickhouse.ClickHouseDriver
spring.datasource.clickhouse.type=com.alibaba.druid.pool.DruidDataSource
seata:
  enabled: true
  application-id: ${spring.application.name} #服务名
  tx-service-group: xxxxxxxxx
  # 启用自动数据源代理
  enableAutoDataSourceProxy: false
  ........

4.2 创建文件:config/mybatis.properties

mybatis.typeAliasesPackage=com.aa.bb.*.dao;com.aa.*.base;com.aa.bb.*.entity;
mybatis.mapperLocations=classpath:mapper/*Mapper.xml,classpath:mapper/**/*Mapper.xml

4.3 创建文件:DataSourceConfigurer

import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.github.pagehelper.PageInterceptor;
import io.seata.rm.datasource.DataSourceProxy;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.util.ClassUtils;

import javax.sql.DataSource;
import java.io.IOException;
import java.util.*;

@Slf4j
@Configuration
@PropertySource("classpath:config/mybatis.properties")
public class DataSourceConfigurer  implements EnvironmentAware {

    private Environment env;

    @Override
    public void setEnvironment(Environment environment) {
        // spring自动注入 会报空指针
        this.env = environment;
    }


    @Bean("clickhouse")
    @ConfigurationProperties("spring.datasource.clickhouse")
    public DataSource clickhouseDataSource() {
        return new DruidDataSource();
    }


    @Bean("originalMysql")
    @ConfigurationProperties("spring.datasource.master")
    public DataSource mysqlDataSource() {
        return new DruidDataSource();
    }

    @Primary
    @Bean("mysql")
    public DataSource dataSource1(@Qualifier("originalMysql")DataSource dataSource) {
        return new DataSourceProxy(dataSource);
    }


    @Bean("dynamicDataSource")
    public DataSource dynamicDataSource(@Qualifier("mysql") DataSource mysql,
                                        @Qualifier("clickhouse") DataSource clickhouse) {
        DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
        dynamicRoutingDataSource.addDataSource("mysql", mysql);
        dynamicRoutingDataSource.addDataSource("clickhouse", clickhouse);
        dynamicRoutingDataSource.setPrimary("mysql");
        dynamicRoutingDataSource.setSeata(true);
        return dynamicRoutingDataSource;
    }

    /**
     * 解决引入seata后@Transactional注解失效的问题
     */
    @Bean("txManager")
    public DataSourceTransactionManager txManager(@Qualifier("dynamicDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    public SqlSessionFactory sqlSessionFactoryBean(@Qualifier("dynamicDataSource")DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        //解决DefaultVFS在获取jar上存在的问题
        factory.setVfs(SpringBootVFS.class);

        //别名
        String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
        String mapperLocations = env.getProperty("mybatis.mapperLocations");
        typeAliasesPackage=setTypeAliasesPackage(typeAliasesPackage);

        factory.setTypeAliasesPackage(typeAliasesPackage);
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        //驼峰
        configuration.setMapUnderscoreToCamelCase(true);
        factory.setConfiguration(configuration);
        //添加XML目录
        factory.setMapperLocations(resolveMapperLocations(mapperLocations));
        return factory.getObject();
    }

    public Resource[] resolveMapperLocations(String mapperLocations){
        ResourcePatternResolver resourceResolver =new PathMatchingResourcePatternResolver();
        List<String> strings = Arrays.asList(mapperLocations.split(","));
        List<Resource> resources =new ArrayList();
        if(mapperLocations != null){
            for(String mapperLocation : strings){
                try{
                    Resource[] mappers = resourceResolver.getResources(mapperLocation);
                    resources.addAll(Arrays.asList(mappers));
                }catch(IOException e){
                }
            }
        }
        return resources.toArray(new Resource[resources.size()]);
    }

    private static final String DEFAULT_RESOURCE_PATTERN = "*.class";

    private static String setTypeAliasesPackage(String typeAliasesPackage) {
        ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver();
        MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(
                resolver);
        String[] packages = typeAliasesPackage.split(";");
        try {
            List<String> result = new ArrayList<>();

            for (String p:packages){
                typeAliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                        + ClassUtils.convertClassNameToResourcePath(p)
                        + "/" + DEFAULT_RESOURCE_PATTERN;
                Resource[] resources  = resolver.getResources(typeAliasesPackage);
                if (resources.length > 0) {
                    MetadataReader metadataReader ;
                    for (Resource resource : resources) {
                        if (resource.isReadable()) {
                            metadataReader = metadataReaderFactory.getMetadataReader(resource);
                            try {
                                log.info(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName());
                                result.add(Class
                                        .forName(
                                                metadataReader.getClassMetadata()
                                                        .getClassName())
                                        .getPackage().getName());
                            } catch (ClassNotFoundException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }

            if (result.size() > 0) {
                HashSet<String> h = new HashSet<>(result);
                result.clear();
                result.addAll(h);
                typeAliasesPackage=String.join(",",(String[]) result.toArray(new String[0]));
                log.info("mybatis扫描通配符:"+typeAliasesPackage+"=========================================");
            } else {
                throw new RuntimeException(
                        "mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:"
                                + typeAliasesPackage + "未找到任何包");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return typeAliasesPackage;
    }
}

配置完成OK
启动项目,然后报错:
在这里插入图片描述
嗯,这个啥错咱也不知道,只能看到dynamic这个多数据源的jar包里报错了,OK,可能是版本问题
调整jar版本:

<!--多数据源-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>

启动项目,正常,搞定收工.

交流Q: 466129139

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
好的,以下是基于 Spring Cloud+Spring Boot+Nacos+Dubbo+mybatis plus+RocketMQ+Sentinel+Seata+mysql 搭建一个项目的步骤: 1. 首先,你需要安装并启动 Nacos 作为服务注册中心和配置中心。可以去官网下载最新版本的 Nacos,然后解压并启动即可。 2. 接下来,使用 Spring Initializr 创建一个基于 Spring Boot项目,并添加以下依赖: ``` <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-dubbo</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-seata</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-sentinel</artifactId> </dependency> <dependency> <groupId>org.apache.rocketmq</groupId> <artifactId>rocketmq-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> ``` 3. 配置 Nacos 作为服务注册中心和配置中心: ``` spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 spring.cloud.nacos.config.server-addr=127.0.0.1:8848 spring.cloud.nacos.config.prefix=/config spring.cloud.nacos.config.file-extension=properties ``` 4. 配置 Dubbo: ``` spring.application.name=provider-service spring.dubbo.application.name=provider-service spring.dubbo.registry.address=nacos://127.0.0.1:8848 spring.dubbo.protocol.name=dubbo spring.dubbo.protocol.port=20880 ``` 5. 配置 RocketMQ: ``` spring.rocketmq.name-server=127.0.0.1:9876 spring.rocketmq.producer.group=producer-group spring.rocketmq.consumer.group=consumer-group ``` 6. 配置 Sentinel: ``` spring.cloud.sentinel.transport.dashboard=127.0.0.1:8080 ``` 7. 配置 Seata: ``` spring.cloud.alibaba.seata.tx-service-group=my_seata_tx_group spring.cloud.alibaba.seata.tx-service-mode=AT spring.cloud.alibaba.seata.config.type=nacos spring.cloud.alibaba.seata.config.nacos.server-addr=127.0.0.1:8848 spring.cloud.alibaba.seata.config.nacos.namespace=seata-dev ``` 8. 配置 MySQL 数据源: ``` spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.jdbc.Driver ``` 9. 编写 Dubbo 服务接口和实现类: ``` public interface UserService { User getUserById(Long id); } @Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Override public User getUserById(Long id) { return userDao.selectById(id); } } ``` 10. 配置 Dubbo 服务提供者: ``` @Configuration public class DubboConfig { @Reference private UserService userService; @Bean public ApplicationRunner runner() { return args -> { User user = userService.getUserById(1L); System.out.println(user); }; } } ``` 11. 编写 RocketMQ 生产者和消费者: ``` @Component public class MessageProducer { @Autowired private RocketMQTemplate rocketMQTemplate; public void send(String message) { rocketMQTemplate.convertAndSend("test-topic", message); } } @Component @RocketMQMessageListener(topic = "test-topic", consumerGroup = "test-consumer-group") public class MessageConsumer implements RocketMQListener<String> { @Override public void onMessage(String message) { System.out.println("Received message: " + message); } } ``` 12. 编写 MySQL 数据访问层: ``` @Mapper public interface UserDao extends BaseMapper<User> { } ``` 13. 使用 Seata 进行分布式事务控制: ``` @Service public class OrderServiceImpl implements OrderService { @Autowired private OrderDao orderDao; @GlobalTransactional @Override public void createOrder(Order order) { orderDao.insert(order); // 调用其他服务,可能会涉及到分布式事务操作 // ... } } ``` 14. 使用 Sentinel 进行服务限流和熔断: ``` @GetMapping("/hello") @SentinelResource(value = "hello", blockHandler = "helloBlockHandler") public String hello() { return "Hello World!"; } public String helloBlockHandler(BlockException ex) { return "Blocked by Sentinel: " + ex.getClass().getSimpleName(); } ``` 15. 最后,启动项目并测试各个功能是否正常运行。 以上就是基于 Spring Cloud+Spring Boot+Nacos+Dubbo+mybatis plus+RocketMQ+Sentinel+Seata+mysql 搭建一个项目的步骤。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值