Druid动态数据源该如何实现?

Hello 小伙伴们大家好, 今天我们一起谈论一下, 如何实现动态数据源呢? Let’s go, 让我们开始吧。本期只讨论动态数据源的实现方式。只提出应用场景,不对应用场景做介绍,应用场景放在下期。

一、应用场景

  • 主从复制
  • 读写分离
  • 分库分表

主从复制与读写分离通常是一起使用的。

二、实现方式

1、技术栈

  • SpringBoot 2.6.13
  • Druid 1.2.16
  • Spring JDBC
  • MySQL 8.0
  • AOP动态代理

2、Maven 依赖

下边依赖放入同一个pom.xml文件就可以

<properties>
    <java.version>11</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <spring-boot.version>2.6.13</spring-boot.version>
</properties>




<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.2.16</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

3、数据库结构

数据库结构非常简单,主要实现思想,复杂的都是同一个操作方式。

image.png

(1)数据库:db_dynamic1

DROP TABLE IF EXISTS `tb_user`;

CREATE TABLE `tb_user` (

  `id` bigint NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;





BEGIN;

INSERT INTO `tb_user` (`id`, `username`) VALUES (1, '张三');
INSERT INTO `tb_user` (`id`, `username`) VALUES (2, '李四');
INSERT INTO `tb_user` (`id`, `username`) VALUES (3, '王五');
COMMIT;




SET FOREIGN_KEY_CHECKS = 1;

(2)数据库:db_dynamic2

DROP TABLE IF EXISTS `tb_user`;

CREATE TABLE `tb_user` (

  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
  PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;





BEGIN;

INSERT INTO `tb_user` (`id`, `username`) VALUES (1, 'Vker');
INSERT INTO `tb_user` (`id`, `username`) VALUES (2, 'Jack');
INSERT INTO `tb_user` (`id`, `username`) VALUES (3, 'Lucy');
COMMIT;




SET FOREIGN_KEY_CHECKS = 1;



4、application.yml 配置文件

将下面配置文件的url, username, password换成你自己的

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      master:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/db_dynamic1?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=false
        username: root
        password: root_root
      slave:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/db_dynamic2?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=false
        username: root
        password: root_root
      initial-size: 1
      min-idle: 1
      max-active: 20
      test-on-borrow: true

5、定义一个 DataSources 注解

这个注解主要用于多数据源的选择,默认是 master

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSources {






    String value() default "master";








}

6、配置一下动态数据源

这个类主要用于配置多数据源,不要忘记@Primary注解,否则会报错。

@Component
@Primary
public class DynamicDataSources extends AbstractRoutingDataSource {






    public static ThreadLocal<String> name = new ThreadLocal<>();








    @Resource
    DataSource master;


    @Resource
    DataSource slave;

    @Override
    protected Object determineCurrentLookupKey() {
        return name.get();
    }



    @Override
    public void afterPropertiesSet() {
        Map<Object, Object> ds = new HashMap<>();
        ds.put("master", master);
        ds.put("slave", slave);


        super.setTargetDataSources(ds);
        super.setDefaultTargetDataSource(master);
        super.afterPropertiesSet();
    }
}

7、写一个基于 DataSources 注解的动态代理

这个动态代理用于检查类或者方法上的@DataSources注解,取到注解中的value值,将值传入动态数据源配置类DynamicDataSourcesThreadLocal中,用于选择数据源。

@Component
@Aspect
public class DynamicDataSourcesAspect {






    Logger logger = LoggerFactory.getLogger(DynamicDataSourcesAspect.class);








    @Before("@annotation(dataSources)")
    public void before(DataSources dataSources) {
        String value = dataSources.value();
        DynamicDataSources.name.set(value);
        logger.info("进入AOP代理: {}", value);
    }




}

8、再来写一个读数据库配置信息的配置类

这个配置类主要将application.yml中的数据库配置信息的配置信息进行加载,然后使用数据源事务管理器DataSourceTransactionManager进行注册。

@Configuration
public class DynamicDataSourcesConfig {





    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid.master")
    public DataSource master() {
        return DruidDataSourceBuilder.create().build();
    }


    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid.slave")
    public DataSource slave() {
        return DruidDataSourceBuilder.create().build();
    }


    @Bean
    public DataSourceTransactionManager dataSourceTransactionManager1(@Qualifier("master") DataSource master) {
        DataSourceTransactionManager dataSourceTransactionManager =
                new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(master);
        return dataSourceTransactionManager;
    }


    @Bean
    public DataSourceTransactionManager dataSourceTransactionManager2(@Qualifier("slave") DataSource slave) {
        DataSourceTransactionManager dataSourceTransactionManager =
                new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(slave);
        return dataSourceTransactionManager;
    }

}

重点的都做完了,接下来就写一下基本的业务层代码吧,注释就不写了,我相信小伙伴们都能看懂。

1、实体类

public class User {



    private Long id;






    private String username;








   // 省略了 getter setter
}

2、Dao 数据层

public interface UserMapper {



    List<User> selectAllUser();






}









@Repository
public class UserMapperImpl implements UserMapper {





    @Resource


    private JdbcTemplate jdbcTemplate;








    @Override

    public List<User> selectAllUser() {
        String sql = "select * from tb_user";
        return jdbcTemplate.query(sql,
                new BeanPropertyRowMapper<>(User.class));
    }

}

3、Service 服务层

public interface UserService {



    List<User> list();






}

@Service
public class UserServiceImpl implements UserService {





    @Resource


    private UserMapper userMapper;








    @Override

    public List<User> list() {
        return userMapper.selectAllUser();
    }

}

4、Controller 控制层

这里说一下,你可以将注解 @DataSources 注解放到你想使用不同数据源的方法上,如下,默认 master 可以不写。

@RestController
public class UserController {





    @Resource


    private UserService userService;








    @GetMapping("list1")
    public List<User> list1() {
        return userService.list();
    }


    @GetMapping("list2")
    @DataSources("slave")
    public List<User> lis2t() {
        return userService.list();
    }



}

5、最后来看一下启动类

需要把 DataSourceAutoConfiguration 排除掉哦

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class DynamicDatasourceDemoApplication {





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




}

6、测试结果

list1接口

image.png

list2接口

image.png

好了,以上就是动态数据源的实现方式了,欢迎小伙伴们留言,下期我们看看,如何实现主从复制读写分离

此致

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MY4UJxQp' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片