Preface:

Application as the increase of the number of users, the number of concurrent requests corresponding will also follow the increasing, gradually, a single database has no way to meet our frequent database operation request, in some scenarios, we might need to configure the multiple data sources, using multiple data sources (such as separate database, speaking, reading and writing) to alleviate the pressure of system, etc., in the same way, Springboot provides the corresponding implementation to help developers to configure multiple data sources, generally divided into two ways (as far as I know), subcontracting and AOP, and in the last article Springboot +Mybatis implementation of multiple data source configuration, we implemented static multi-data source configuration. However, this approach is not flexible enough in practice. In order to solve this problem, we can use the second approach mentioned above, which uses AOP aspect oriented programming with our custom annotations to achieve the purpose of dynamic switching between different data sources.

1. Database preparation:

The database preparation is the same as in the previous example, the specific SQL statement is not detailed, the table is as follows:

The database testdatasource1 testdatasource2
The data table sys_user sys_user2
field User_id (int), user_name(varchar) user_age (int) with

Two records were inserted respectively. Testdatasource1 was zhang SAN, who was 25 years old, and Testdatasource2 was Li Si, who was 30 years old.

2. Environment Preparation:

First, create a new Springboot project (version 2.1.7.release), and introduce related dependencies in the POM file. Compared with last time, this time mainly added additional AOP related dependencies, as follows:

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

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


        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.1.5. RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.2</version>
        </dependency>
Copy the code

3. Code

First, we can configure our datasourt in our Springboot configuration file. Unlike previous datasourts, we can specify the name of our database where the primary data source is primary and the secondary data source is secondary.

# configuration primary database spring. The datasource. Primary. JDBC - url = JDBC: mysql: / / localhost: 3306 / testdatasource1? useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false spring.datasource.primary.username=root Spring. The datasource. Primary. Password = root spring. The datasource. Primary. The driver - class - name = com. Mysql. Cj). The JDBC driver # # configuration database spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/testdatasource2? useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false spring.datasource.secondary.username=root spring.datasource.secondary.password=root spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver spring.http.encoding.charset=UTF-8 spring.http.encoding.enabled=true spring.http.encoding.force=trueCopy the code

It is important to note that Springboot2.0 requires jdbc-url when configuring database connections, which will be reported if only url is used

jdbcUrl is required with driverClassName. Error.

Create a new configuration file, DynamicDataSourceConfig, to configure our associated bean

@Configuration
@MapperScan(basePackages = "com.mzd.multipledatasources.mapper", sqlSessionFactoryRef = "SqlSessionFactory") //basePackages The address of our interface file
public class DynamicDataSourceConfig {

    // Put this object into the Spring container
    @Bean(name = "PrimaryDataSource")
    // Indicates this data source is the default data source
    @Primary
    // Read the configuration parameters in application.properties and map them to an object
    // prefix indicates the parameter prefix
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource getDateSource1(a) {
        return DataSourceBuilder.create().build();
    }


    @Bean(name = "SecondaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource getDateSource2(a) {
        return DataSourceBuilder.create().build();
    }

    
    @Bean(name = "dynamicDataSource")
    public DynamicDataSource DataSource(@Qualifier("PrimaryDataSource") DataSource primaryDataSource,
                                        @Qualifier("SecondaryDataSource") DataSource secondaryDataSource) {
        
        // This place is the comparison core targetDataSource collection is the mapping between our database and the name
        Map<Object, Object> targetDataSource = new HashMap<>();
        targetDataSource.put(DataSourceType.DataBaseType.Primary, primaryDataSource);
        targetDataSource.put(DataSourceType.DataBaseType.Secondary, secondaryDataSource);
        DynamicDataSource dataSource = new DynamicDataSource(); 
        dataSource.setTargetDataSources(targetDataSource);
        dataSource.setDefaultTargetDataSource(primaryDataSource);// Set the default object
        return dataSource;
    }
    
    
    @Bean(name = "SqlSessionFactory")
    public SqlSessionFactory SqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dynamicDataSource);
        bean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/*/*.xml"));// Set our XML file path
        returnbean.getObject(); }}Copy the code

DynamicDataSource class DynamicDataSource class DynamicDataSource class DynamicDataSource class This class inherits AbstractRoutingDataSource class and write its determineCurrentLookupKey () method.

AbstractRoutingDataSource internal maintains a class called targetDataSources Map, and provide the setter method is used to set data source keyword and the relationship between the data source, Implementation classes are required to achieve its determineCurrentLookupKey () method, this method return values determine specific get connection from which data source. AbstractRoutingDataSource class provides the program run at the same time the dynamic switching method of data source in the dao classes or methods need to access the data on the labeling of keywords, routing to specify the data source, for the connection.

The DynamicDataSource code is as follows:

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey(a) {
        DataSourceType.DataBaseType dataBaseType = DataSourceType.getDataBaseType();
        returndataBaseType; }}Copy the code

The code for the DataSourceType class is as follows:

public class DataSourceType {

    // An internal enumeration class for selecting a specific data type
    public enum DataBaseType {
        Primary, Secondary
    }

    // Use ThreadLocal to ensure thread safety
    private static final ThreadLocal<DataBaseType> TYPE = new ThreadLocal<DataBaseType>();

    // Set the data source type to the current thread
    public static void setDataBaseType(DataBaseType dataBaseType) {
        if (dataBaseType == null) {
            throw new NullPointerException();
        }
        TYPE.set(dataBaseType);
    }

    // Get the data source type
    public static DataBaseType getDataBaseType(a) {
        DataBaseType dataBaseType = TYPE.get() == null ? DataBaseType.Primary : TYPE.get();
        return dataBaseType;
    }

    // Clear the data type
    public static void clearDataBaseType(a) { TYPE.remove(); }}Copy the code

Next, write our related Mapper and XML files as follows:

@Component
@Mapper
public interface PrimaryUserMapper {

    List<User> findAll(a);
}


@Component
@Mapper
public interface SecondaryUserMapper {

    List<User> findAll(a);
}


Copy the code
<?xml version="1.0" encoding="UTF-8" ? >

      
<mapper namespace="com.jdkcb.mybatisstuday.mapper.one.PrimaryUserMapper">

    <select id="findAll" resultType="com.jdkcb.mybatisstuday.pojo.User">
                select * from sys_user;
    </select>

</mapper>


<?xml version="1.0" encoding="UTF-8" ? >

      
<mapper namespace="com.jdkcb.mybatisstuday.mapper.two.SecondaryUserMapper">

    <select id="findAll" resultType="com.jdkcb.mybatisstuday.pojo.User">
                select * from sys_user2;
    </select>


</mapper>
Copy the code

With the relevant interface files in place, we are ready to write our AOP code:

@Aspect
@Component
public class DataSourceAop {
    // Execute before the primary method
    @Before("execution(* com.jdkcb.mybatisstuday.controller.UserController.primary(..) )")
    public void setDataSource2test01(a) {
        System.err.println("The Primary business");
        DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary);
    }

    // Execute before the secondary method
    @Before("execution(* com.jdkcb.mybatisstuday.controller.UserController.secondary(..) )")
    public void setDataSource2test02(a) {
        System.err.println("Secondary business"); DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Secondary); }}Copy the code

Write our test UserController: as follows:

@RestController
public class UserController {

    @Autowired
    private PrimaryUserMapper primaryUserMapper;
    @Autowired
    private SecondaryUserMapper secondaryUserMapper;


    @RequestMapping("primary")
    public Object primary(a){
        List<User> list = primaryUserMapper.findAll();
        return list;
    }
    @RequestMapping("secondary")
    public Object secondary(a){
        List<User> list = secondaryUserMapper.findAll();
        returnlist; }}Copy the code

4. Test:

Start the project, in the browser type http://127.0.0.1:8080/primary and http://127.0.0.1:8080/primary respectively, the results are as follows:

[{"user_id":1."user_name":"Zhang"."user_age":25}]

[{"user_id":1."user_name":"Bill"."user_age":30}]
Copy the code

5. Etc.

Wait, wait, wait, wait, wait, wait, wait, wait, wait, wait, wait, wait, wait, wait, wait, wait, wait, wait, wait, wait, wait, wait, wait.

That certainly can’t, AOP also has the benefit of AOP, such as two packages of code using two different data sources, can be directly used aop expression can be completed, but, if you want to this example of method level interception, it appears that the advantage is not too flexible, this suitable for our annotations play.

6. Implement with annotations

Start by customizing our @datasource annotation

/** * Toggle data annotations can be used at class or method level method level priority > class level */
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String value(a) default "primary"; // This value is the key value and the default database is used
}
Copy the code

Get the value of the attribute value of the annotation using AOP interception. If the value of value is not in our DataBaseType, use our default data source and switch to the corresponding data source if it is.

@Aspect
@Component
public class DynamicDataSourceAspect {
    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);

    @Before("@annotation(dataSource)")// Intercept our annotations
    public void changeDataSource(JoinPoint point, DataSource dataSource) throws Throwable {
        String value = dataSource.value();
        if (value.equals("primary")){
            DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary);
        }else if (value.equals("secondary")){
            DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Secondary);
        }else {
            DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary);// Use the primary database by default}}@After("@annotation(dataSource)") // Clear the data source configuration
    public void restoreDataSource(JoinPoint point, DataSource dataSource) { DataSourceType.clearDataBaseType(); }}Copy the code

7. How to Lose weight

Modify our Mapper by adding our custom @datasouse annotation and annotating the contents of our DataSourceAop class. As follows:

@Component
@Mapper
public interface PrimaryUserMapper {

    @DataSource
    List<User> findAll(a);
}

@Component
@Mapper
public interface SecondaryUserMapper {

    @DataSource("secondary")// Specify the data source as :secondary
    List<User> findAll(a);
}


Copy the code

Start the project, in the browser type http://127.0.0.1:8080/primary and http://127.0.0.1:8080/primary respectively, the results are as follows:

[{"user_id":1."user_name":"Zhang"."user_age":25}]

[{"user_id":1."user_name":"Bill"."user_age":30}]
Copy the code

At this point, we are really done.

Finally finally, everybody is good, I am Han number, hum, pay attention to me, have good fruit to eat (akimbo).

Make sure you like it before you leave

Wait a minute:

Welcome to github for more information:

Github.com/hanshuaikan…