乐趣区

关于java:海星框架引入DB模块多数据源

1. 设置数据库属性

包含:数据库名称、用户名、明码
在 business 下创立 dto 文件夹,自定义 SegmentInfo.java,继承 AmqServer

package com.hikvision.isc.module.business.dto;

import com.hikvision.starfish.discovery.AmqServer;
import lombok.Data;

@Data
public class SegmentInfo extends AmqServer {
    
    // 数据库名称
    private String dbname;
    // 用户名
    private String dbusername;
    // 明码
    private String dbpassword;

}

2. 配置文件中配置数据库信息

以本地数据库和 ibuilding 数据库为例,在配置文件中配置两个数据库的信息

# 多数据库切换——数据库配置
#本地数据库信息
localDburl=jdbc:postgresql://10.196.1.45:7092/sscvhb_sscvhbdb
localUserName=sscvhb_sscvhbdb_user
localingPwd=StgR23C3
#ibuilding 数据库信息
ibuildDburl=jdbc:postgresql://10.196.1.45:7092/ibuilding_ibuildingdb
ibuildingUserName=ibuilding_ibuildingdb_user
ibuildingPwd=I65BRJSB

3. 引入 db 模块

在 business 下引入 db 模块,该模块蕴含五个文件

3.1 DataSourceSwitchAspect

切换数据源


package com.hikvision.isc.module.business.db;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;


/**
 * @Description:
 * @Author: wangyongqiang13
 * @Date: 2020/12/22 9:15
 */

@Component
@Aspect
@Order(-1) // 这是为了保障 AOP 在事务注解之前失效,Order 的值越小, 优先级越高
public class DataSourceSwitchAspect {private final static Logger log = LoggerFactory.getLogger(DataSourceSwitchAspect.class);
    
    // 须要从本地数据库获取的数据,须要在 mapper 文件夹下创立 local 文件夹,并将 mapper 文件定义在 local 文件夹下
    @Pointcut("execution(* com.hikvision.isc.sscvhb.module.business.mapper.local..*.*(..))")
    private void db1Aspect() {}

    // 须要从 ibuilding 数据库获取的数据,须要在 mapper 文件夹下创立 ibuilding 文件夹,并将 mapper 文件定义在 local 文件夹下
    @Pointcut("execution(* com.hikvision.isc.sscvhb.module.business.mapper.ibuilding..*.*(..))")
    private void db2Aspect() {}


    @Before("db1Aspect()")
    public void db1() {log.debug("切换到 local 数据源...");
        DbContextHolder.setDbType(DBTypeEnum.db1);
    }

    @Before("db2Aspect()")
    public void db2() {log.debug("切换到 ibuilding 数据源...");
        DbContextHolder.setDbType(DBTypeEnum.db2);
    }


}


同样,实体类也要有雷同的操作,例如:

3.2 DbConfig

用来获取配置文件中的数据库信息


package com.hikvision.isc.module.business.db;

import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.hikvision.isc.module.business.service.impl.MyHikDiscoveryClientImpl;
import com.hikvision.starfish.discovery.client.impl.HikDiscoveryClientImpl;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
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;


/**
 * mybatis-plus 扩大配置
 *
 * @author wangqingxun
 * @Date: Created in 2020/12/26
 * @since jdk1.8
 */

@Configuration
@MapperScan("com.hikvision.isc.sscvhb.**.mapper*")
public class DbConfig {private final static Logger log = LoggerFactory.getLogger(DbConfig.class);
    
    // 组件示意信息:也是在配置文件中进行配置
    @Value("${base.application.componentId}")
    private String componentId;
    @Value("${base.application.dbSegmentId}")
    private String dbSegmentId;

    // 配置文件中数据库的信息
    @Value("${localDburl}")
    private String localDburl;
    @Value("${localUserName}")
    private String localUserName;
    @Value("${localingPwd}")
    private String localingPwd;
    @Value("${ibuildDburl}")
    private String ibuildDburl;
    @Value("${ibuildingUserName}")
    private String ibuildingUserName;
    @Value("${ibuildingPwd}")
    private String ibuildingPwd;

    @Autowired
    private MyHikDiscoveryClientImpl myHikDiscoveryClient;

    @Autowired
    private HikDiscoveryClientImpl hikDiscoveryClient;

    private static String driverClass = "org.postgresql.Driver";

    @Bean
    public PaginationInterceptor paginationInterceptor() {return new PaginationInterceptor();
    }


    @Bean(name = "db1")
    public DataSource db1() {// SegmentInfo segmentInfo = myHikDiscoveryClient.findAmqServer(componentId,dbSegmentId);
    /*    log.info("初始化新组件数据库 >>>>>>componentId="+componentId+",dbSegmentId="+dbSegmentId+",segmentInfo="+ JSONObject.toJSONString(segmentInfo));
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClass);
        dataSource.setUrl("jdbc:postgresql://"+segmentInfo.getIp()+":"+segmentInfo.getPort()+"/"+segmentInfo.getDbName());
        dataSource.setUsername(segmentInfo.getDbusername());
        dataSource.setPassword(segmentInfo.getDbpassword());*/

        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClass);
        dataSource.setUrl(localDburl);
        dataSource.setUsername(localUserName);
        dataSource.setPassword(localingPwd);

        return dataSource;


    }

    @Bean(name = "db2")
    public DataSource db2() {DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClass);
        dataSource.setUrl(ibuildDburl);
        dataSource.setUsername(ibuildingUserName);
        dataSource.setPassword(ibuildingPwd);
        return dataSource;
    }

    /**
     * 动静数据源配置
     *
     * @return
     */

    @Bean
    @Primary
    public DataSource multipleDataSource(@Qualifier("db1") DataSource db1,
                                         @Qualifier("db2") DataSource db2
    ) {DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DBTypeEnum.db1.getValue(), db1);
        targetDataSources.put(DBTypeEnum.db2.getValue(), db2);
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(db2); // 程序默认数据源,这个要依据程序调用数据源频次,常常把常调用的数据源作为默认
        return dynamicDataSource;
    }

    @Bean("sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory() throws Exception {MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(multipleDataSource(db1(), db2()));

        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setJdbcTypeForNull(JdbcType.NULL);
        configuration.setMapUnderscoreToCamelCase(true);
        configuration.setCacheEnabled(false);
        sqlSessionFactory.setConfiguration(configuration);
        sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*Mapper.xml"));
        //PerformanceInterceptor(),OptimisticLockerInterceptor()
        // 增加分页性能
        sqlSessionFactory.setPlugins(new Interceptor[]{paginationInterceptor()
        });
//        sqlSessionFactory.setGlobalConfig(globalConfiguration()); // 正文掉全局配置,因为在 xml 中读取就是全局配置
        return sqlSessionFactory.getObject();}


}

3.3 DbContextHolder


package com.hikvision.isc.module.business.db;

public class DbContextHolder {private static final ThreadLocal contextHolder = new ThreadLocal<>();

    /**
     * 设置数据源
     *
     * @param dbTypeEnum
     */

    public static void setDbType(DBTypeEnum dbTypeEnum) {contextHolder.set(dbTypeEnum.getValue());
    }


    /**
     * 获得以后数据源
     *
     * @return
     */

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


    /**
     * 革除上下文数据
     */

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

3.4 DBTypeEnum


package com.hikvision.isc.module.business.db;

public enum DBTypeEnum {db1("db1"),
    db2("db2");
    private String value;

    DBTypeEnum(String value) {this.value = value;}

    public String getValue() {return value;}
}

3.5 DynamicDataSource

获取以后应用哪个数据源


package com.hikvision.isc.module.business.db;


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


public class DynamicDataSource extends AbstractRoutingDataSource {
    /**
     * 获得以后应用哪个数据源
     *
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {return DbContextHolder.getDbType();
    }
}

4. 主动寻址

在 service.impl 下创立 MyHikDiscoveryClientImpl.java 文件夹,继承 HikDiscoveryClientImpl(在 startfish-discovery-1.8.9.RELEASE.jar 包中)

package com.hikvision.starfish.discovery.client.impl;

import com.hikvision.discovery.exception.ServerNotFoundException;
import com.hikvision.notify.dto.servicechange.ServiceChangeMsgDto;
import com.hikvision.starfish.bic.bo.AmqInfo;
import com.hikvision.starfish.bic.client.ServiceDirectoryClient;
import com.hikvision.starfish.bic.constant.BicConstants;
import com.hikvision.starfish.bic.dto.response.ServiceAddressInfoDto;
import com.hikvision.starfish.bic.dto.response.ServiceInfoDto;
import com.hikvision.starfish.core.response.api.ApiResponse;
import com.hikvision.starfish.discovery.AmqServer;
import com.hikvision.starfish.discovery.Server;
import com.hikvision.starfish.discovery.client.HikDiscoveryClient;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.core.env.Environment;
import org.springframework.web.client.HttpServerErrorException;

import java.util.Optional;

/**
 * @author dengyishi
 * @date 2019/5/25
 * @since 1.1.0
 */
public class HikDiscoveryClientImpl implements HikDiscoveryClient {
    private static final String SUCCESS = "0";
    private ServiceDirectoryClient serviceDirectoryClient;
    private CacheManager cacheManager;
    Server bicServer;
    Server casServer;
    Server serviceDirectoryServer;
    Server licenseServer;
    private HikHttpDiscoveryProperties hikHttpDiscoveryProperties;

    public HikDiscoveryClientImpl(ServiceDirectoryClient serviceDirectoryClient, CacheManager cacheManager,
                                  Environment environment, HikHttpDiscoveryProperties hikHttpDiscoveryProperties) {super();
        this.serviceDirectoryClient = serviceDirectoryClient;
        this.cacheManager = cacheManager;
        this.hikHttpDiscoveryProperties = hikHttpDiscoveryProperties;
        String bicPort = environment.getRequiredProperty("@bic.bic.port");
        bicServer = new Server(environment.getRequiredProperty("@bic.bic.ip"), Integer.valueOf(bicPort), environment.getRequiredProperty("@bic.bic.context"));
        String casPort = environment.getRequiredProperty("@bic.cas.port");
        casServer = new Server(environment.getRequiredProperty("@bic.cas.ip"), Integer.valueOf(casPort), environment.getRequiredProperty("@bic.cas.context"));
        String serviceDirectoryPort = environment.getRequiredProperty("@bic.serviceDirectory.port");
        serviceDirectoryServer = new Server(environment.getRequiredProperty("@bic.serviceDirectory.ip"),
                Integer.valueOf(serviceDirectoryPort), environment.getRequiredProperty("@bic.serviceDirectory.context"));
        String licensePort = environment.getRequiredProperty("@bic.license.port");
        licenseServer = new Server(environment.getRequiredProperty("@bic.license.ip"), Integer.valueOf(licensePort), environment.getRequiredProperty("@bic.license.context"));
    }

    public ServiceInfoDto findService(String componentId, String segmentId) {ApiResponse<ServiceInfoDto> result = serviceDirectoryClient.getServiceInfoV2(componentId, segmentId);

        return result.getValidatedData(ServerNotFoundException.class,
                "寻址" + componentId + "." + segmentId + "时服务目录产生谬误");

    }

    @Cacheable(cacheManager = SERVICE_DISCOVERY_CACHE_MANAGER, cacheNames = SERVICE_DISCOVERY_CACHE, key = "#p0+'.'+#p1", unless = "#result==null")
    @Override
    public AmqServer findAmqServer(String componentId, String mqSegmentId) {AmqServer amqServer = new AmqServer();

        ServiceInfoDto serviceInfoDto = findService(componentId, mqSegmentId);
        if (serviceInfoDto != null && serviceInfoDto.getAmqInfo() != null) {AmqInfo amqInfo = serviceInfoDto.getAmqInfo();
            amqServer.setIp(amqInfo.getIp());
            amqServer.setUsername(amqInfo.getUsername());
            amqServer.setPassword(amqInfo.getPassword());
            amqServer.setSslPort(amqInfo.getSslPort());
            amqServer.setPort(amqInfo.getPort());
        } else {throw new ServerNotFoundException("寻址" + componentId + "." + mqSegmentId + "失败,无可用服务信息。请查看寻址参数是否正确");

        }

        return amqServer;
    }

    @Cacheable(cacheManager = SERVICE_DISCOVERY_CACHE_MANAGER, cacheNames = SERVICE_DISCOVERY_CACHE, key = "#p0+'.'+#p1", unless = "#result==null")
    @Override
    public Server findHttpServer(String componentId, String segmentId) {Server serverInProperties = localPropertiesDiscovery(componentId, segmentId);
        if (serverInProperties != null) {return serverInProperties;}
        Optional<HikHttpDiscoveryProperties.Custom> customWrapper = getCustom(componentId, segmentId);

        if (customDiscoveryResult(customWrapper)) {return new Server(customWrapper.get().getHost(), customWrapper.get().getPort(), customWrapper.get().getContextPath());
        }
        ApiResponse<ServiceInfoDto> result;
        String portName = getHttpPortName(customWrapper);

        try {result = serviceDirectoryClient.getServiceInfo(componentId, segmentId);
        } catch (HttpServerErrorException e) {
            throw new ServerNotFoundException("寻址" + componentId + "." + segmentId + "时服务目录产生外部谬误,谬误音讯:"
                    + e.getMessage() + ",HTTP BODY 为" + e.getResponseBodyAsString());
        }
        ServiceInfoDto serviceInfoDto = result.getValidatedData(ServerNotFoundException.class,
                "寻址" + componentId + "." + segmentId + "时服务目录产生谬误");
        if (serviceInfoDto == null) {throw new ServerNotFoundException("未查问到" + componentId + "." + segmentId + "服务地址,请确认组件标识、服务标识是否正确以及相应组件是否已装置");
        }
        Optional<ServiceAddressInfoDto> address = serviceInfoDto.getAddress().stream()
                .filter(addressInfo -> portName.equals(addressInfo.getKey())).findAny();
        if (!address.isPresent()) {throw new ServerNotFoundException("寻址" + componentId + "." + segmentId + "失败,无可用 HTTP 端口地址");
        }
        return new Server(address.get().getIp(), address.get().getPort(), serviceInfoDto.getContext());

    }

    private Optional<HikHttpDiscoveryProperties.Custom> getCustom(String componentId, String segmentId) {return hikHttpDiscoveryProperties.getCustoms().stream().filter(cp -> (componentId + "." + segmentId).equals(cp.getHostname()) || (componentId + "-" + segmentId).equals(cp.getHostname())).findAny();}

    private boolean customDiscoveryResult(Optional<HikHttpDiscoveryProperties.Custom> customWrapper) {if (customWrapper.isPresent()) {HikHttpDiscoveryProperties.Custom custom = customWrapper.get();
            if (StringUtils.isNotBlank(custom.getHost()) && custom.getPort() != null) {return true;} else {return false;}
        } else {return false;}
    }

    private String getHttpPortName(Optional<HikHttpDiscoveryProperties.Custom> custom) {if (custom.isPresent()) {return custom.get().getPortName();} else {return BicConstants.KEY_WEB_PORT;}
    }

    /**
     * 间接从组件配置文件中寻址到外围服务
     *
     * @param componentId
     * @param segmentId
     * @return
     */
    private Server localPropertiesDiscovery(String componentId, String segmentId) {if ("bic".equals(componentId) && componentId.equals(segmentId)) {return bicServer;}
        if ("bic".equals(componentId) && "cas".equals(segmentId)) {return casServer;}
        if ("bic".equals(componentId) && "serviceDirectory".equals(segmentId)) {return serviceDirectoryServer;}
        if ("bic".equals(componentId) && "license".equals(segmentId)) {return licenseServer;}
        return null;
    }

    @Override
    public void receiveMsg(ServiceChangeMsgDto serviceChangeMsgDto) {if (serviceChangeMsgDto.getData() == null) {return;}
        serviceChangeMsgDto.getData().getIds().forEach(serviceChangeDetailDto -> {String componentId = serviceChangeDetailDto.getComponentId();
            String segmentId = serviceChangeDetailDto.getServiceType();
            cacheManager.getCache(SERVICE_DISCOVERY_CACHE).evict(componentId + "." + segmentId);
        });
    }

}

5. 按需更改

有时候可能须要不止两个数据库,比方须要三个、四个等等,这个时候可依据模板批改 db 模块中的相应文件即可

退出移动版