Blasting column - SpringBoot2 Configuring multiple data sources in AOP mode in SpringBoot of X series of tutorials

Posted by michaellunsford on Wed, 29 Dec 2021 04:20:22 +0100

preface

In the previous chapter, Yige took you through subcontracting to realize the function of configuring multiple data sources in a project at the same time. As I told you before, there are two ways to configure multiple data sources, so next I will lead you to realize the second way to configure multiple data sources through AOP aspect configuration, which is written on the basis of the previous case.

I Implementation process

1. Create a Web project

According to our previous experience, we create a SpringBoot Web program. For the specific process, you can refer to the following figure to create the project.

2. Add dependent packages

In POM Add several core dependency packages to the XML file.

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

<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
</dependency>

<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>druid</artifactId>
   <version>1.1.10</version>
</dependency>

3. Create application YML profile

In this configuration file, to configure two databases, we use the default HikariDataSource data source in this case.

spring:
  main:
    allow-bean-definition-overriding: true
  datasource:
    #First database information
    ds1:
      url: jdbc:mysql://localhost:3306/db1?characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
      username: root
      password: syc
      driverClassName: com.mysql.jdbc.Driver
    #2nd database information
    ds2:
      url: jdbc:mysql://localhost:3306/db4?characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
      username: root
      password: syc
      driverClassName: com.mysql.jdbc.Driver
    type: com.zaxxer.hikari.HikariDataSource
    #type: com.alibaba.druid.pool.DruidDataSource
  jpa:
    database: mysql
    show-sql: true
    hibernate:
      ddl-auto: update
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    database-platform: org.hibernate.dialect.MySQL5Dialect

4. Create database configuration class

4.1 first database configuration class

Here, we first write the first database configuration class to read the database information in ds1.

package com.yyg.boot.config.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * @Author One brother Sun
 * @Date Created in 2020/4/3
 * @Description db1 Data source configuration class
 */
@ConfigurationProperties(prefix = "spring.datasource.ds1")
@Component("ds1Properties")
@Data
public class Ds1Properties {

    private String url;

    private String username;

    private String password;

    private String driverClassName;

}

4.2 the second database configuration class

Then write the second database configuration class to read the database information in ds2.

package com.yyg.boot.config.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * @Author One brother Sun
 * @Date Created in 2020/4/3
 * @Description db4 Data source configuration class
 */
@ConfigurationProperties(prefix = "spring.datasource.ds2")
@Component("ds2Properties")
@Data
public class Ds2Properties {

    private String url;

    private String username;

    private String password;

    private String driverClassName;

}

5. Register data source

Next, we can register two data sources in a class at the same time. Note that we load two data sources in this configuration class.

package com.yyg.boot.config;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;

/**
 * @Author One brother Sun
 * @Date Created in 2020/4/3
 * @Description Configuration class of data source
 */
@Configuration
public class DataSourceRegisterConfig {

    /**
     * Master data source configuration} ds1 data source
     */
    @Primary
    @Bean(name = "ds1Properties")
    @ConfigurationProperties(prefix = "spring.datasource.ds1")
    public DataSourceProperties ds1DataSourceProperties() {
        return new DataSourceProperties();
    }

    /**
     * Master data source} ds1 data source
     */
    @Primary
    @Bean(name = "ds1DataSource")
    public DataSource ds1DataSource(@Qualifier("ds1Properties") DataSourceProperties dataSourceProperties) {
        //HikariDataSource","org.apache.tomcat.jdbc.pool.DataSource", "org.apache.commons.dbcp2.BasicDataSource
        return dataSourceProperties.initializeDataSourceBuilder().build();
    }

    /**
     * Second ds2 data source configuration
     */
    @Bean(name = "ds2Properties")
    @ConfigurationProperties(prefix = "spring.datasource.ds2")
    public DataSourceProperties ds2DataSourceProperties() {
        return new DataSourceProperties();
    }

    /**
     * Second ds2 data source
     */
    @Bean("ds2DataSource")
    public DataSource ds2DataSource(@Qualifier("ds2Properties") DataSourceProperties dataSourceProperties) {
        return dataSourceProperties.initializeDataSourceBuilder().build();
    }

}

6. Configure data source, connection factory, transaction manager and scan dao directory

After configuring the two data sources, we also use the transaction manager to associate the two data sources respectively. Pay attention to the rational use of @ Primary annotation here!

6.1 configuring the first data source Manager

Associate the first data source in this data source manager.

package com.yyg.boot.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

/**
 * @Author One brother Sun
 * @Date Created in 2020/4/3
 * @Description Configure data source, connection factory, transaction manager, dao directory
 */
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "managerFactory1", //Configure connection factory
        transactionManagerRef = "transactionManager1", //Configure transaction manager
        basePackages = {"com.yyg.boot.dao.db01"} //Set the location of dao

)
public class ManagerFactory01Config {

    /**
     * Configure the data source and connect the first data source
     */
    @Autowired
    @Qualifier("ds1DataSource")
    private DataSource ds1DataSource;

    @Primary
    @Bean(name = "managerFactory1")
    public LocalContainerEntityManagerFactoryBean buildEntityManagerFactory1(EntityManagerFactoryBuilder builder) {
        return builder
                //Set data source
                .dataSource(ds1DataSource)
                //Set the location of the Entity class Scan all classes with @ Entity annotation
                .packages("com.yyg.boot.entity")
                //Spring will inject the EntityManagerFactory into the Repository With {EntityManagerFactory,
                //The Repository can use it to create an EntityManager, and then the EntityManager can perform operations on the database
                .persistenceUnit("ds1PersistenceUnit")
                .build();

    }

    /**
     * Configure transaction manager
     */
    @Bean(name = "transactionManager1")
    public PlatformTransactionManager transactionManagerDatabase1(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(buildEntityManagerFactory1(builder).getObject());
    }

}

6.2 configuring the second data source Manager

Associate the second data source in this data source manager.

package com.yyg.boot.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

/**
 * @Author One brother Sun
 * @Date Created in 2020/4/3
 * @Description Configure data source, connection factory, transaction manager, dao directory
 */
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "managerFactory2", //Configure connection factory
        transactionManagerRef = "transactionManager2", //Configure transaction manager
        basePackages = {"com.yyg.boot.dao.db02"} //Set the location of dao

)
public class ManagerFactory02Config {

    /**
     * Configure the data source and connect the second data source
     */
    @Autowired
    @Qualifier("ds2DataSource")
    private DataSource ds2DataSource;

    @Bean(name = "managerFactory2")
    public LocalContainerEntityManagerFactoryBean buildEntityManagerFactory2(EntityManagerFactoryBuilder builder) {
        return builder
                //Set data source
                .dataSource(ds2DataSource)
                //Set the location of the Entity class Scan all classes with @ Entity annotation
                .packages("com.yyg.boot.entity")
                //Spring will inject the EntityManagerFactory into the Repository With {EntityManagerFactory,
                //The Repository can use it to create an EntityManager, and then the EntityManager can perform operations on the database
                .persistenceUnit("ds2PersistenceUnit")
                .build();

    }

    /**
     * Configure transaction manager
     */
    @Bean(name = "transactionManager2")
    public PlatformTransactionManager transactionManagerDatabase1(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(buildEntityManagerFactory2(builder).getObject());
    }

}

7. Create data source type

Here, I use ThreadLocal to ensure thread security, so that each thread will not affect each other.

package com.yyg.boot.datasource;

import lombok.extern.slf4j.Slf4j;

/**
 * @Author One brother Sun
 * @Date Created in 2020/4/7
 * @Description Data source type
 */
@Slf4j
public class DataSourceType {

    public enum SourceType {
        /**
         * User data source
         */
        DS_USER,
        /**
         * Commodity data source
         */
        DS_SHOP
    }

    /**
     * Using ThreadLocal to ensure thread safety
     */
    private static final ThreadLocal<SourceType> TYPES = new ThreadLocal<>();

    /**
     * Set the data source type to the current process
     */
    public static void setDataSourceType(SourceType dataSourceType) {
        if (dataSourceType == null) {
            throw new NullPointerException();
        }
        log.warn("[Set current data source as]: " + dataSourceType);
        TYPES.set(dataSourceType);
    }

    /**
     * Get data source type
     */
    public static SourceType getDataSourceType() {
        SourceType dataSourceType = TYPES.get() == null ? SourceType.DS_USER : TYPES.get();
        log.warn("[The current data source type is]: " + dataSourceType);
        return dataSourceType;
    }

    /**
     * Clear data type
     */
    public static void removeDataSourceType() {
        TYPES.remove();
    }

}

8. Define dynamic data sources

Define a dynamic data source, inherit the AbstractRoutingDataSource abstract class, and override the determineCurrentLookupKey() method.

AbstractRoutingDataSource is the key to the implementation of multiple data sources. It is used to dynamically switch data sources.

There is a target Data Sources collection in this class, which is a map type attribute of Abstract Routing DataSource, where key represents the name of each data source and value is each data source.

Then obtain the key value of the current data source in the map according to the determineCurrentLookupKey() method, and then dynamically obtain the current data source in the determineTargetDataSource() method. If the current data source does not exist and the default data source does not exist, an exception will be thrown.

package com.yyg.boot.datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * @Author One brother Sun
 * @Date Created in 2020/4/7
 * @Description Dynamically switch data sources
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceType.getDataSourceType();
    }

}

9. Configure multiple data sources

Here you can configure multiple data sources.

package com.yyg.boot.datasource;

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 javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author One brother Sun
 * @Date Created in 2020/4/7
 * @Description Multi data source configuration
 */
@Configuration
public class DynamicDataSourceConfig {

    @Bean(name = "dynamicDataSource")
    public DynamicDataSource dynamicDataSource(@Qualifier("ds1DataSource") DataSource ds1DataSource,
                                        @Qualifier("ds2DataSource") DataSource ds2DataSource) {
        Map<Object, Object> targetDataSource = new HashMap<>();
        targetDataSource.put(DataSourceType.SourceType.DS_SHOP, ds1DataSource);
        targetDataSource.put(DataSourceType.SourceType.DS_USER, ds2DataSource);
        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSource);
        dataSource.setDefaultTargetDataSource(ds2DataSource);
        return dataSource;
    }

}

10. Define AOP section

Write the AOP aspect class here and configure the pre notification method through the @ Before annotation.

package com.yyg.boot.datasource;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * @Author One brother Sun
 * @Date Created in 2020/4/7
 * @Description Description
 */
@Aspect
@Component
@Slf4j
public class DataSourceAop {

    @Before("execution(* com.yyg.boot.service.impl.GoodsServiceImpl.*(..))")
    public void setDataSource01() {
        log.warn("db01 Commodity data source");
        DataSourceType.setDataSourceType(DataSourceType.SourceType.DS_SHOP);
    }

    @Before("execution(* com.yyg.boot.service.impl.UserServiceImpl.*(..))")
    public void setDataSource02() {
        log.warn("db02 User data source");
        DataSourceType.setDataSourceType(DataSourceType.SourceType.DS_USER);
    }

}

11. Create Entity class

11.1 Goods

Encapsulates an entity class of commodity information.

package com.yyg.boot.entity;

import lombok.Data;

import javax.persistence.*;

/**
 * @Author One brother Sun
 * @Date Created in 2020/4/3
 * @Description db1 Item list in
 */
@Entity
@Table(name = "goods")
@Data
public class Goods {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    private String name;

}

11.2 User class

Entity class that encapsulates user information.

package com.yyg.boot.entity;

import lombok.Data;

import javax.persistence.*;

/**
 * @Author One brother Sun
 * @Date Created in 2020/4/3
 * @Description db4 User table in
 */
@Entity
@Table(name = "user")
@Data
public class User {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    private String username;

    private String birthday;

    private String sex;

    private String address;

}

12. Create Dao layer code 12.1 GoodsRepository class

12.1 GoodsRepository class

Write the database Dao layer interface for operating Goods commodity information.

package com.yyg.boot.dao.db01;

import com.yyg.boot.entity.Goods;
import com.yyg.boot.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;

/**
 * @Author One brother Sun
 * @Date Created in 2020/4/3
 * @Description Description
 */
@Repository
public interface GoodsRepository extends JpaRepository<GoodsLong>,JpaSpecificationExecutor<User> {
}

12.2 UserRepository class

Write the database Dao layer interface for operating User information.

package com.yyg.boot.dao.db02;

import com.yyg.boot.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;

/**
 * @Author One brother Sun
 * @Date Created in 2020/4/3
 * @Description Description
 */
@Repository
public interface UserRepository extends JpaRepository<UserLong>,JpaSpecificationExecutor<User> {
}

13. Create Service code

13.1 UserServiceImpl implementation class

Write UserServiceImpl implementation class and call UserRepository for database operation.

package com.yyg.boot.service.impl;

import com.yyg.boot.dao.db02.UserRepository;
import com.yyg.boot.entity.User;
import com.yyg.boot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @Author One brother Sun
 * @Date Created in 2020/4/7
 * @Description Description
 */
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public List<User> findAll() {
        return userRepository.findAll();
    }

}

13.2 GoodsServiceImpl implementation class

Write the GoodsServiceImpl implementation class and call GoodsRepository for database operation.

package com.yyg.boot.service.impl;

import com.yyg.boot.dao.db01.GoodsRepository;
import com.yyg.boot.dao.db02.UserRepository;
import com.yyg.boot.entity.Goods;
import com.yyg.boot.entity.User;
import com.yyg.boot.service.GoodsService;
import com.yyg.boot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @Author One brother Sun
 * @Date Created in 2020/4/7
 * @Description Description
 */
@Service
public class GoodsServiceImpl implements GoodsService {

    @Autowired
    private GoodsRepository goodsRepository;

    @Override
    public List<Goods> findAll() {
        return goodsRepository.findAll();
    }

}

14. Create Controller interface

Then we create a Controller class and define several interfaces here for later testing.

package com.yyg.boot.web;

import com.yyg.boot.entity.Goods;
import com.yyg.boot.entity.User;
import com.yyg.boot.service.GoodsService;
import com.yyg.boot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @Author One brother Sun
 * @Date Created in 2020/4/3
 * @Description Description
 */
@RestController
public class GoodsController {

    @Autowired
    private UserService userService;

    @Autowired
    private GoodsService goodsService;

    @GetMapping(value = "/users")
    public List<User> users() {
        return userService.findAll();
    }

    @GetMapping(value = "/goods")
    public List<Goods> goods() {
        return goodsService.findAll();
    }

}

15. Create an entry class

Finally, write a project entry class to start the project.

package com.yyg.boot;

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

/**
 * @Author One brother Sun
 * @Date Created in 2020/4/3
 * @Description Description
 */
@SpringBootApplication
public class DataSourceApplication {

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

}

16. Project structure

Finally, we can take a look at the code structure of the project. You can refer to the following figure to create it.

17. Operation test

17.1 test commodity database

Let's start the project. First, test the goods interface. Here we query the data in the db1 database.

This corresponds to the data in the db1 database.

17.2 test user database

Then we will test the users interface, where the data in the db4 database is queried.

This corresponds to the data in the db4 database.

 

If you need more tutorials, just scan the code on wechat

 

👆👆👆

Don't forget to scan the code to get the information [HD Java learning roadmap]

And [full set of learning videos and supporting materials of various disciplines]

 

Topics: Java Spring Spring Boot