本文记录下通过注解动态配置数据源的实现方式,可以是不同的数据库(如读写分离),也可以是mycat代理的分表数据源。废话不多,上干货——

一、数据库配置文件(这里用的是阿波罗配置中心,也可以是application.yml文件)

#mysql本地数据源1
spring.datasource.db1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.db1.jdbc-url=jdbc:mysql://127.0.0.1:3306/mysqlDB1?useUnicode=true&zeroDateTimeBehavior=convertToNull&characterEncoding=UTF-8&useSSL=false
spring.datasource.db1.username=root
spring.datasource.db1.password=123456

#mysql本地数据源2
spring.datasource.db2.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.db2.jdbc-url=jdbc:mysql://127.0.0.1:3306/mysqlDB2?useUnicode=true&zeroDateTimeBehavior=convertToNull&characterEncoding=UTF-8&useSSL=false
spring.datasource.db2.username=root
spring.datasource.db2.password=123456

#mycat本地数据源
spring.datasource.mycat.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.mycat.jdbc-url=jdbc:mysql://192.168.0.110:3306/mycatDB?useUnicode=true&zeroDateTimeBehavior=convertToNull&characterEncoding=UTF-8&useSSL=false
spring.datasource.mycat.username=root
spring.datasource.mycat.password=123456

二、java代码功能部分(根据自己的需求配置实现,不需要mycat分表的可以不配置mycat部分)

   1、首先实现数据源配置

import com.xxx.xxx.aop.DataSourceNames;
import com.xxx.xxx.aop.DynamicDataSource;
import org.mybatis.spring.annotation.MapperScan;
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.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;

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

/**
 * 数据源配置
 *
 * @Author Ldpy
 * @Date 2021/11/30 16:50
 * @Version 1.0
 **/
@Configuration
@Component
@MapperScan(basePackages = DataSourceConfig.PACKAGE, sqlSessionFactoryRef = "mysqlSqlSessionFactory")
public class DataSourceConfig {

    static final String PACKAGE = "com.xxx.xxx.mapper";
    static final String MAPPER_LOCATION = "classpath*:com/xxx/xxx/mapper/xml/*.xml";

    @Bean(name = "mysqlDatasource1")
    @ConfigurationProperties("spring.datasource.db1")
    public DataSource mysqlDataSource1(){
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "mysqlDatasource2")
    @ConfigurationProperties("spring.datasource.db2")
    public DataSource mysqlDataSource2(){
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "mycatDatasource")
    @ConfigurationProperties("spring.datasource.mycat")
    public DataSource mycatDataSource(){
        return DataSourceBuilder.create().build();
    }

    @Primary
    @Bean(name = "mysqlSqlSessionFactory")
    public SqlSessionFactory mysqlSqlSessionFactory(
            @Qualifier("mysqlDatasource1") DataSource mysqlSqlSessionFactory) throws Exception {
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(mysqlSqlSessionFactory);
        sessionFactory.setDataSource(dataSource(mysqlDataSource1(),mysqlDataSource2(),mycatDataSource()));
        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources(MAPPER_LOCATION));
        return sessionFactory.getObject();
    }

    @Primary
    @Bean("mysqlSqlSessionTemplate")
    public SqlSessionTemplate mysqlSqlSessionTemplate(
            @Qualifier("mysqlSqlSessionFactory") SqlSessionFactory mysqlSqlSessionFactory) {
        return new SqlSessionTemplate(mysqlSqlSessionFactory);
    }

    @Bean(name = "dataSource")
    @Primary
    public DynamicDataSource dataSource(@Qualifier("mysqlDatasource1") DataSource mysqlDataSource1, 
                                        @Qualifier("mysqlDatasource2") DataSource mysqlDataSource2, 
                                        @Qualifier("mycatDatasource") DataSource mycatDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceNames.MYSQLDB1, mysqlDataSource1);
        targetDataSources.put(DataSourceNames.MYSQLDB2, mysqlDataSource2);
        targetDataSources.put(DataSourceNames.MYCATDB, mycatDataSource);
        return new DynamicDataSource(mysqlDataSource1, targetDataSources);
    }

    /**
     * 注入事务管理器
     * @author Ldpy
     * @return
     */
    @Bean
    public DataSourceTransactionManager dataSourceTransactionManager(@Qualifier("dataSource") DataSource ds) {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(ds);
        return dataSourceTransactionManager;
    }

    /**
     * 注入事务管理器也可以简单这样写,与上面一个意思
     * @author Ldpy
     * @return
     */
    /*@Bean
    public DataSourceTransactionManager dataSourceTransactionManager(@Qualifier("mysqlDatasource1") DataSource mysqlDataSource1, 
                                        @Qualifier("mysqlDatasource2") DataSource mysqlDataSource2, 
                                        @Qualifier("mycatDatasource") DataSource mycatDataSource) {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource(mysqlDataSource1,mysqlDataSource2,mycatDataSource));
        return dataSourceTransactionManager;
    }*/

}

   2、定义数据源注解常量 

/**
 * 数据源注解常量
 *
 * @Author Ldpy
 * @Date 2021/11/30 16:55
 * @Version 1.0
 **/
public interface DataSourceNames {
    String MYSQLDB1 = "mysqlDB1";
    String MYSQLDB2 = "mysqlDB2";
    String MYCATDB = "mycatDB";
}

   3、定义数据源注解 

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 数据源注解
 *
 * @Author Ldpy
 * @Date 2021/11/30 16:55
 * @Version 1.0
 **/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface DataSource {

    String dataSourceName() default DataSourceNames.MYSQLDB1;

}

   4、实现数据源注解 

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 数据源注解切面实现
 *
 * @Author Ldpy
 * @Date 2021/11/30 16:58
 * @Version 1.0
 **/
@Component
@Aspect
@Order(0)
public class DataSourceSection {

    @Pointcut("@annotation(com.xxx.xxx.aop.DataSource)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();

        DataSource ds = method.getAnnotation(DataSource.class);
        if(ds == null){
            DynamicDataSource.setDataSource(DataSourceNames.MYSQLDB1);
        }else {
            DynamicDataSource.setDataSource(ds.dataSourceName());
        }

        try {
            return point.proceed();
        } finally {
            DynamicDataSource.clearDataSource();
        }
    }

}

    5、动态数据源实现

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

import javax.sql.DataSource;
import java.util.Map;

/**
 * 动态数据源
 *
 * @Author Ldpy
 * @Date 2021/11/30 16:50
 * @Version 1.0
 **/
public class DynamicDataSource extends AbstractRoutingDataSource {

    // 用来保存数据源与获取数据源
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return getDataSource();
    }

    public static void setDataSource(String dataSource) {
        contextHolder.set(dataSource);
    }

    public static String getDataSource() {
        return contextHolder.get();
    }

    public static void clearDataSource() {
        contextHolder.remove();
    }

}

三、测试部分

    1、编写启动类(注意添加@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class })屏蔽springboot的数据源自动配置)

import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class })
@EnableScheduling
@EnableApolloConfig
@MapperScan("com.xxx.xxx.mysql.mapper*")
public class MyApplication {

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

}

    2、常规测试

import com.xxx.xxx.mysql.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;


/**
 * @Description 测试
 * @Author Ldpy
 * @Date 2021/11/30
 */
@RestController
@RequestMapping("/test")
public class TestController {

    @Autowired
    private TestService testService;

    /**
     * 查询
     *
     * @Author Ldpy
     * @Date 2021/11/30
     */
    @PostMapping("manualOperation")
    public List<Map<String, Object>> manualOperation(String table, String imei) {
        return testService.manualOperation(table, imei);
    }

    /**
     * 修改
     *
     * @Author Ldpy
     * @Date 2021/11/30
     */
    @PostMapping("manualUpdate")
    public Map<String, Object> manualUpdate(String table, String imei) {
        return testService.manualUpdate(table, imei);
    }

}
import java.util.List;
import java.util.Map;


public interface TestService {

    /**
     * @Author Ldpy
     * @Description 查询测试
     * @Date 2021/12/1 18:34
     **/
    List<Map<String, Object>> manualOperation(String table, String imei);

    /**
     * @Author Ldpy
     * @Description 更新测试
     * @Date 2021/12/1 18:34
     **/
    Map<String, Object> manualUpdate(String table, String imei);

}
import com.xxx.xxx.aop.DataSource;
import com.xxx.xxx.aop.DataSourceNames;
import com.xxx.xxx.mysql.TestService;
import com.xxx.xxx.mysql.mapper.TestMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.List;
import java.util.Map;


@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private TestMapper testMapper;

    // 需要哪个数据源直接添加注解即可,不加默认用MYSQLDB1
    @Override
    @DataSource(dataSourceName = DataSourceNames.MYSQLDB2)
    public List<Map<String, Object>> manualOperation(String table, String imei) {
        return testMapper.manualOperation(table, imei);
    }

    // 测试事务
    @Override
    @Transactional
    @DataSource(dataSourceName = DataSourceNames.MYCATDB)
    public Map<String, Object> manualUpdate(String table, String imei) {
        testMapper.manualDelete(table, imei);
        testMapper.manualUpdate(table, imei);
        Map<String, Object> map = new HashMap<>();
        map.put("result", 2/0);
        return map;
    }
}
import org.apache.ibatis.annotations.Param;

import java.util.List;
import java.util.Map;


public interface TestMapper {

    List<Map<String, Object>> manualOperation(@Param("table") String table, @Param("imei") String imei);

    int manualDelete(@Param("table") String table, @Param("imei") String imei);

    int manualUpdate(@Param("table") String table, @Param("imei") String imei);

}
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.xxx.xxx.mysql.mapper.TestMapper">

    <select id="manualOperation" resultType="map">
        SELECT
            字段1, 字段2
        FROM
            表名
        WHERE
            imei = #{imei}
    </select>

    <update id="manualDelete">
        DELETE FROM
            表名
        WHERE
            imei = #{imei}
    </update>

    <update id="manualUpdate">
        INSERT INTO `表名`(`imei`, `len`, `run_time`)
        VALUES ('124526', 381.50, 24)
    </update>

四、遇到的问题

    1、数据库连接问题:(dataSource or dataSourceClassName or jdbcUrl is required.)MySQL版本不同,jdbc-url 和 driver-class-name 的写法不同,需要注意;

    2、事务不生效:首先考虑MySQL引擎,InnoDB 引擎支持事务,而 MyISAM 引擎不支持事务,期次看下是否在配置数据源时忘记注入事务管理器。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐