SpringBoot 2.0 +spring-data-jpa 自定义注解完成数据库切库

1、pom中添加

    <!--spring boot-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
         <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.29</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

2、配置文件中添加

############ 主库datasource master ###########
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=nature
spring.datasource.password=#####
spring.datasource.url=jdbc:mysql://192.168.x.xxx:3306/nature_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false

###########从库 datasource slave ############ #spring-boot2.0中datasource-slave不能写成datasourceSlave不然会报错
spring.datasource-slave.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource-slave.driver-class-name=com.mysql.jdbc.Driver
spring.datasource-slave.username=health
spring.datasource-slave.password=####
spring.datasource-slave.url=jdbc:mysql://192.168.x.xxx:3306/health_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false

#################jpa###########################
spring.jpa.show-sql=false
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy

3、定义支持的数据源key

package com.example.demo.constant;

/**
 * @author zhuenbang
 * @description 定义支持的数据源key
 * @date 2018/8/13 11:26
 */
public interface DataSources {
    /**
    *主库
    */
    String MASTER_DB = "masterDB";

    /**
    *从库
    */
    String SLAVE_DB = "slaveDB";
}

4、自定义数据库注解

package com.example.demo.anno;

import com.example.demo.constant.DataSources;

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

/**
 * @author zhuenbang
 * @description 自定义数据库注解
 * @date 2018/8/13 11:43
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface RouteDataSource {
    String value() default DataSources.MASTER_DB; //默认为主数据源
}

5、ThreadLocal安全的管理当前进程使用的数据源连接

package com.example.demo.config;

import com.example.demo.constant.DataSources;
import org.jboss.logging.Logger;

/**
 * @author zhuenbang
 * @description
 * @date 2018/8/13 12:57
 */
public class DataSourceContextHolder {
    private static Logger logger  = Logger.getLogger(DataSourceContextHolder.class);
    /**
     * 默认数据源
     */
    public static final String DEFAULT_DATASOURCE = DataSources.MASTER_DB;

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    // 设置数据源名
    public static void setDB(String dbType) {
        logger.debugv("切换到{}数据源", dbType);
        contextHolder.set(dbType);
    }

    // 获取数据源名
    public static String getDB() {
        return (contextHolder.get());
    }

    // 清除数据源名
    public static void clearDB() {
        contextHolder.remove();
    }
}

6、自定义切库注解的方法进行拦截,动态的选择数据源

package com.example.demo.apsect;

/**
 * @author zhuenbang
 * @description
 * @date 2018/8/13 12:54
 */

import com.example.demo.anno.RouteDataSource;
import com.example.demo.config.DataSourceContextHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.jboss.logging.Logger;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
public class DynamicDataSourceAspect {
    private static Logger logger = Logger.getLogger(DynamicDataSourceAspect.class);
    @Before("@annotation(com.example.demo.anno.RouteDataSource)")
    public void beforeSwitchDS(JoinPoint point){
        //获得当前访问的class
        Class<?> className = point.getTarget().getClass();
        //获得访问的方法名
        String methodName = point.getSignature().getName();
        //得到方法的参数的类型
        Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
        String dataSource = DataSourceContextHolder.DEFAULT_DATASOURCE;
        try {
            // 得到访问的方法对象
            Method method = className.getMethod(methodName, argClass);
            // 判断是否存在@DS注解
            if (method.isAnnotationPresent(RouteDataSource.class)) {
                RouteDataSource annotation = method.getAnnotation(RouteDataSource.class);
                // 取出注解中的数据源名
                dataSource = annotation.value();
            }
        } catch (Exception e) {
            logger.error("routing datasource exception, " + methodName, e);
        }
        // 切换数据源
        DataSourceContextHolder.setDB(dataSource);
    }

    @After("@annotation(com.example.demo.anno.RouteDataSource)")
    public void afterSwitchDS(JoinPoint point){
        DataSourceContextHolder.clearDB();
    }
}

7、定义数据库实体类并配置为多数据源的形式

package com.example.demo.config;
import com.alibaba.druid.pool.DruidDataSource;
import javax.sql.DataSource;
import com.example.demo.constant.DataSources;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
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;

/**
 * @author zhuenbang
 * @description 定义数据库实体类并配置为多数据源的形式
 * @date 2018/8/13 11:28
 */
@Configuration
public class DatasourceConfig {
    private final static String MASTER_DATASOURCE_KEY = DataSources.MASTER_DB;
    private final static String SLAVE_DATASOURCE_KEY = DataSources.SLAVE_DB;

    //destroy-method="close"的作用是当数据库连接不使用的时候,就把该连接重新放到数据池中,方便下次使用调用.
    @Value("${spring.datasource-slave.type}")
    private Class<? extends DataSource> dataSourceType;

    @Primary
    @Qualifier(MASTER_DATASOURCE_KEY)
    @Bean(destroyMethod =  "close", name = DataSources.MASTER_DB, initMethod="init")
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        System.out.println("-------------------- primaryDataSource初始化 ---------------------");
        return DataSourceBuilder.create().type(dataSourceType).build();
    }

    @Bean(destroyMethod =  "close", name = DataSources.SLAVE_DB,initMethod="init")
    @Qualifier(SLAVE_DATASOURCE_KEY)
    @ConfigurationProperties(prefix = "spring.datasource-slave")
    public DataSource dataSourceSlave() {
        System.out.println("-------------------- slaveDataSource初始化---------------------");
        return DataSourceBuilder.create().type(dataSourceType).build();
    }
}

8、取出切面里设置的数据源key

package com.example.demo.config;

import org.jboss.logging.Logger;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * @author zhuenbang
 * @description
 * @date 2018/8/13 13:03
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    private static Logger logger = Logger.getLogger(DynamicDataSource.class);
    @Override
    protected Object determineCurrentLookupKey() {
        logger.debugv("数据源为{}", DataSourceContextHolder.getDB());
        return DataSourceContextHolder.getDB();
    }
}

9、动态数据源

package com.example.demo.config;

/**
 * @author zhuenbang
 * @description
 * @date 2018/8/13 12:15
 */

import com.example.demo.constant.DataSources;
import com.google.common.collect.Maps;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
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.boot.orm.jpa.EntityManagerFactoryBuilder;
import javax.annotation.Resource;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.Map;

@SuppressWarnings("all")
@Configuration
@EnableConfigurationProperties(JpaProperties.class)
@EnableJpaRepositories(value = "com.example.demo.db.repository",
        entityManagerFactoryRef = "entityManagerFactory",
        transactionManagerRef = "transactionManager")
public class MyjpaConfig {
    @Autowired
    private JpaProperties jpaProperties;
    @Resource(name = DataSources.MASTER_DB)
    private DataSource masterDB;
    @Resource(name = DataSources.SLAVE_DB)
    private DataSource slaveDB;
    /**
     * 动态数据源
     */
    @Bean(name = "dynamicDataSource")
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 默认数据源
        dynamicDataSource.setDefaultTargetDataSource(masterDB);
        // 配置多数据源
        Map<Object, Object> dsMap = Maps.newHashMap();
        dsMap.put(DataSources.MASTER_DB, masterDB);
        dsMap.put(DataSources.SLAVE_DB, slaveDB);
        dynamicDataSource.setTargetDataSources(dsMap);
        return dynamicDataSource;
    }

    @Bean(name = "entityManagerFactoryBean")
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(EntityManagerFactoryBuilder builder) {
        Map<String, String> properties = jpaProperties.getProperties();
        properties.put("hibernate.physical_naming_strategy",
                "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
        return builder
                .dataSource(dynamicDataSource())
                .properties(properties)
                .packages("com.example.demo.db.entity") //设置实体类所在位置
                .build();
    }

    @Primary
    @Bean(name = "entityManagerFactory")
    public EntityManagerFactory entityManagerFactory(EntityManagerFactoryBuilder builder) {
        return this.entityManagerFactoryBean(builder).getObject();
    }

    @Primary
    @Bean(name = "transactionManager")
    public PlatformTransactionManager transactionManager(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(entityManagerFactory(builder));
    }
}

10、单元测试

这里写图片描述
这里写图片描述
@RouteDataSource 为空的话默认为主数据源

猜你喜欢

转载自blog.csdn.net/workit123/article/details/81633393