SpringBootJava

SpringBoot+JPA多数据源(注解方式)

2019-06-28  本文已影响1人  maxzhao_
title: SpringBoot+JPA多数据源(注解方式)
date: 2019-06-27
author: maxzhao
tags:
  - JAVA
  - 多数据源
  - SpringBoot
  - 自定义注解
  - AOP
  - MYSQL8
categories:
  - SpringBoot
  - JPA
  - JAVA

First

优点

缺点

构建

添加依赖

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>druid-spring-boot-starter</artifactId>
   <version>1.1.16</version>
</dependency>
<!--使用啦Lombok插件,需要自己添加 其它需要自己添加了-->

配置文件

spring:
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/maxzhao_ittest?charset=utf8mb4&useSSL=false
    username: maxzhao
    password: maxzhao
  main:
    allow-bean-definition-overriding: true

  jpa:
    database: MYSQL
    database-plinatform: org.hibernate.dialect.MySQL5InnoDBDialect
    show-sql: true
    generate-ddl: true
    open-in-view: false

    hibernate:
      ddl-auto: update
    #       naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy
    properties:
      #不加此配置,获取不到当前currentsession
      hibernate:
        current_session_context_class: org.springframework.orm.hibernate5.SpringSessionContext
        dialect: org.hibernate.dialect.MySQL5Dialect
# 多数据源配置
gt:
  maxzhao:
    boot:
    #主动开启多数据源
      multiDatasourceOpen: true
      datasource[0]:
        dbName: second
        driverClassName: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/pos?charset=utf8mb4&useSSL=false
        username: maxzhao
        password: maxzhao
      datasource[1]:
        dbName: third
        driverClassName: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/biz?charset=utf8mb4&useSSL=false
        username: maxzhao
        password: maxzhao

添加注解类

package gt.maxzhao.boot.common.annotation;

import java.lang.annotation.*;

/**
 * <p>多数据源标识</p>
 * <p>使用方式:必须用在方法上</p>
 *
 * @author maxzhao
 * @date 2019-06-26 16:13
 */
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MultiDataSource {
    String name() default "main";
}

数据源配置映射 yml配置类

package gt.maxzhao.boot.config.source.model;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.util.JdbcConstants;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.sql.SQLException;

/**
 * <p>多数据源配置</p>
 * <p>数据库数据源配置</p>
 * <p>说明:这个类中包含了许多默认配置,建议不要修改本类,直接在"application.yml"中配置即可</p>
 *
 * @author maxzhao
 * @date 2019-06-26 16:13
 */
@Component
@ConfigurationProperties(prefix = "spring.datasource")
@Setter
@Getter
@Slf4j
public class DruidProperties {
    public DruidProperties() {
        log.info("default 数据源加载");
    }

    /**
     * 数据源名称
     */
    private String dbName = "main";

    private String url;

    private String username;

    private String password;
    /**
     * 默认为 MYSQL 8.x 配置
     */
    private String driverClassName = "com.mysql.cj.jdbc.Driver";

    private Integer initialSize = 10;

    private Integer minIdle = 3;

    private Integer maxActive = 60;

    private Integer maxWait = 60000;

    private Boolean removeAbandoned = true;

    private Integer removeAbandonedTimeout = 180;

    private Integer timeBetweenEvictionRunsMillis = 60000;

    private Integer minEvictableIdleTimeMillis = 300000;

    private String validationQuery = "SELECT 'x'";

    private Boolean testWhileIdle = true;

    private Boolean testOnBorrow = false;

    private Boolean testOnReturn = false;

    private Boolean poolPreparedStatements = true;

    private Integer maxPoolPreparedStatementPerConnectionSize = 50;

    private String filters = "stat";

    public DruidDataSource config() {
        DruidDataSource dataSource = new DruidDataSource();
        return config(dataSource);
    }

    public DruidDataSource config(DruidDataSource dataSource) {
        dataSource.setDbType(JdbcConstants.MYSQL);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setDriverClassName(driverClassName);
        dataSource.setInitialSize(initialSize);     // 定义初始连接数
        dataSource.setMinIdle(minIdle);             // 最小空闲
        dataSource.setMaxActive(maxActive);         // 定义最大连接数
        dataSource.setMaxWait(maxWait);             // 获取连接等待超时的时间
        dataSource.setRemoveAbandoned(removeAbandoned); // 超过时间限制是否回收
        dataSource.setRemoveAbandonedTimeout(removeAbandonedTimeout); // 超过时间限制多长

        // 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        // 配置一个连接在池中最小生存的时间,单位是毫秒
        dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        // 用来检测连接是否有效的sql,要求是一个查询语句
        dataSource.setValidationQuery(validationQuery);
        // 申请连接的时候检测
        dataSource.setTestWhileIdle(testWhileIdle);
        // 申请连接时执行validationQuery检测连接是否有效,配置为true会降低性能
        dataSource.setTestOnBorrow(testOnBorrow);
        // 归还连接时执行validationQuery检测连接是否有效,配置为true会降低性能
        dataSource.setTestOnReturn(testOnReturn);
        // 打开PSCache,并且指定每个连接上PSCache的大小
        dataSource.setPoolPreparedStatements(poolPreparedStatements);
        dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
        // 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:
        // 监控统计用的filter:stat
        // 日志用的filter:log4j
        // 防御SQL注入的filter:wall
        try {
            dataSource.setFilters(filters);
        } catch (SQLException e) {
            log.error("扩展插件失败.{}", e.getMessage());
        }
        return dataSource;
    }

}

多数据源配置映射 yml配置类

package gt.maxzhao.boot.config.source;

import gt.maxzhao.boot.config.source.model.DruidProperties;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import java.util.List;

/**
 * <p>多数据源配置</p>
 * <p>多个数据源</p>
 *
 * @author maxzhao
 * @date 2019-06-26 16:22
 */
@Configuration
@ConfigurationProperties(prefix = "gt.maxzhao.boot")
@Getter
@Setter
@Slf4j
public class MultiDataSource {
    public MultiDataSource() {
        log.info("加载多数据源配置信息  -->  {}", "gt.maxzhao.boot.datasource");
    }
    /**
     * 多个数据源
     */
    private List<DruidProperties> datasource;
}

多数据源配置类

这里需要配置动态开启多数据源,如果不主动开启,配置了注解也不会生效。

这里也做了一个不必要的处理,如果多数据源中有处理失败或名称填写错误,默认使用主数据源。

/**
 * <p>多数据源配置</p>
 * <p>多数据源配置</p>
 *
 * @author maxzhao
 * @date 2019-06-26 16:07
 */
@Slf4j
@Component
public class MultiSourceConfig {
    @Autowired
    private DruidProperties druidProperties;

    @Autowired
    private MultiDataSource multiDataSource;


    /**
     * 单数据源连接池配置
     */
    @Bean
    @ConditionalOnProperty(name = "gt.maxzhao.boot.multiDatasourceOpen", havingValue = "false")
    public DruidDataSource singleDatasource() {
        log.error("singleDatasource");
        return druidProperties.config(new DruidDataSource());
    }

    /**
     * 多数据源连接池配置
     */
    @Bean
    @ConditionalOnProperty(name = "gt.maxzhao.boot.multiDatasourceOpen", havingValue = "true")
    public DynamicDataSource mutiDataSource() {
        log.error("mutiDataSource");

        //存储数据源别名与数据源的映射
        HashMap<Object, Object> dbNameMap = new HashMap<>();
        // 核心数据源
        DruidDataSource mainDataSource = druidProperties.config();
        // 这里添加 主要数据库,其它数据库挂了,默认使用主数据库
        dbNameMap.put("main", mainDataSource);
        // 其它数据源
        // 当前多数据源是否存在
        if (multiDataSource.getDatasource() != null) {
            //过滤掉没有添加 dbName 的数据源,先加载娟全局配置,再次加载当前配置
            List<DruidDataSource> multiDataSourceList = multiDataSource.getDatasource().stream()
                    .filter(dp -> !"".equals(Optional.ofNullable(dp.getDbName()).orElse("")))
                    .map(dp -> {
                        DruidDataSource druidDataSource = dp.config(druidProperties.config());
                        dbNameMap.put(dp.getDbName(), druidDataSource);
                        return druidDataSource;
                    })
                    .collect(Collectors.toList());

            // 测试所有的数据源
            try {
                mainDataSource.init();
                for (DruidDataSource druidDataSource : multiDataSourceList) {
                    druidDataSource.init();
                }
            } catch (SQLException sql) {
                log.error("=======================    多数据源配置错误   ==========================");
                sql.printStackTrace();
            }
        }
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setTargetDataSources(dbNameMap);
        dynamicDataSource.setDefaultTargetDataSource(mainDataSource);
        return dynamicDataSource;
    }

}

DataSource 的 router

/**
 * <p>多数据源配置</p>
 * <p>动态数据源</p>
 * <p>多 datasource 的上下文</p>
 *
 * @author xiongneng
 * @since 2017年3月5日 上午9:11:49
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    /**
     * <p>多 datasource 的上下文</p>
     * <p>每个线程独立的数据库连接名称</p>
     */
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

    /**
     * @param dataSourceDbName 数据库别名
     * @Description: 设置数据源别名
     */
    public static void setDataSourceDbName(String dataSourceDbName) {
        contextHolder.set(dataSourceDbName);
    }

    /**
     * @Description: 获取数据源别名
     */
    public static String getDataSourceDbName() {
        return contextHolder.get();
    }

    /**
     * @Description: 清除数据源别名
     */
    public static void clearDataSourceDbName() {
        contextHolder.remove();
    }

    /**
     * 重写获取连接名称的方法
     * @return 连接名称
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return getDataSourceDbName();
    }

}

AOP配置

切点是自定义注解的包路径

/**
 * <p>多数据源切换的 aop</p>
 *
 * @author maxzhao
 * @date 2019-06-26 16:22
 */
@Aspect
@Component
@ConditionalOnProperty(prefix = "gt.maxzhao.boot", name = "multiDatasourceOpen", havingValue = "true")
public class MultiDataSourceAop implements Ordered {
    private Logger log = LoggerFactory.getLogger(this.getClass());

    public MultiDataSourceAop() {
        log.info("多数据源初始化 AOP ");
    }

    @Pointcut(value = "@annotation(gt.maxzhao.boot.common.annotation.MultiDataSource)")
    private void cut() {
    }

    @Around("cut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {

        Signature signature = point.getSignature();
        MethodSignature methodSignature ;
        if (!(signature instanceof MethodSignature)) {
            throw new IllegalArgumentException("该注解只能用于方法");
        }
        methodSignature = (MethodSignature) signature;
        //获取当点方法的注解
        Object target = point.getTarget();
        Method currentMethod = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());

        MultiDataSource datasource = currentMethod.getAnnotation(MultiDataSource.class);
        if (datasource != null) {
            DynamicDataSource.setDataSourceDbName(datasource.name());
            log.debug("设置数据源为:" + datasource.name());
        } else {
            DynamicDataSource.setDataSourceDbName("main");
            log.debug("设置数据源为:默认  -->  main");
        }
        try {
            return point.proceed();
        } finally {
            log.debug("清空数据源信息!");
            DynamicDataSource.clearDataSourceDbName();
        }
    }

    /**
     * aop的顺序要早于spring的事务
     */
    @Override
    public int getOrder() {
        return 1;
    }
}

到这里构建结束

测试

model

@Accessors(chain = true)
@Data
@Entity
@Table(name = "temp", schema = "", catalog = "")
public class Temp implements Serializable {
    private static final long serialVersionUID = -1L;

    @Id
    @Column(name = "ID",unique = true)
    @ApiModelProperty(value = "主键")
    private Long id;
    @Basic
    @Column(name = "NAME")
    @ApiModelProperty(value = "地区名称")
    private String name;
}

service

@Service
@Transactional
public class TempServiceDemo {

    @Autowired
    private TempRepository tempRepository;


    public List<Temp> findAll() {
        return tempRepository.findAll();
    }

    @MultiDataSource(name = "second")
    public List<Temp> findAllSecond() {
        return tempRepository.findAll();
    }

    @MultiDataSource(name = "third")
    public List<Temp> findAllThird() {
        return tempRepository.findAll();
    }
}

dao

@Repository("tempRepository")
public interface TempRepository extends JpaRepository<Temp, Long> {
}

Test

@RunWith(SpringRunner.class )
// 这里的 BasicApplication 是当前SpringBoot的启动类
@SpringBootTest(classes = BasicApplication.class)
@Slf4j
public class MultiDataSourceTest {
    @Resource
    private TempServiceDemo tempServiceDemo;

    @Autowired
    private MultiDataSource multiDataSource;

    @Test
    public void testMultiDataSource() {
        System.out.println("\r\n=================\r\n");
        System.out.println(tempServiceDemo.findAllSecond());
        System.out.println("\r\n=================\r\n");
        System.out.println( tempServiceDemo.findAllThird());
        System.out.println("\r\n=================\r\n");
    }
}

本文地址:
SpringBoot+JPA多数据源(注解方式)

推荐
IDEA好用的插件
JAVA自定义注解

上一篇下一篇

猜你喜欢

热点阅读