精华内容
下载资源
问答
  • 系统之间的对接,无外乎就是把己方系统的数据发送给第三方或接收第三方系统发送过来的数据(加密签名等机制本文不谈)。作为一个支持动态定义接口的平台,应该有自己的数据库设计,它不应该和业务系统的耦合度太高。...

    一. 需求采集

    公司要求开发一个接口平台,作为我司各系统之间,或我司系统与第三方系统的对接工具。使用微服务架构,通过该接口平台可动态定义并生成restful接口。

    二. 需求分析

    系统之间的对接,无外乎就是把己方系统的数据发送给第三方或接收第三方系统发送过来的数据(加密签名等机制本文不谈)。作为一个支持动态定义接口的平台,应该有自己的数据库设计,它不应该和业务系统的耦合度太高。因此,最终决定方案如下:

    1. 本平台独立运行且有自己独立的数据库,即不能把本平台的表建立在业务系统的库里面;
    2. 可登陆平台创建数据源信息(数据库连接信息),并给每个数据源分配一个唯一的code,支持Oracle、Mysql、SQLServer三种数据库类型;
    3. 数据源创建成功后,可以针对该数据源定义相关资源的增删改查接口,分别对应restful的post、delete、put、get请求;
    4. 接口定义完成后,自动生成接口地址,平台接收到调用者的请求后,从请求URL(接口地址)解析出要操作的数据源code,然后基于Durid创建对应的数据库连接池(由于创建数据库连接池是比较费时的操作,因此仅当该数据源第一次被使用时才创建它的连接池,后期不会重复创建),连接池创建完成后再继续后续的操作。

    三. 程序设计

    为了方便区分,我们把平台自身的数据源称为“主数据源”,动态创建的数据源称为“客数据源”。
    第1步:在application.properties配置主数据源信息

    	   spring.datasource.url=jdbc:oracle:thin:@127.0.0.1:1521:orcl
           spring.datasource.username=adi
           spring.datasource.password=adipassword
           spring.datasource.driverClassName = oracle.jdbc.OracleDriver
           spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
           spring.datasource.initialSize=20
           spring.datasource.minIdle=10
           spring.datasource.maxActive=50
           spring.datasource.maxWait=60000
    

    第2步:创建一个数据源实体类com.bitservice.adi.entity.DataSource(类名可以改)
    该类的关键字段:

          url:数据库地址
          userName:数据库用户名
          passWord:数据库密码
          code:数据源编码,保证唯一
          databasetype:数据库类型,支持oracle、mysql、sqlserver2000、sqlserver
    

    创建一个数据库工具类DBUtil,可以用来在创建数据源的数据库连接池之前判断数据库信息是否正确

    package com.bitservice.adi.util;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    
    import org.apache.log4j.Logger;
    
    public class DBUtil {
    	private final Logger logger = Logger.getLogger(getClass());
    	private DatabaseType databaseType = null;
    	private String username;
    	private String password;
    	private String url;
    	public static final String mysqldriver = "com.mysql.jdbc.Driver"; // mysql数据库的驱动类
    	public static final String oracledriver = "oracle.jdbc.OracleDriver"; // oracles数据库的驱动类
    	public static final String sql2005driver = "com.microsoft.sqlserver.jdbc.SQLServerDriver"; // sqlserver数据库的驱动类
    	public static final String sql2000driver = "net.sourceforge.jtds.jdbc.Driver"; // sqlserver数据库的驱动类
    	
    	public DBUtil(DatabaseType databaseType, String username,
    			String password,String url) {
    		this.databaseType = databaseType;
    		this.username = username;
    		this.password = password;
    		this.url = url;
    		forName();
    	}
    	
    	private void forName() {
    		try {
    			if (null == databaseType) {
    				throw new RuntimeException("没有指定数据库类型");
    			}
    			if (databaseType == DatabaseType.MYSQL) {
    				Class.forName(mysqldriver).newInstance();
    			} else if (databaseType == DatabaseType.ORACLE) {
    				Class.forName(oracledriver).newInstance();
    			} else if (databaseType == DatabaseType.SQLSERVER2000) {
    				Class.forName(sql2000driver).newInstance();
    			} else if(databaseType == DatabaseType.SQLSERVER) {
    				Class.forName(sql2005driver).newInstance();
    			}
    		} catch (Exception e) {
    			logger.error(e.getMessage(),e);
    			throw new RuntimeException("加载数据库驱动失败");
    		}
    	}
    	
    	/**
    	 * 如果程序发生异常,则表明无法连接
    	 * @return
    	 * @throws SQLException
    	 */
    	public Connection testConnection() throws SQLException {
    		Connection conn = DriverManager.getConnection(
    				url, username, password);// 获取连接对象
    		return conn;
    	}
    	
    	public Connection getConnection() {
    		try {
    			Connection conn = DriverManager.getConnection(
    					url, username, password);// 获取连接对象
    			return conn;
    		} catch (SQLException e) {
    			e.printStackTrace();
    			return null;
    		}
    	}
    	
    	public boolean connIsOk(Connection conn) throws SQLException {
    		if (null != conn && !conn.isClosed()) {
    			return true;
    		}
    		return false;
    	}
    	
    	public void closeConn(Connection conn) {
    		try {
    			if (conn != null) {
    				conn.close();
    				conn = null;
    			}
    		} catch (SQLException e) {
    			e.printStackTrace();
    		}
    	}
    	
    	public enum DatabaseType {
    		MYSQL, ORACLE, SQLSERVER2000, SQLSERVER
    	}
    }
    
    

    第3步:创建动态数据源类DynamicDataSource(类名可以改)

    注意:这里的动态数据源不是“客数据源”,动态数据源类有一个“袋子”,用来装具体的数据源对象,也就是说可以装主数据源对象和各个客数据源对象。
    该类必须继承AbstractRoutingDataSource,其中的奥妙请参考
    http://blog.csdn.net/rj042/article/details/21654627 (感谢这位博主!向您学习了不少!)

    代码:

    package com.bitservice.adi.datasource;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.util.Map;
    import java.util.Set;
    
    import javax.sql.DataSource;
    
    import org.apache.log4j.Logger;
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    import com.alibaba.druid.pool.DruidDataSource;
    import com.alibaba.druid.stat.DruidDataSourceStatManager;
    import com.bitservice.adi.exception.ADIException;
    import com.bitservice.adi.security.Base64;
    import com.bitservice.adi.security.SecurityTools;
    import com.bitservice.adi.util.ADIPropUtil;
    import com.bitservice.adi.util.DBUtil;
    import com.bitservice.adi.util.NullUtil;
    
    public class DynamicDataSource extends AbstractRoutingDataSource {
        private boolean debug = false;
        private final Logger log = Logger.getLogger(getClass());
        private Map<Object, Object> dynamicTargetDataSources;
        private Object dynamicDefaultTargetDataSource;
        @Override
        protected Object determineCurrentLookupKey() {
            String datasource = DBContextHolder.getDataSource();
            if (debug) {
                if (NullUtil.IsAllNotNullOfString(datasource)) {
                    Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
                    if (dynamicTargetDataSources2.containsKey(datasource)) {
                        log.info("---当前数据源:" + datasource + "---");
                    } else {
                        throw new ADIException("不存在的数据源:"+datasource,500);
                    }
                } else {
                    log.info("---当前数据源:默认数据源---");
                }
            }
            return datasource;
        }
    
        @Override
        public void setTargetDataSources(Map<Object, Object> targetDataSources) {
            super.setTargetDataSources(targetDataSources);
            this.dynamicTargetDataSources = targetDataSources;
        }
        // 创建数据源
        public boolean createDataSource(String key, String driveClass, String url, String username, String password, String databasetype) {
            try {
                try { // 排除连接不上的错误
                    Class.forName(driveClass);
                    DriverManager.getConnection(url, username, password);// 相当于连接数据库
                } catch (Exception e) {
                    return false;
                }
                @SuppressWarnings("resource")
                DruidDataSource druidDataSource = new DruidDataSource();
                
                druidDataSource.setName(key);
                druidDataSource.setDriverClassName(driveClass);
                druidDataSource.setUrl(url);
                druidDataSource.setUsername(username);
                druidDataSource.setPassword(password);
                druidDataSource.setInitialSize(50); //初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
                druidDataSource.setMaxActive(200); //最大连接池数量
                druidDataSource.setMaxWait(60000); //获取连接时最大等待时间,单位毫秒。当链接数已经达到了最大链接数的时候,应用如果还要获取链接就会出现等待的现象,等待链接释放并回到链接池,如果等待的时间过长就应该踢掉这个等待,不然应用很可能出现雪崩现象
                druidDataSource.setMinIdle(40); //最小连接池数量
                String validationQuery = "select 1 from dual";
                if("mysql".equalsIgnoreCase(databasetype)) {
                    driveClass = DBUtil.mysqldriver;
                    validationQuery = "select 1";
                } else if("oracle".equalsIgnoreCase(databasetype)){
                    driveClass = DBUtil.oracledriver;
                    druidDataSource.setPoolPreparedStatements(true); //是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
                    druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(50);
                    int sqlQueryTimeout = ADIPropUtil.sqlQueryTimeOut();
                    druidDataSource.setConnectionProperties("oracle.net.CONNECT_TIMEOUT=6000;oracle.jdbc.ReadTimeout="+sqlQueryTimeout);//对于耗时长的查询sql,会受限于ReadTimeout的控制,单位毫秒
                } else if("sqlserver2000".equalsIgnoreCase(databasetype)){
                    driveClass = DBUtil.sql2000driver;
                    validationQuery = "select 1";
                } else if("sqlserver".equalsIgnoreCase(databasetype)){
                    driveClass = DBUtil.sql2005driver;
                    validationQuery = "select 1";
                }
                
                druidDataSource.setTestOnBorrow(true); //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用
                druidDataSource.setTestWhileIdle(true);//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
                druidDataSource.setValidationQuery(validationQuery); //用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
                druidDataSource.setFilters("stat");//属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
                druidDataSource.setTimeBetweenEvictionRunsMillis(60000); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
                druidDataSource.setMinEvictableIdleTimeMillis(180000); //配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000
                druidDataSource.setKeepAlive(true); //打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断
                
                druidDataSource.setRemoveAbandoned(true); //是否移除泄露的连接/超过时间限制是否回收。
                druidDataSource.setRemoveAbandonedTimeout(3600); //泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为1小时
                druidDataSource.setLogAbandoned(true); 移除泄露连接发生是是否记录日志
                
                DataSource createDataSource = (DataSource) druidDataSource;
                druidDataSource.init();
                Map<Object, Object> dynamicTargetDataSources_temp = this.dynamicTargetDataSources;
                dynamicTargetDataSources_temp.put(key, createDataSource);// 加入map
                setTargetDataSources(dynamicTargetDataSources_temp);// 将map赋值给父类的TargetDataSources
                super.afterPropertiesSet();// 将TargetDataSources中的连接信息放入resolvedDataSources管理
                log.info(key+"数据源初始化成功");
                //log.info(key+"数据源的概况:"+druidDataSource.dump());
                return true;
            } catch (Exception e) {
                log.error(e + "");
                return false;
            }
        }
        // 删除数据源
        public boolean delDatasources(String datasourceid) {
            Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
            if (dynamicTargetDataSources2.containsKey(datasourceid)) {
                Set<DruidDataSource> druidDataSourceInstances = DruidDataSourceStatManager.getDruidDataSourceInstances();
                for (DruidDataSource l : druidDataSourceInstances) {
                    if (datasourceid.equals(l.getName())) {
                        dynamicTargetDataSources2.remove(datasourceid);
                        DruidDataSourceStatManager.removeDataSource(l);
                        setTargetDataSources(dynamicTargetDataSources2);// 将map赋值给父类的TargetDataSources
                        super.afterPropertiesSet();// 将TargetDataSources中的连接信息放入resolvedDataSources管理
                        return true;
                    }
                }
                return false;
            } else {
                return false;
            }
        }
    
        // 测试数据源连接是否有效
        public boolean testDatasource(String key, String driveClass, String url, String username, String password) {
            try {
                Class.forName(driveClass);
                DriverManager.getConnection(url, username, password);
                return true;
            } catch (Exception e) {
                return false;
            }
        }
    
        /**
         * Specify the default target DataSource, if any.
         * <p>
         * The mapped value can either be a corresponding
         * {@link javax.sql.DataSource} instance or a data source name String (to be
         * resolved via a {@link #setDataSourceLookup DataSourceLookup}).
         * <p>
         * This DataSource will be used as target if none of the keyed
         * {@link #setTargetDataSources targetDataSources} match the
         * {@link #determineCurrentLookupKey()} current lookup key.
         */
        @Override
        public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
            super.setDefaultTargetDataSource(defaultTargetDataSource);
            this.dynamicDefaultTargetDataSource = defaultTargetDataSource;
        }
    
        /**
         * @param debug
         *            the debug to set
         */
        public void setDebug(boolean debug) {
            this.debug = debug;
        }
    
        /**
         * @return the debug
         */
        public boolean isDebug() {
            return debug;
        }
    
        /**
         * @return the dynamicTargetDataSources
         */
        public Map<Object, Object> getDynamicTargetDataSources() {
            return dynamicTargetDataSources;
        }
    
        /**
         * @param dynamicTargetDataSources
         *            the dynamicTargetDataSources to set
         */
        public void setDynamicTargetDataSources(Map<Object, Object> dynamicTargetDataSources) {
            this.dynamicTargetDataSources = dynamicTargetDataSources;
        }
    
        /**
         * @return the dynamicDefaultTargetDataSource
         */
        public Object getDynamicDefaultTargetDataSource() {
            return dynamicDefaultTargetDataSource;
        }
    
        /**
         * @param dynamicDefaultTargetDataSource
         *            the dynamicDefaultTargetDataSource to set
         */
        public void setDynamicDefaultTargetDataSource(Object dynamicDefaultTargetDataSource) {
            this.dynamicDefaultTargetDataSource = dynamicDefaultTargetDataSource;
        }
    
        public void createDataSourceWithCheck(com.bitservice.adi.entity.DataSource dataSource) throws Exception {
            String datasourceId = dataSource.getDatasourceId();
            log.info("准备创建数据源"+datasourceId);
            Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
            if (dynamicTargetDataSources2.containsKey(datasourceId)) {
                log.info("数据源"+datasourceId+"之前已经创建,准备测试数据源是否正常...");
                //DataSource druidDataSource = (DataSource) dynamicTargetDataSources2.get(datasourceId);
                DruidDataSource druidDataSource = (DruidDataSource) dynamicTargetDataSources2.get(datasourceId);
                boolean rightFlag = true;
                Connection connection = null;
                try {
    //                log.info(datasourceId+"数据源的概况->当前闲置连接数:"+druidDataSource.getPoolingCount());
    //                long activeCount = druidDataSource.getActiveCount();
    //                log.info(datasourceId+"数据源的概况->当前活动连接数:"+activeCount);
    //                if(activeCount > 0) {
    //                    log.info(datasourceId+"数据源的概况->活跃连接堆栈信息:"+druidDataSource.getActiveConnectionStackTrace());
    //                }
                    log.info("准备获取数据库连接...");
                    connection = druidDataSource.getConnection();
                    log.info("数据源"+datasourceId+"正常");
                } catch (Exception e) {
                    log.error(e.getMessage(),e); //把异常信息打印到日志文件
                    rightFlag = false;
                    log.info("缓存数据源"+datasourceId+"已失效,准备删除...");
                    if(delDatasources(datasourceId)) {
                        log.info("缓存数据源删除成功");
                    } else {
                        log.info("缓存数据源删除失败");
                    }
                } finally {
                    if(null != connection) {
                        connection.close();
                    }
                }
                if(rightFlag) {
                    log.info("不需要重新创建数据源");
                    return;
                } else {
                    log.info("准备重新创建数据源...");
                    createDataSource(dataSource);
                    log.info("重新创建数据源完成");
                }
            } else {
                createDataSource(dataSource);
            }
            
        }
        
        private  void createDataSource(com.bitservice.adi.entity.DataSource dataSource) throws Exception {
            String datasourceId = dataSource.getDatasourceId();
            log.info("准备创建数据源"+datasourceId);
            String databasetype = dataSource.getDatabasetype();
            String username = dataSource.getUserName();
            String password = dataSource.getPassWord();
            password = new String(SecurityTools.decrypt(Base64.decode(password)));
            String url = dataSource.getUrl();
            String driveClass = "";
            if("mysql".equalsIgnoreCase(databasetype)) {
                driveClass = DBUtil.mysqldriver;
            } else if("oracle".equalsIgnoreCase(databasetype)){
                driveClass = DBUtil.oracledriver;
            }  else if("sqlserver2000".equalsIgnoreCase(databasetype)){
                driveClass = DBUtil.sql2000driver;
            } else if("sqlserver".equalsIgnoreCase(databasetype)){
                driveClass = DBUtil.sql2005driver;
            }
            if(testDatasource(datasourceId,driveClass,url,username,password)) {
                boolean result = this.createDataSource(datasourceId, driveClass, url, username, password, databasetype);
                if(!result) {
                    throw new ADIException("数据源"+datasourceId+"配置正确,但是创建失败",500);
                }
            } else {
                throw new ADIException("数据源配置有错误",500);
            }
        }
    
    }
    

    第4步:创建数据源配置类DruidDBConfig(类名可以改)
    该类在springboot启动时就会实例化,主要功能是创建主数据源对象和第3步的动态数据源对象。动态数据源对象手上有一个“袋子”,用来装具体的数据源对象,通过代码可以看到,我把主数据源对象也放到了这个“袋子”里面。

    代码:

    package com.bitservice.adi.config;
    
    import java.sql.SQLException;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Properties;
    
    import javax.persistence.EntityManager;
    import javax.sql.DataSource;
    
    import org.apache.log4j.Logger;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
    import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
    import org.springframework.transaction.annotation.EnableTransactionManagement;
    
    import com.alibaba.druid.pool.DruidDataSource;
    import com.bitservice.adi.datasource.DynamicDataSource;
    
    /**
     * DruidDBConfig类被@Configuration标注,用作配置信息; DataSource对象被@Bean声明,为Spring容器所管理,
     *
     * @Primary表示这里定义的DataSource将覆盖其他来源的DataSource。
     *
     * @author aiyo92
     */
    @Configuration
    @EnableTransactionManagement
    public class DruidDBConfig {
    
        private final Logger log = Logger.getLogger(getClass());
        
        // adi数据库连接信息
        @Value("${spring.datasource.url}")
        private String dbUrl;
        @Value("${spring.datasource.username}")
        private String username;
        @Value("${spring.datasource.password}")
        private String password;
        @Value("${spring.datasource.driverClassName}")
        private String driverClassName;
    
        // 连接池连接信息
        @Value("${spring.datasource.initialSize}")
        private int initialSize;
        @Value("${spring.datasource.minIdle}")
        private int minIdle;
        @Value("${spring.datasource.maxActive}")
        private int maxActive;
        @Value("${spring.datasource.maxWait}")
        private int maxWait;
    
        @Bean // 声明其为Bean实例
        @Primary // 在同样的DataSource中,首先使用被标注的DataSource
        @Qualifier("adiDataSource")
        public DataSource dataSource() throws SQLException {
            DruidDataSource datasource = new DruidDataSource();
            // 基础连接信息
            datasource.setUrl(this.dbUrl);
            datasource.setUsername(username);
            datasource.setPassword(password);
            datasource.setDriverClassName(driverClassName);
            // 连接池连接信息
            datasource.setInitialSize(initialSize);
            datasource.setMinIdle(minIdle);
            datasource.setMaxActive(maxActive);
            datasource.setMaxWait(maxWait);
            
            datasource.setPoolPreparedStatements(true); //是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
            datasource.setMaxPoolPreparedStatementPerConnectionSize(50);
            datasource.setConnectionProperties("oracle.net.CONNECT_TIMEOUT=6000;oracle.jdbc.ReadTimeout=60000");//对于耗时长的查询sql,会受限于ReadTimeout的控制,单位毫秒
            datasource.setTestOnBorrow(true); //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用
            datasource.setTestWhileIdle(true);//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
            String validationQuery = "select 1 from dual";
            datasource.setValidationQuery(validationQuery); //用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
            datasource.setFilters("stat,wall");//属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
            datasource.setTimeBetweenEvictionRunsMillis(60000); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
            datasource.setMinEvictableIdleTimeMillis(180000); //配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000
            datasource.setKeepAlive(true); //打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断
            
            datasource.setRemoveAbandoned(true); //是否移除泄露的连接/超过时间限制是否回收。
            datasource.setRemoveAbandonedTimeout(3600); //泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为1小时
            datasource.setLogAbandoned(true); 移除泄露连接发生是是否记录日志
            return datasource;
        }
    
        @Bean(name = "dynamicDataSource")
        @Qualifier("dynamicDataSource")
        public DataSource dynamicDataSource() throws SQLException {
            DynamicDataSource dynamicDataSource = new DynamicDataSource();
            dynamicDataSource.setDebug(false);
            dynamicDataSource.setDefaultTargetDataSource(dataSource());
            Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
            targetDataSources.put("adiDataSource", dataSource());
            dynamicDataSource.setTargetDataSources(targetDataSources);
            return dynamicDataSource;
        }
    
        @Bean(name = "dynamicJdbcTemplate")
        @Qualifier("dynamicJdbcTemplate")
        public NamedParameterJdbcTemplate dynamicJdbcTemplate(@Qualifier("dynamicDataSource") DataSource dataSource) {
            return new NamedParameterJdbcTemplate(dataSource);
        }
        
        @Bean(name = "adiJdbcTemplate")
        @Qualifier("adiJdbcTemplate")
        @Primary
        public NamedParameterJdbcTemplate adiJdbcTemplate(@Qualifier("adiDataSource") DataSource dataSource) {
            return new NamedParameterJdbcTemplate(dataSource);
        }
        
        @Bean(name = "entityManagerFactory")
        @Qualifier("entityManagerFactory")
        @Primary
        public LocalContainerEntityManagerFactoryBean entityManageFactory(EntityManagerFactoryBuilder builder) throws SQLException{
            LocalContainerEntityManagerFactoryBean entityManagerFactory = builder.dataSource(dataSource()).packages("com.bitservice.adi.entity").build();
            Properties jpaProperties = new Properties();
            jpaProperties.put("hibernate.dialect", "org.hibernate.dialect.Oracle10gDialect");
            jpaProperties.put("hibernate.physical_naming_strategy", "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
            jpaProperties.put("hibernate.connection.charSet", "utf-8");
            jpaProperties.put("hibernate.show_sql", "false");
            entityManagerFactory.setJpaProperties(jpaProperties);
            return entityManagerFactory;
        }
    
        @Bean(name = "entityManager")
        @Qualifier("entityManager")
        @Primary
        public EntityManager entityManager(EntityManagerFactoryBuilder builder) throws SQLException{
            return entityManageFactory(builder).getObject().createEntityManager();
        }
        
        
        
        @Bean(name = "dynamicEntityManageFactory")
        @Qualifier("dynamicEntityManageFactory")
        public LocalContainerEntityManagerFactoryBean dynamicEntityManageFactory(EntityManagerFactoryBuilder builder) throws SQLException{
            LocalContainerEntityManagerFactoryBean entityManagerFactory = builder.dataSource(dynamicDataSource()).packages("com.bitservice.dynamic.entity").build();
            Properties jpaProperties = new Properties();
            //jpaProperties.put("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
            jpaProperties.put("hibernate.physical_naming_strategy", "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
            jpaProperties.put("hibernate.connection.charSet", "utf-8");
            jpaProperties.put("hibernate.show_sql", "false");
            entityManagerFactory.setJpaProperties(jpaProperties);
            return entityManagerFactory;
        }
    
        @Bean(name = "dynamicEntityManage")
        @Qualifier("dynamicEntityManage")
        public EntityManager dynamicEntityManage(EntityManagerFactoryBuilder builder) throws SQLException{
            return entityManageFactory(builder).getObject().createEntityManager();
        }
    }
    

    第5步:创建数据源切换类DBContextHolder(类名可以改)

    代码:

    package com.bitservice.adi.datasource;
    
    /**
     * 数据源切换
     *
     * @author aiyo92
     *
     */
    public class DBContextHolder {
        // 对当前线程的操作-线程安全的
        private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    
        // 调用此方法,切换数据源
        public static void setDataSource(String dataSource) {
            contextHolder.set(dataSource);
        }
    
        // 获取数据源
        public static String getDataSource() {
            return contextHolder.get();
        }
    
        // 删除数据源
        public static void clearDataSource() {
            contextHolder.remove();
        }
    }
    

    核心代码写完了,接下来就是怎么用了!

    创建一个DAO类SqlRepository,专门用来操作各个客数据源。

    以实现对客数据源的查询请求为例,假如平台创建了多个数据源,并为每个数据源定义了SQL语句,由于平台底层执行SQL的方法是共用的(都在SqlRepository类里面),那么如何在执行查询方法之前动态切换要查询的客数据源呢?上代码!

    package com.bitservice.adi.dao;
    
    import java.util.List;
    import java.util.Map;
    
    import javax.persistence.EntityManager;
    import javax.persistence.PersistenceContext;
    import javax.transaction.Transactional;
    
    import org.apache.log4j.Logger;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
    import org.springframework.stereotype.Service;
    
    import com.bitservice.adi.datasource.DBContextHolder;
    import com.bitservice.adi.datasource.DynamicDataSource;
    import com.bitservice.adi.entity.DataSource;
    
    @Service
    @Transactional
    public class SqlRepository {
        private final Logger logger = Logger.getLogger(getClass());
    
        @Autowired
        @Qualifier("dynamicJdbcTemplate")
        private NamedParameterJdbcTemplate jdbcTemplate;
        
        @Autowired
        @Qualifier("dynamicDataSource")
        private DynamicDataSource dynamicDataSource;
        
        @PersistenceContext(unitName = "dynamicEntityManageFactory")
        private EntityManager entityManager;
        
        private static boolean dynamicFlag = true;
        
        public List<Map<String, Object>> doSelect(DataSource dataSource, String sql, Map<String, Object> params) throws Exception {
            if(dynamicFlag) {
                dynamicDataSource.createDataSourceWithCheck(dataSource);
                DBContextHolder.setDataSource(dataSource.getDatasourceId());
            }
            //logger.info("执行sql查询doSelect-sql:" + sql);
            logger.info("sql_params:" + params);
            List<Map<String, Object>> resultList = jdbcTemplate.queryForList(sql, params);
            logger.info("查询数据库结果doSelect-result:" + resultList.toString());
            return resultList;
        }
    }
    

    我这里是手动切换的,大家可以使用AOP自动切换。

    最后温馨提示:以上我给出的只是核心代码,大家直接粘贴使用的话肯定会报错(比如检查字符串是否为空的工具类不是官方提供的,大家可以使用官方提供的类),报错的地方大家一看就知道怎么改,所以你们只需要把报错的代码根据实际情况换成自己的代码即可。如果有任何疑问,欢迎给我留言。

    展开全文
  • 公司要求开发一个接口平台,作为我司各系统之间,或我司系统与第三方系统的对接工具。使用微服务架构,通过该接口平台可动态定义并生成restful接口。 二. 思考 系统之间的对接,无外乎就是...

    本文最新地址:https://blog.csdn.net/aiyo92/article/details/101064167 内容更完整,排版更友好,欢迎移步!!!

    一. 背景

            公司要求开发一个接口平台,作为我司各系统之间,或我司系统与第三方系统的对接工具。使用微服务架构,通过该接口平台可动态定义并生成restful接口。

    二. 思考

           系统之间的对接,无外乎就是把己方系统的数据发送给第三方或接收第三方系统发送过来的数据(加密签名等机制本文不谈)。作为一个支持动态定义接口的平台,应该有自己的数据库设计,它不应该和业务系统的耦合度太高。因此,最终决定方案如下:

    1. 本平台独立运行且有自己独立的数据库,即不能把本平台的表建立在业务系统的库里面;
    2. 可登陆平台创建数据源信息(数据库连接信息),并给每个数据源分配一个唯一的code,支持Oracle、Mysql、SQLServer三种数据库类型;
    3. 数据源创建成功后,可以针对该数据源定义增删改查接口,分别对应restful的post、delete、put、get请求;
    4. 接口定义完成后,自动生成接口地址,平台接收到调用者的请求后,从请求URL(接口地址)解析出要操作的数据源code,然后基于Durid创建对应的数据库连接池(由于创建数据库连接池是比较费时的操作,因此仅当该数据源第一次被使用时才创建它的连接池,后期不会重复创建),连接池创建完成后再继续后续的操作。

    三. 实现

            为了方便区分,我们把平台自身的数据源称为“主数据源”,动态创建的数据源称为“客数据源”。

            第1步:在application.properties配置主数据源信息

           spring.datasource.url=jdbc:oracle:thin:@127.0.0.1:1521:orcl
           spring.datasource.username=adi
           spring.datasource.password=adipassword
           spring.datasource.driverClassName = oracle.jdbc.OracleDriver

           spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
           spring.datasource.initialSize=20
           spring.datasource.minIdle=10
           spring.datasource.maxActive=50
           spring.datasource.maxWait=60000

     

          第2步:创建一个数据源实体类DataSource(类名可以改)

          该类的关键字段:

          url:数据库地址

          userName:数据库用户名

          passWord:数据库密码

          code:数据源编码,保证唯一

          databasetype:数据库类型,支持oracle、mysql、sqlserver2000、sqlserver

     

           第3步:创建动态数据源类DynamicDataSource(类名可以改)

          注意:这里的动态数据源不是“客数据源”,动态数据源类有一个“袋子”,用来装具体的数据源对象,也就是说可以装主数据源对象和各个客数据源对象。

         该类必须继承AbstractRoutingDataSource,其中的奥妙请参考

    http://blog.csdn.net/rj042/article/details/21654627    (感谢这位博主!向您学习了不少!)

    代码:

    package com.bitservice.adi.datasource;

    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.util.Map;
    import java.util.Set;

    import javax.sql.DataSource;

    import org.apache.log4j.Logger;
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

    import com.alibaba.druid.pool.DruidDataSource;
    import com.alibaba.druid.stat.DruidDataSourceStatManager;
    import com.bitservice.adi.exception.ADIException;
    import com.bitservice.adi.security.Base64;
    import com.bitservice.adi.security.SecurityTools;
    import com.bitservice.adi.util.ADIPropUtil;
    import com.bitservice.adi.util.DBUtil;
    import com.bitservice.adi.util.NullUtil;

    public class DynamicDataSource extends AbstractRoutingDataSource {
        private boolean debug = false;
        private final Logger log = Logger.getLogger(getClass());
        private Map<Object, Object> dynamicTargetDataSources;
        private Object dynamicDefaultTargetDataSource;
        @Override
        protected Object determineCurrentLookupKey() {
            String datasource = DBContextHolder.getDataSource();
            if (debug) {
                if (NullUtil.IsAllNotNullOfString(datasource)) {
                    Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
                    if (dynamicTargetDataSources2.containsKey(datasource)) {
                        log.info("---当前数据源:" + datasource + "---");
                    } else {
                        throw new ADIException("不存在的数据源:"+datasource,500);
                    }
                } else {
                    log.info("---当前数据源:默认数据源---");
                }
            }
            return datasource;
        }

        @Override
        public void setTargetDataSources(Map<Object, Object> targetDataSources) {
            super.setTargetDataSources(targetDataSources);
            this.dynamicTargetDataSources = targetDataSources;
        }
        // 创建数据源
        public boolean createDataSource(String key, String driveClass, String url, String username, String password, String databasetype) {
            try {
                try { // 排除连接不上的错误
                    Class.forName(driveClass);
                    DriverManager.getConnection(url, username, password);// 相当于连接数据库
                } catch (Exception e) {
                    return false;
                }
                @SuppressWarnings("resource")
                DruidDataSource druidDataSource = new DruidDataSource();
                
                druidDataSource.setName(key);
                druidDataSource.setDriverClassName(driveClass);
                druidDataSource.setUrl(url);
                druidDataSource.setUsername(username);
                druidDataSource.setPassword(password);
                druidDataSource.setInitialSize(50); //初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
                druidDataSource.setMaxActive(200); //最大连接池数量
                druidDataSource.setMaxWait(60000); //获取连接时最大等待时间,单位毫秒。当链接数已经达到了最大链接数的时候,应用如果还要获取链接就会出现等待的现象,等待链接释放并回到链接池,如果等待的时间过长就应该踢掉这个等待,不然应用很可能出现雪崩现象
                druidDataSource.setMinIdle(40); //最小连接池数量
                String validationQuery = "select 1 from dual";
                if("mysql".equalsIgnoreCase(databasetype)) {
                    driveClass = DBUtil.mysqldriver;
                    validationQuery = "select 1";
                } else if("oracle".equalsIgnoreCase(databasetype)){
                    driveClass = DBUtil.oracledriver;
                    druidDataSource.setPoolPreparedStatements(true); //是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
                    druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(50);
                    int sqlQueryTimeout = ADIPropUtil.sqlQueryTimeOut();
                    druidDataSource.setConnectionProperties("oracle.net.CONNECT_TIMEOUT=6000;oracle.jdbc.ReadTimeout="+sqlQueryTimeout);//对于耗时长的查询sql,会受限于ReadTimeout的控制,单位毫秒
                } else if("sqlserver2000".equalsIgnoreCase(databasetype)){
                    driveClass = DBUtil.sql2000driver;
                    validationQuery = "select 1";
                } else if("sqlserver".equalsIgnoreCase(databasetype)){
                    driveClass = DBUtil.sql2005driver;
                    validationQuery = "select 1";
                }
                
                druidDataSource.setTestOnBorrow(true); //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用
                druidDataSource.setTestWhileIdle(true);//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
                druidDataSource.setValidationQuery(validationQuery); //用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
                druidDataSource.setFilters("stat");//属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
                druidDataSource.setTimeBetweenEvictionRunsMillis(60000); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
                druidDataSource.setMinEvictableIdleTimeMillis(180000); //配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000
                druidDataSource.setKeepAlive(true); //打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断
                
                druidDataSource.setRemoveAbandoned(true); //是否移除泄露的连接/超过时间限制是否回收。
                druidDataSource.setRemoveAbandonedTimeout(3600); //泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为1小时
                druidDataSource.setLogAbandoned(true); 移除泄露连接发生是是否记录日志
                
                DataSource createDataSource = (DataSource) druidDataSource;
                druidDataSource.init();
                Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
                dynamicTargetDataSources2.put(key, createDataSource);// 加入map
                setTargetDataSources(dynamicTargetDataSources2);// 将map赋值给父类的TargetDataSources
                super.afterPropertiesSet();// 将TargetDataSources中的连接信息放入resolvedDataSources管理
                log.info(key+"数据源初始化成功");
                //log.info(key+"数据源的概况:"+druidDataSource.dump());
                return true;
            } catch (Exception e) {
                log.error(e + "");
                return false;
            }
        }
        // 删除数据源
        public boolean delDatasources(String datasourceid) {
            Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
            if (dynamicTargetDataSources2.containsKey(datasourceid)) {
                Set<DruidDataSource> druidDataSourceInstances = DruidDataSourceStatManager.getDruidDataSourceInstances();
                for (DruidDataSource l : druidDataSourceInstances) {
                    if (datasourceid.equals(l.getName())) {
                        dynamicTargetDataSources2.remove(datasourceid);
                        DruidDataSourceStatManager.removeDataSource(l);
                        setTargetDataSources(dynamicTargetDataSources2);// 将map赋值给父类的TargetDataSources
                        super.afterPropertiesSet();// 将TargetDataSources中的连接信息放入resolvedDataSources管理
                        return true;
                    }
                }
                return false;
            } else {
                return false;
            }
        }

        // 测试数据源连接是否有效
        public boolean testDatasource(String key, String driveClass, String url, String username, String password) {
            try {
                Class.forName(driveClass);
                DriverManager.getConnection(url, username, password);
                return true;
            } catch (Exception e) {
                return false;
            }
        }

        /**
         * Specify the default target DataSource, if any.
         * <p>
         * The mapped value can either be a corresponding
         * {@link javax.sql.DataSource} instance or a data source name String (to be
         * resolved via a {@link #setDataSourceLookup DataSourceLookup}).
         * <p>
         * This DataSource will be used as target if none of the keyed
         * {@link #setTargetDataSources targetDataSources} match the
         * {@link #determineCurrentLookupKey()} current lookup key.
         */
        @Override
        public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
            super.setDefaultTargetDataSource(defaultTargetDataSource);
            this.dynamicDefaultTargetDataSource = defaultTargetDataSource;
        }

        /**
         * @param debug
         *            the debug to set
         */
        public void setDebug(boolean debug) {
            this.debug = debug;
        }

        /**
         * @return the debug
         */
        public boolean isDebug() {
            return debug;
        }

        /**
         * @return the dynamicTargetDataSources
         */
        public Map<Object, Object> getDynamicTargetDataSources() {
            return dynamicTargetDataSources;
        }

        /**
         * @param dynamicTargetDataSources
         *            the dynamicTargetDataSources to set
         */
        public void setDynamicTargetDataSources(Map<Object, Object> dynamicTargetDataSources) {
            this.dynamicTargetDataSources = dynamicTargetDataSources;
        }

        /**
         * @return the dynamicDefaultTargetDataSource
         */
        public Object getDynamicDefaultTargetDataSource() {
            return dynamicDefaultTargetDataSource;
        }

        /**
         * @param dynamicDefaultTargetDataSource
         *            the dynamicDefaultTargetDataSource to set
         */
        public void setDynamicDefaultTargetDataSource(Object dynamicDefaultTargetDataSource) {
            this.dynamicDefaultTargetDataSource = dynamicDefaultTargetDataSource;
        }

        public void createDataSourceWithCheck(com.bitservice.adi.entity.DataSource dataSource) throws Exception {
            String datasourceId = dataSource.getDatasourceId();
            log.info("准备创建数据源"+datasourceId);
            Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
            if (dynamicTargetDataSources2.containsKey(datasourceId)) {
                log.info("数据源"+datasourceId+"之前已经创建,准备测试数据源是否正常...");
                //DataSource druidDataSource = (DataSource) dynamicTargetDataSources2.get(datasourceId);
                DruidDataSource druidDataSource = (DruidDataSource) dynamicTargetDataSources2.get(datasourceId);
                boolean rightFlag = true;
                Connection connection = null;
                try {
    //                log.info(datasourceId+"数据源的概况->当前闲置连接数:"+druidDataSource.getPoolingCount());
    //                long activeCount = druidDataSource.getActiveCount();
    //                log.info(datasourceId+"数据源的概况->当前活动连接数:"+activeCount);
    //                if(activeCount > 0) {
    //                    log.info(datasourceId+"数据源的概况->活跃连接堆栈信息:"+druidDataSource.getActiveConnectionStackTrace());
    //                }
                    log.info("准备获取数据库连接...");
                    connection = druidDataSource.getConnection();
                    log.info("数据源"+datasourceId+"正常");
                } catch (Exception e) {
                    log.error(e.getMessage(),e); //把异常信息打印到日志文件
                    rightFlag = false;
                    log.info("缓存数据源"+datasourceId+"已失效,准备删除...");
                    if(delDatasources(datasourceId)) {
                        log.info("缓存数据源删除成功");
                    } else {
                        log.info("缓存数据源删除失败");
                    }
                } finally {
                    if(null != connection) {
                        connection.close();
                    }
                }
                if(rightFlag) {
                    log.info("不需要重新创建数据源");
                    return;
                } else {
                    log.info("准备重新创建数据源...");
                    createDataSource(dataSource);
                    log.info("重新创建数据源完成");
                }
            } else {
                createDataSource(dataSource);
            }
            
        }
        
        private  void createDataSource(com.bitservice.adi.entity.DataSource dataSource) throws Exception {
            String datasourceId = dataSource.getDatasourceId();
            log.info("准备创建数据源"+datasourceId);
            String databasetype = dataSource.getDatabasetype();
            String username = dataSource.getUserName();
            String password = dataSource.getPassWord();
            password = new String(SecurityTools.decrypt(Base64.decode(password)));
            String url = dataSource.getUrl();
            String driveClass = "";
            if("mysql".equalsIgnoreCase(databasetype)) {
                driveClass = DBUtil.mysqldriver;
            } else if("oracle".equalsIgnoreCase(databasetype)){
                driveClass = DBUtil.oracledriver;
            }  else if("sqlserver2000".equalsIgnoreCase(databasetype)){
                driveClass = DBUtil.sql2000driver;
            } else if("sqlserver".equalsIgnoreCase(databasetype)){
                driveClass = DBUtil.sql2005driver;
            }
            if(testDatasource(datasourceId,driveClass,url,username,password)) {
                boolean result = this.createDataSource(datasourceId, driveClass, url, username, password, databasetype);
                if(!result) {
                    throw new ADIException("数据源"+datasourceId+"配置正确,但是创建失败",500);
                }
            } else {
                throw new ADIException("数据源配置有错误",500);
            }
        }

    }

     

           第4步:创建数据源配置类DruidDBConfig(类名可以改)

           该类在springboot启动时就会实例化,主要功能是创建主数据源对象和第3步的动态数据源对象。动态数据源对象手上有一个“袋子”,用来装具体的数据源对象,通过代码可以看到,我把主数据源对象也放到了这个“袋子”里面。

    代码:

    package com.bitservice.adi.config;

    import java.sql.SQLException;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Properties;

    import javax.persistence.EntityManager;
    import javax.sql.DataSource;

    import org.apache.log4j.Logger;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
    import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
    import org.springframework.transaction.annotation.EnableTransactionManagement;

    import com.alibaba.druid.pool.DruidDataSource;
    import com.bitservice.adi.datasource.DynamicDataSource;

    /**
     * DruidDBConfig类被@Configuration标注,用作配置信息; DataSource对象被@Bean声明,为Spring容器所管理,
     *
     * @Primary表示这里定义的DataSource将覆盖其他来源的DataSource。
     *
     * @author aiyo92
     */
    @Configuration
    @EnableTransactionManagement
    public class DruidDBConfig {

        private final Logger log = Logger.getLogger(getClass());
        
        // adi数据库连接信息
        @Value("${spring.datasource.url}")
        private String dbUrl;
        @Value("${spring.datasource.username}")
        private String username;
        @Value("${spring.datasource.password}")
        private String password;
        @Value("${spring.datasource.driverClassName}")
        private String driverClassName;

        // 连接池连接信息
        @Value("${spring.datasource.initialSize}")
        private int initialSize;
        @Value("${spring.datasource.minIdle}")
        private int minIdle;
        @Value("${spring.datasource.maxActive}")
        private int maxActive;
        @Value("${spring.datasource.maxWait}")
        private int maxWait;

        @Bean // 声明其为Bean实例
        @Primary // 在同样的DataSource中,首先使用被标注的DataSource
        @Qualifier("adiDataSource")
        public DataSource dataSource() throws SQLException {
            DruidDataSource datasource = new DruidDataSource();
            // 基础连接信息
            datasource.setUrl(this.dbUrl);
            datasource.setUsername(username);
            datasource.setPassword(password);
            datasource.setDriverClassName(driverClassName);
            // 连接池连接信息
            datasource.setInitialSize(initialSize);
            datasource.setMinIdle(minIdle);
            datasource.setMaxActive(maxActive);
            datasource.setMaxWait(maxWait);
            
            datasource.setPoolPreparedStatements(true); //是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
            datasource.setMaxPoolPreparedStatementPerConnectionSize(50);
            datasource.setConnectionProperties("oracle.net.CONNECT_TIMEOUT=6000;oracle.jdbc.ReadTimeout=60000");//对于耗时长的查询sql,会受限于ReadTimeout的控制,单位毫秒
            datasource.setTestOnBorrow(true); //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用
            datasource.setTestWhileIdle(true);//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
            String validationQuery = "select 1 from dual";
            datasource.setValidationQuery(validationQuery); //用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
            datasource.setFilters("stat,wall");//属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
            datasource.setTimeBetweenEvictionRunsMillis(60000); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
            datasource.setMinEvictableIdleTimeMillis(180000); //配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000
            datasource.setKeepAlive(true); //打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断
            
            datasource.setRemoveAbandoned(true); //是否移除泄露的连接/超过时间限制是否回收。
            datasource.setRemoveAbandonedTimeout(3600); //泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为1小时
            datasource.setLogAbandoned(true); 移除泄露连接发生是是否记录日志
            return datasource;
        }

        @Bean(name = "dynamicDataSource")
        @Qualifier("dynamicDataSource")
        public DataSource dynamicDataSource() throws SQLException {
            DynamicDataSource dynamicDataSource = new DynamicDataSource();
            dynamicDataSource.setDebug(false);
            dynamicDataSource.setDefaultTargetDataSource(dataSource());
            Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
            targetDataSources.put("adiDataSource", dataSource());
            dynamicDataSource.setTargetDataSources(targetDataSources);
            return dynamicDataSource;
        }

        @Bean(name = "dynamicJdbcTemplate")
        @Qualifier("dynamicJdbcTemplate")
        public NamedParameterJdbcTemplate dynamicJdbcTemplate(@Qualifier("dynamicDataSource") DataSource dataSource) {
            return new NamedParameterJdbcTemplate(dataSource);
        }
        
        @Bean(name = "adiJdbcTemplate")
        @Qualifier("adiJdbcTemplate")
        @Primary
        public NamedParameterJdbcTemplate adiJdbcTemplate(@Qualifier("adiDataSource") DataSource dataSource) {
            return new NamedParameterJdbcTemplate(dataSource);
        }
        
        @Bean(name = "entityManagerFactory")
        @Qualifier("entityManagerFactory")
        @Primary
        public LocalContainerEntityManagerFactoryBean entityManageFactory(EntityManagerFactoryBuilder builder) throws SQLException{
            LocalContainerEntityManagerFactoryBean entityManagerFactory = builder.dataSource(dataSource()).packages("com.bitservice.adi.entity").build();
            Properties jpaProperties = new Properties();
            jpaProperties.put("hibernate.dialect", "org.hibernate.dialect.Oracle10gDialect");
            jpaProperties.put("hibernate.physical_naming_strategy", "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
            jpaProperties.put("hibernate.connection.charSet", "utf-8");
            jpaProperties.put("hibernate.show_sql", "false");
            entityManagerFactory.setJpaProperties(jpaProperties);
            return entityManagerFactory;
        }

        @Bean(name = "entityManager")
        @Qualifier("entityManager")
        @Primary
        public EntityManager entityManager(EntityManagerFactoryBuilder builder) throws SQLException{
            return entityManageFactory(builder).getObject().createEntityManager();
        }
        
        
        
        @Bean(name = "dynamicEntityManageFactory")
        @Qualifier("dynamicEntityManageFactory")
        public LocalContainerEntityManagerFactoryBean dynamicEntityManageFactory(EntityManagerFactoryBuilder builder) throws SQLException{
            LocalContainerEntityManagerFactoryBean entityManagerFactory = builder.dataSource(dynamicDataSource()).packages("com.bitservice.dynamic.entity").build();
            Properties jpaProperties = new Properties();
            //jpaProperties.put("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
            jpaProperties.put("hibernate.physical_naming_strategy", "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
            jpaProperties.put("hibernate.connection.charSet", "utf-8");
            jpaProperties.put("hibernate.show_sql", "false");
            entityManagerFactory.setJpaProperties(jpaProperties);
            return entityManagerFactory;
        }

        @Bean(name = "dynamicEntityManage")
        @Qualifier("dynamicEntityManage")
        public EntityManager dynamicEntityManage(EntityManagerFactoryBuilder builder) throws SQLException{
            return entityManageFactory(builder).getObject().createEntityManager();
        }
    }

    第5步:创建数据源切换类DBContextHolder(类名可以改)

    代码:

    package com.bitservice.adi.datasource;

    /**
     * 数据源切换
     *
     * @author aiyo92
     *
     */
    public class DBContextHolder {
        // 对当前线程的操作-线程安全的
        private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

        // 调用此方法,切换数据源
        public static void setDataSource(String dataSource) {
            contextHolder.set(dataSource);
        }

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

        // 删除数据源
        public static void clearDataSource() {
            contextHolder.remove();
        }
    }

    核心代码写完了,接下来就是怎么用了!

    创建一个DAO类SqlRepository,专门用来操作各个客数据源。

    以实现对客数据源的查询请求为例,假如平台创建了多个数据源,并为每个数据源定义了SQL语句,由于平台底层执行SQL的方法是共用的(都在SqlRepository类里面),那么如何在执行查询方法之前动态切换要查询的客数据源呢?上代码!

    package com.bitservice.adi.dao;

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

    import javax.persistence.EntityManager;
    import javax.persistence.PersistenceContext;
    import javax.transaction.Transactional;

    import org.apache.log4j.Logger;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
    import org.springframework.stereotype.Service;

    import com.bitservice.adi.datasource.DBContextHolder;
    import com.bitservice.adi.datasource.DynamicDataSource;
    import com.bitservice.adi.entity.DataSource;

    @Service
    @Transactional
    public class SqlRepository {
        private final Logger logger = Logger.getLogger(getClass());

        @Autowired
        @Qualifier("dynamicJdbcTemplate")
        private NamedParameterJdbcTemplate jdbcTemplate;
        
        @Autowired
        @Qualifier("dynamicDataSource")
        private DynamicDataSource dynamicDataSource;
        
        @PersistenceContext(unitName = "dynamicEntityManageFactory")
        private EntityManager entityManager;
        
        private static boolean dynamicFlag = true;
        
        public List<Map<String, Object>> doSelect(DataSource dataSource, String sql, Map<String, Object> params) throws Exception {
            if(dynamicFlag) {
                dynamicDataSource.createDataSourceWithCheck(dataSource);
                DBContextHolder.setDataSource(dataSource.getDatasourceId());
            }
            //logger.info("执行sql查询doSelect-sql:" + sql);
            logger.info("sql_params:" + params);
            List<Map<String, Object>> resultList = jdbcTemplate.queryForList(sql, params);
            logger.info("查询数据库结果doSelect-result:" + resultList.toString());
            return resultList;
        }
    }

     

    我这里是手动切换的,大家可以使用AOP自动切换。

    最后温馨提示:以上我给出的只是核心代码,大家直接粘贴使用的话肯定会报错,报错的地方大家一看就知道怎么改,所以你们只需要把报错的代码根据实际情况换成自己的代码即可。如果有任何疑问,咱们可以随时交流!

     

     

        

     

    展开全文
  • 详情请看:DataX 数据源指南 四、DataX3.0 核心架构 DataX 3.0 开源版本支持单机多线程模式完成同步作业运行,本小节按一个 DataX 作业生命周期的时序图,从整体架构设计非常简要说明 DataX 各个模块相互关系。...
  • 之后各个数据源不要再独自去声明事务控制对象了,因为这时候Atomikos会自动有一个统一的分布式事务控制对象来控制事务; 代码: application.yml 数据源配置方式: spring: datasource01: mapperPackage...

    利用spring boot集成JTA(Atomikos案例)实现分布式事务控制;

    maven引入:

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

    原理简述:

    将spring原有的数据源信息管理的类型改为atomikos实现的AtomikosDataSourceBean数据源类(他也是javax.sql.DataSource的实现类),该类有一个属性是xaDataSource(xa就是分布式事务处理的一种模型规范),实现一下xaDataSource赋给这个属性;

    之后各个数据源不要再独自去声明事务控制对象了,因为这时候Atomikos会自动有一个统一的分布式事务控制对象来控制事务;

    代码:

    application.yml 数据源配置方式:

    spring:
      datasource01:
        mapperPackage: com.alicyu.springcloud.dao.dbone         #mapper包路径
        mapperxmlDir: classpath:mybatis/mapper/dbone/**/*.xml      #mapper.xml路径
        entityPackage: com.alicyu.springcloud.entities.dbone    #实体包路径
        mybatiscfg: classpath:mybatis/mybatis.cfg.xml      #mapper对应mybatis通用配置文件路径
        url: jdbc:mysql://localhost:3306/clouddb01?useSSL=false
        username: root
        password: zhicheng
        minPoolSize: 3
        maxPoolSize: 25
        maxLifetime: 20000
        borrowConnectionTimeout: 30
        loginTimeout: 30
        maintenanceInterval: 60
        maxIdleTime: 60
      datasource02:
        mapperPackage: com.alicyu.springcloud.dao.dbtwo         #mapper包路径
        mapperxmlDir: classpath:mybatis/mapper/dbtwo/**/*.xml      #mapper.xml路径
        entityPackage: com.alicyu.springcloud.entities.dbtwo    #实体包路径
        mybatiscfg: classpath:mybatis/mybatis.cfg.xml      #mapper对应mybatis通用配置文件路径
        url: jdbc:mysql://localhost:3306/clouddb02?useSSL=false
        username: root
        password: zhicheng
        minPoolSize: 3
        maxPoolSize: 25
        maxLifetime: 20000
        borrowConnectionTimeout: 30
        loginTimeout: 30
        maintenanceInterval: 60
        maxIdleTime: 60
    package com.alicyu.config;
    
    import lombok.Data;
    
    @Data
    public class DBConfig1 {
       private String entityPackage;
       private String mapperxmlDir;
       private String mybatiscfg;
       private String url;
       private String username;
       private String password;
       private int minPoolSize;
       private int maxPoolSize;
       private int maxLifetime;
       private int borrowConnectionTimeout;
       private int loginTimeout;
       private int maintenanceInterval;
       private int maxIdleTime;
       private String testQuery;
    }
    package com.alicyu.config;
    
    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    
    @Data
    public class DBConfig2 {
       private String entityPackage;
       private String mapperxmlDir;
       private String mybatiscfg;
       private String url;
       private String username;
       private String password;
       private int minPoolSize;
       private int maxPoolSize;
       private int maxLifetime;
       private int borrowConnectionTimeout;
       private int loginTimeout;
       private int maintenanceInterval;
       private int maxIdleTime;
       private String testQuery;
    }
    package com.alicyu.config;
    
    import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.mybatis.spring.SqlSessionFactoryBean;
    import org.mybatis.spring.SqlSessionTemplate;
    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.jta.atomikos.AtomikosDataSourceBean;
    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 org.springframework.jdbc.datasource.DataSourceTransactionManager;
    import javax.sql.DataSource;
    import java.sql.SQLException;
    
    @Configuration
    @MapperScan(basePackages = "${spring.datasource01.mapperPackage}", sqlSessionFactoryRef = "SqlSessionFactory")
    public class DataSourceOneConfig {
       
        @Bean(name = "DBConfig1")
        @ConfigurationProperties(prefix="spring.datasource01")
        @Primary
        public DBConfig1 dBConfig1() {
            return new DBConfig1();
        }
    
        @Bean(name = "DataSource")
        @Primary
        public DataSource dataSource(@Qualifier("DBConfig1")DBConfig1 testConfig) throws SQLException {
            MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
            mysqlXaDataSource.setUrl(testConfig.getUrl());
            mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
            mysqlXaDataSource.setPassword(testConfig.getPassword());
            mysqlXaDataSource.setUser(testConfig.getUsername());
            mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
            // 将本地事务注册到创 Atomikos全局事务
            AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
            xaDataSource.setXaDataSource(mysqlXaDataSource);
            xaDataSource.setUniqueResourceName("DataSource");
            xaDataSource.setMinPoolSize(testConfig.getMinPoolSize());
            xaDataSource.setMaxPoolSize(testConfig.getMaxPoolSize());
            xaDataSource.setMaxLifetime(testConfig.getMaxLifetime());
            xaDataSource.setBorrowConnectionTimeout(testConfig.getBorrowConnectionTimeout());
            xaDataSource.setLoginTimeout(testConfig.getLoginTimeout());
            xaDataSource.setMaintenanceInterval(testConfig.getMaintenanceInterval());
            xaDataSource.setMaxIdleTime(testConfig.getMaxIdleTime());
            xaDataSource.setTestQuery(testConfig.getTestQuery());
            return xaDataSource;
        }
    
    
        //提供SqlSeesion
        @Bean(name = "SqlSessionFactory")
        @Primary
        public SqlSessionFactory sqlSessionFactoryBean(@Qualifier("DataSource") DataSource dataSource,@Qualifier("DBConfig1")DBConfig1 testConfig) throws Exception {
            SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
            PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            // 数据源
            sqlSessionFactoryBean.setDataSource(dataSource);
            //下边的值可以通过@value
            // 实体返回映射
            sqlSessionFactoryBean.setTypeAliasesPackage(testConfig.getEntityPackage());
            // sql xml文件路径
            sqlSessionFactoryBean.setMapperLocations(resolver.getResources(testConfig.getMapperxmlDir()));
            // 配置文件
            sqlSessionFactoryBean.setConfigLocation(resolver.getResource(testConfig.getMybatiscfg()));
            return sqlSessionFactoryBean.getObject();
        }
    
    //    因为事务会统一交给Atomikos全局事务,(因为是用了AtomikosDataSourceBean管理数据源),所以不能添加其他事务管理器
    //    // 事务管理
    //    @Bean(name = "transactionManager")
    //    @Primary
    // public DataSourceTransactionManager transactionManager(@Qualifier("DataSource") DataSource dataSource) {
    //    return new DataSourceTransactionManager(dataSource);
    // }
        // sqlSessionTemplate
        @Bean(name = "sqlSessionTemplate")
        @Primary
       public SqlSessionTemplate sqlSessionTemplate(@Qualifier("SqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
            return new SqlSessionTemplate(sqlSessionFactory);
       }
    
    }
    
    package com.alicyu.config;
    
    import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.mybatis.spring.SqlSessionFactoryBean;
    import org.mybatis.spring.SqlSessionTemplate;
    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.jta.atomikos.AtomikosDataSourceBean;
    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 org.springframework.jdbc.datasource.DataSourceTransactionManager;
    
    import javax.sql.DataSource;
    import java.sql.SQLException;
    
    @Configuration
    @MapperScan(basePackages = "${spring.datasource02.mapperPackage}", sqlSessionFactoryRef = "SqlSessionFactory02")
    public class DataSourceTwoConfig {
       
        @Bean(name = "DBConfig2")
        @ConfigurationProperties(prefix="spring.datasource02")
        public DBConfig2 dBConfig2() {
            return new DBConfig2();
        }
    
        @Bean(name = "DataSource02")
        public DataSource dataSource(@Qualifier("DBConfig2")DBConfig2 testConfig) throws SQLException {
            MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
            mysqlXaDataSource.setUrl(testConfig.getUrl());
            mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
            mysqlXaDataSource.setPassword(testConfig.getPassword());
            mysqlXaDataSource.setUser(testConfig.getUsername());
            mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
            // 将本地事务注册到创 Atomikos全局事务
            AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
            xaDataSource.setXaDataSource(mysqlXaDataSource);
            xaDataSource.setUniqueResourceName("DataSource02");
            xaDataSource.setMinPoolSize(testConfig.getMinPoolSize());
            xaDataSource.setMaxPoolSize(testConfig.getMaxPoolSize());
            xaDataSource.setMaxLifetime(testConfig.getMaxLifetime());
            xaDataSource.setBorrowConnectionTimeout(testConfig.getBorrowConnectionTimeout());
            xaDataSource.setLoginTimeout(testConfig.getLoginTimeout());
            xaDataSource.setMaintenanceInterval(testConfig.getMaintenanceInterval());
            xaDataSource.setMaxIdleTime(testConfig.getMaxIdleTime());
            xaDataSource.setTestQuery(testConfig.getTestQuery());
            return xaDataSource;
        }
    
        //提供SqlSeesion
        @Bean(name = "SqlSessionFactory02")
        public SqlSessionFactory sqlSessionFactoryBean(@Qualifier("DataSource02") DataSource dataSource,@Qualifier("DBConfig2")DBConfig2 testConfig) throws Exception {
            SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
            PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            // 数据源
            sqlSessionFactoryBean.setDataSource(dataSource);
            //下边的值可以通过@value
            // 实体返回映射
            sqlSessionFactoryBean.setTypeAliasesPackage(testConfig.getEntityPackage());
            // sql xml文件路径
            sqlSessionFactoryBean.setMapperLocations(resolver.getResources(testConfig.getMapperxmlDir()));
            // 配置文件
            sqlSessionFactoryBean.setConfigLocation(resolver.getResource(testConfig.getMybatiscfg()));
            return sqlSessionFactoryBean.getObject();
        }
        //    因为事务会统一交给Atomikos全局事务,(因为是用了AtomikosDataSourceBean管理数据源),所以不能添加其他事务管理器
    //    // 事务管理
    //    @Bean(name = "transactionManager02")
    // public DataSourceTransactionManager transactionManager(@Qualifier("DataSource02") DataSource dataSource) {
    //    return new DataSourceTransactionManager(dataSource);
    // }
        // sqlSessionTemplate
        @Bean(name = "sqlSessionTemplate02")
       public SqlSessionTemplate sqlSessionTemplate(@Qualifier("SqlSessionFactory02") SqlSessionFactory sqlSessionFactory) {
            return new SqlSessionTemplate(sqlSessionFactory);
       }
    
    }
    

     

    之后对两个数据源的操作,抛异常后数据都会滚了

    有时候还会报一些分布式事务授权问题,有时候及时报这个错也能用,然后要给相应的数据库用户受相应的权限

    解决方法:数据库执行 GRANT XA_RECOVER_ADMIN ON *.* TO `root`@`%`; 授予root用户在所有库下的所有表上执行 XA RECOVER语句的权限     或参考:https://ask.csdn.net/questions/759904

    展开全文
  • 提示:以下是本篇文章正文内容,下面案例仅供参考 一、数据源系统 1.编写接口 作为数据源的系统需要写一个接口,约束好参数和返回值,以json形式提供给需要获取数据的系统。为了数据安全起见,需要约定好密钥和公钥...

    前言

    最近做了两个系统,现在有一个需求,需要做数据对接,这个有很多方法实现,我这里用的是接口对接。由于是第一次做这种功能,也踩了不少坑,所以在这里记录一下。


    提示:以下是本篇文章正文内容,下面案例仅供参考

    一、数据源系统

    1.编写接口

    作为数据源的系统需要写一个接口,约束好参数和返回值,以json形式提供给需要获取数据的系统。为了数据安全起见,需要约定好密钥和公钥,只有两把钥匙都符合,才可以提取数据。

    public void getYpinfo(){
    	JSONObject jsonData = new JSONObject();
    	System.out.println(this.getRequest());
    	String cydbh = this.getRequest().getHeader("cydbh");
    	String fkey =this.getRequest().getHeader("key");
    	String ftoken =this.getRequest().getHeader("token");
    	jsonData = drugDao1.getYpinfo(cydbh, fkey, ftoken);
    	writeJson(jsonData);
    }
    
    public JSONObject getYpinfo(String cydbh,String fkey,String ftoken){
    		JSONObject json = new JSONObject();
    		List< Map<String,Object>> resultMaps = new ArrayList<>();
    		 Map<String,Object> map = new HashMap<String,Object>();
    		//OfficeConfig密钥等信息写在配置文件里
    		//token(公钥)  key(私钥) 
    		String systoken  = OfficeConfig.get("yptoken");
    		if(ftoken==null||!ftoken.equals(systoken)){
    			map.put("message", "令牌失效,请联系榕盛系统开发工程师!");
    			map.put("resultdata", null);
    			map.put("success", false);
    		}else {
    			TSysDepartment department=findBySid(TSysDepartment.class,fkey);
    			System.out.println(fkey);
    			if(Util.isEmpty(department)){
    				map.put("message", "秘钥失效,请联系系统开发工程师!");
    				map.put("resultdata", null);
    				map.put("success", false);
    			}else{
    				List<TDrugSample>  basics=findByProperty(TDrugSample.class, "fcybh", uuid);
    				if(basics.size()>1){
    					//后期可以加多抽样状态控制,抽样计划控制,即某种状态才能被提取。
    					map.put("message", "UUID【"+uuid+"】重复无法提取,请联系系统开发工程师!");
    					map.put("resultdata", null);
    					map.put("success", false);
    				}else if( !department.getFdepmc().equals(basics.get(0).getFjydw())){
    					//后期可以加多抽样状态控制,抽样计划控制,即某种状态才能被提取。
    					map.put("message", "抽样单编号【"+uuid+"】不属于本单位,请确认再提取!"+department.getFdepmc()+"    ==   "+basics.get(0).getFjydw());
    					map.put("resultdata", null);
    					map.put("success", false);
    				}else{
    					TDrugSample basic = basics.get(0);
    					YpcyInterfaceBasicFileds basicFileds = getBasicFiles(basic);
    					map.put("message", "操作成功!");
    					map.put("resultdata", basicFileds); 
    					map.put("success", true);
    					log.saveLog(null, LeeCyCzjl.type_base,  "通过接口提取抽样单信息", 
    							basic.getFypmc(), basic.getFcybh(), basic.getFsid(), "",null);
    				}
    			}
    		}
    		HashMap result = new HashMap();
    		result.put("result", map);
    		json.put("result", result);
    		String jm = AESUtil.encrypt(OfficeConfig.get("yptoken"), json.toString());
    		System.out.println("加密内容:"+jm);
    		json.put("result", result);
    		return json;
    	}
    }
    

    这里我们把密钥等信息写在配置文件里面,我们这里写一个工具类来调用这个配置文件里的信息,代码示例如下:

    import org.apache.commons.configuration.ConfigurationException;
    import org.apache.commons.configuration.HierarchicalConfiguration;
    import org.apache.commons.configuration.XMLConfiguration;
    import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
    import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
    /**
     * 获取系统配置信息
     * @author Administrator
     *
     */
    public class OfficeConfig {
    	 private static XMLConfiguration config;
    	static{
    		try {
    			XMLConfiguration.setDefaultListDelimiter(';');
    			config = new XMLConfiguration(
    					"sysConfig.xml");
    			config.setReloadingStrategy(new FileChangedReloadingStrategy());
    			((HierarchicalConfiguration) config)
    			.setExpressionEngine(new XPathExpressionEngine());
    		} catch (ConfigurationException e) {
    			// TODO Auto-generated catch block
    			throw new RuntimeException(e);
    		}
    		
    	}
    	public static String get(String key){
    		return config.getString(key);
    	}
    }
    

    配置文件代码如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <a>
    	<email-smtp></email-smtp>
    	<email-from></email-from>
    	<email-username></email-username>
    	<email-password></email-password>
    	<web-title>后台管理应用</web-title>
    	<!-- 机构名称简写 -->
    	<jgmcjx>gxfda</jgmcjx>
    	<!-- 机构名称 -->
    	<jgmc>机构名称</jgmc>
    	<!-- yptoken -->
    	<yptoken>b2fefca95e0579f2</yptoken>
    </a>
    

    到了这里,我们已经把接口做好,另一端只需要把两把钥匙匹配正确,就可以根据唯一的编号来提取数据了。当然,现在我们的接口没有直接暴露出来,可能需要用户登录的session才能访问,所以我们可能还需要通过重写过滤器来放行我们的接口方法。

    二、接收数据

    1.引入库

    代码如下(示例):

    /**
    	 * 提取数据
    	 * @author XXX
    	 * @Date 2021年4月16日上午10:30:16
    	 */
    	public void tqYaop(){
    		JSONObject json = dao.tqYaop(cydbh,this.getCurrentUser());
    		writeJson(json);
    	}
    

    实现代码:

    public JSONObject tqYaop(String cydbh,TSysPerson per) {
    		JSONObject json = new JSONObject();
    		try {
    			List sp = this.findByProperty(DrugBasic.class, "lccb_chouyjpzbh",cydbh);
    			if(sp!=null&&sp.size()>0){
    				 json.put("state", "0");
    				 json.put("msg", "抽样编号【"+cydbh+"】已存在");
    				 //continue;
    			}else{
    			Map<String, String> header = new HashMap<String, String>();
    			 String  yptoken= OfficeConfig.get("yptoken");
    			 String yptqurl =  OfficeConfig.get("yptqurl") ;
    			 String ypkey = OfficeConfig.get("ypkey");
    			header.put("token", yptoken);
    	        header.put("key", ypkey);
    	        header.put("cydbh", cydbh);
    	        String url = yptqurl;
    	        
    	   	 	Map<String, Object> result = postHttpReq("", url, header);
    	        String str=result.get("result").toString(); 	       
     	       //解密
    	    	//String mingwen = AESUtil.decrypt(OfficeConfig.get("yptoken"), str);
    	        //System.out.println(mingwen);
    	        //解析接口数据
     	        JSONObject mingwenJson=JSONObject.fromObject(str);
     	        String str3=mingwenJson.getString("result");
     	       JSONObject resultJson=JSONObject.fromObject(str3);
     	       //String str4=resultJson.getString("result");
    	       //resultJson=JSONObject.fromObject(str4);
     	       if(resultJson.containsKey("success")&&resultJson.getBoolean("success")){
     	    	   String datastr = resultJson.getString("resultdata");
     	    	   JSONArray datalist = new JSONArray();
     	    	   datalist.add(datastr);
     	    	   DrugBasic basic = new DrugBasic();
    		        if(datalist!=null&&datalist.size()>0){
    		        	java.util.Iterator<Object> it = datalist.iterator();
    		        	while(it.hasNext()){
    		        		JSONObject objs = (JSONObject) it.next();
    		        		Set<Entry<String, Object>> et = new TreeSet<>();
    						et = objs.entrySet();
    						for(Entry<String, Object> ent:et){
    							String key = ent.getKey();
    							Object value = ent.getValue();
    							String valueStr = value.toString();
    							basic=this.getYpBasic(key,valueStr,basic,per);
    						}
    						
     						if(sp!=null&&sp.size()>0){
     							 json.put("state", "0");
     							json.put("msg", "抽样编号【"+basic.getLccb_chouyjpzbh()+"】已存在");
     							continue;
     						}else{
     							basic.setLccb_jpdl("药品");
     							basic.setFsid(new GUID().toString());
     							basic.setFpubtime(new Date());
     							basic.setLccb_jianpUID(Util.jianpUID(basic));
     							basic.setLccb_sbzt("未上报");
     							this.attachDirty(basic);
     							 json.put("state", "1");
     				 			  json.put("msg", "提取数据成功!");
     						}
    		        	}
    		        }else{
    		        	  json.put("state", "0");
    		 			  json.put("msg", "未提取到任何数据!");
    		        }
     	       }else{
     	    	  json.put("state", "0");
     			  json.put("msg", "提取失败:"+(resultJson.getString("message")==null?"":resultJson.getString("message")));
     	       }
    			} 
    		} catch (Exception e) {
    			e.printStackTrace();
    			json.put("state", "0");
    			json.put("msg", "提取失败:"+e.getMessage());
    		}
    		return json;
    	}
    
    //数据下载上报操作方法
    	//{{
    	//ebs的数据下载、上报方式
    	private static JSONObject postHttpReq(String json, String url, Map<String, String> headers) {
            JSONObject resJson = new JSONObject();
            HttpClient httpClient = new HttpClient();
            byte b[]=null;
    		try {
    			b = json.getBytes("utf-8");
    		} catch (UnsupportedEncodingException e1) {
    			// TODO Auto-generated catch block
    			e1.printStackTrace();
    		}// 把字符串转换为二进制数据
            RequestEntity requestEntity = new ByteArrayRequestEntity(b);
            EntityEnclosingMethod postMethod = new PostMethod();
            postMethod.setRequestEntity(requestEntity);// 设置数据
            postMethod.setPath(url);// 设置服务的url
            postMethod.setRequestHeader("Content-Type", "application/json;charset=utf-8");// 设置请求头编码
            postMethod.addRequestHeader("Content-Type","application/json;charset=UTF-8");
            /*postMethod.setRequestHeader("Content-Type", "text/html;charset=UTF-8");*/
            if(headers!=null){
            	 for (Map.Entry<String, String> entry : headers.entrySet()) {
                     postMethod.setRequestHeader(entry.getKey(), entry.getValue());
                 }
            }
            // 设置连接超时
            httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(5 * 10000);
            // 设置读取超时
            httpClient.getHttpConnectionManager().getParams().setSoTimeout(20 * 10000);
            int statusCode = 0;
            try {
                statusCode = httpClient.executeMethod(postMethod);// 发送请求
                System.out.println(statusCode);
                //加状态码处理
                if(200<=statusCode &&statusCode<300){
                	resJson = JSONObject.fromObject(postMethod.getResponseBodyAsString());
                }else{
                }
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                postMethod.releaseConnection();// 释放连接
            }
            if (statusCode != HttpStatus.SC_OK) {
            }
            return resJson;
        }
    

    这里密钥等信息也是一样,写在配置文件里,通过工具类来调用。
    然后就可以根据提取到的数据来一一插入到数据库了。

    总结

    就这样子啦,很少写总结,可能有遗漏的东西,以后随时修改。

    展开全文
  • LTE概述

    千次阅读 2019-10-09 17:05:00
    [外链图片转存失败,站可能有防盗链机制,建议将图片保存下来直接上传(img-Gs3cH5TP-1570611879891)(C:\Users\gxdbi\Desktop\MarkDownd笔记\通信\image\LTE全网架构.png)] 网络结构扁平化 全IP ...
  • ESP8266 wifi模块开发汇总

    千次阅读 多人点赞 2018-10-23 08:35:19
    因为 ESP8266 Station 会遍历各个信道查找目标路由,意味着 ESP8266 其实在不停切换信道,ESP8266 SoftAP 的信道也因此在不停更改。这可能导致 ESP8266 SoftAP 端的原有连接断开,或者 UDP 丢包,ping 丢包等情况。 ...
  • 由于项目需要,需要为我的springMCV项目配置mysql+oracle两种数据源,运行时进行切换。由于技术水平有限,断断续续用了一周时间解决了这个小问题(比较懒),特来发篇博客,为以后需要的朋友提供参考。 首先,这个...
  • regionId: 'cn-shanghai', } } var that = this //传进去三元组等信息,拿到mqtt连接的各个参数 let clientOpt = aliyunOpt.getAliyunIotMqttClient({ productKey: that.data.aliyunInfo.productKey, deviceName: ...
  • 新版本支持阿里云全线产品,支持十余款主流开源数据系统,可帮助企业和个人轻松实现不同数据源之间的数据同步。 开源地址:https://github.com/alibaba/DataX DataX简介 DataX 是一个异构数据源离线同步...
  • 导语:SuperSQL是腾讯数据平台部自研的跨数据源、跨数据中心、跨执行引擎的统一大数据SQL分析平台/中间件,支持对接适配多类外部开源SQL执行引擎,如Spark、Hi...
  • Android面试题整理

    千次阅读 2018-02-27 21:56:47
    参考TextView的代码,BaseSavedState是View的一个内部静态类,从代码上我们也很容易看出是把控件的属性(如selStart)打包到Parcel容器,Activity的onSaveInstanceState、onRestoreInstanceState最终也会调用到...
  • IT系统对接方案汇总

    千次阅读 2021-02-07 09:28:46
    如果系统之间存在权限限制或技术限制,可采用接口以保证数据的安全和对接的规范性等等,不同的场景下有不同的对接方案,以下对常用的对接方案做出汇总。 技术方案 接口 接口对接方式是比较常用,且安全规范的传输...
  • 随着信息化的发展,以及互联网、万物互联的逐渐普及和大数据时代的来临,数据资产已经成为企业的战略武器,越来越多的企事业单位不断上线了各种软件系统,另外随着移动互联网的发展,又出现了移动端多端应用,既出现...
  • 数据源的数据库包含关系数据库,本地数据库(Java数据源,Excel数据源,主要用于自定义数据结构及上传本地数据文件),多维数据库,高速缓存库(用于抽取数据和优化性能),NoSQL数据库(如MongoD
  • 众所周知,工业大数据是工厂智能化水平提升的关键路径,而数据源的分析和管理是工厂大数据的基础,理清理顺数据源也是实施工厂大数据的第一步。本文就对工厂的数据源进行简要分析。数据源的划分以下从两个维度对工厂...
  • LCD

    千次阅读 2017-12-09 20:27:27
    注意:本资料来源于朱友鹏老师的课程的视频,只用于学习使用,如用于其他用途,请联系朱老师本人,不然后果自负,不允许转载!!!LCD的接口技术从电平角度来讲本质上都是TTL信号 ...所以他们俩本来是可以直接对接
  • 统一配置数据源:蓝鲸配置平台

    千次阅读 2018-09-07 18:12:48
    这样一来,在企业中,只需要维护一套核心的CMDB数据源,就能在几乎所有场景中消费并维护数据。         3、 统一的、流转的、“活”起来的、闭环的CMDB数据源泉 在上述任意一种运维...
  • 如今,以“智慧医疗”为代表的健康信息化建设...医疗领域的数据源虽然众多,但医院各种系统的数据库还是最重要的医疗数据来源。 在中国,一家普通医院的软件系统往往有十几个到几十个不等,三甲医院内部信息系统...
  • 统一数据接入实践分享

    千次阅读 2019-10-08 00:15:21
    数据接入就是对于不同的数据来源、不同的合作伙伴,完成数据采集、数据传输、数据处理、数据缓存到行业统一的数据平台的过程。 数据接入的三个阶段 前 0.非结构化数据----(word,excel,图片,pdf,扫描件,视频...
  • 中台概念着实火了一把,继去年购买了“数据中台”的百度搜索指数后,昨天我又购买了“业务中台”的百度指数,可能是由于刚刚购买,全量数据还没有统计汇总出来,所以当我们在百度指数中,搜索业务中台的时候,目前...
  • 端OGG 12.3.0.1.4 目标端OGG 12.3.0.1.2 Ambari HDP-2.6.4.0 Kerberos 1.10.3-10 kafka 0.10.1 zookeeper 3.4.6 一. Ambari下开启Kerberos使用kafka 一般对于新加入分布式系统的...
  • 第三方接口对接之鉴权

    千次阅读 2019-04-24 15:06:47
    第三方接口对接 鉴权 记录一次和第三方接口对接的过程。 对接要求 对方的接口做了加密验证,需要将参数进行加密生成一个Signature签名。然后对方也会根据参数做一样的步骤来比对签名是否相等来判断参数是否被篡改...
  • 点击上方 "云祁QI"关注,星标或置顶一起成长如今,随着诸如互联网以及物联网等技术的不断发展,越来越多的数据被生产出来。据统计,每天大约有超过2.5亿亿字节的各种各样数据...
  • 在启动Slipstream之前... Slipstream和Inceptor的server不在一个节点上,只能共同使用同一个数据库,相关操作只能在Slipstream的引擎中操作。  安全模式下,一个Stream要成功读取/写入一个topic中的消息必须满...
  • openstack对接ceph存储

    千次阅读 2020-04-17 11:26:38
    把这个卷挂到云主机上,如果云主机出现故障被删除,那么在找一个云主机,把这个卷挂到新的云主机上,数据还是会存在的,不丢失 cinder与nfs对接 (1)创建一个pv,vg(controller节点操作) vgdisplay 显示如下 ...
  • 开源离线同步工具—DataX3.0介绍

    千次阅读 2018-05-16 10:41:23
    ​ DataX 是一个异构数据源离线同步工具,致力于实现包括关系型数据库(MySQL、Oracle等)、HDFS、Hive、ODPS、HBase、FTP等各种异构数据源之间稳定高效的数据同步功能。 设计理念 为了解决异构数据源同步问题...
  • XX数据中心技术方案

    千次阅读 2020-11-15 22:49:45
    “证券公司应将数据治理纳入公司整体信息技术建设战略规划,制定数据标准,涵盖数据源管理、数据库建设、数据质量监测等环节。” 业务背景 中国金融行业发展迅速,随着互联网,软件等行业的推陈出新,全球信息化...
  • 本文探讨了消息总线的扩展方向——面向消息的数据集成的可行性。
  • 其次,受限于数据规模和数据源种类的丰富程度,多数企业的数据应用刚刚起步,主要集中在精准营销,舆情感知和风险控制等有限场景,应用深度不够,应用空间亟待开拓。 再次,由于数据的价值很难评估,企业难以对...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 13,409
精华内容 5,363
关键字:

对接各个数据源