一個動態數據庫路由實現


需求:

業務庫按機構+應用進行了分庫,業務系統要求能根據機構+應用切換數據庫連接.

實現:

系統將所有業務庫的數據連接信息保存到一個路由數據庫中的路由表里.

程序在需要連接具體的業務庫時,可以查詢路由表得到連接信息,並建立連接.

知識點:

spring JdbcTemplate JavaConfig

首先定義一個助手接口JDBCRouteHelper,提供給業務類使用.

下面是一個業務類使用JDBCRouteHelper查詢業務庫的方式:

 1     @Autowired
 2     JDBCRouteHelper jdbcRouteHelper;
 3     
 4     @Override
 5     public Map<String, Object> getItem(Map<String, Object> inParam) {
 6         if (MapUtil.getValue("id", inParam) == "")
 7             throw new InParamCheckException("id不能為空:" + inParam.get("id"));
 8         String orgId = MapUtil.getValue("org_id", inParam);
 9         String appId = IDConstants.APP_ID_PIS;
10         NamedParameterJdbcTemplate jdbc = jdbcRouteHelper.getJDBCTemplate(orgId, appId);
11 
12         return new MyMap().put("simple_info", jdbc.query("select * from simple_info where id=:id", inParam, new HashMapRowMapper()))
13                 .put("simple_test",
14                         jdbc.query("select * from simple_test where simple_info_id=:id", inParam, new HashMapRowMapper()))
15                 .getMap();
16     }
 1 public interface JDBCRouteHelper {
 2 
 3     /**
 4      * @param jdbcRouteTemplate
 5      * 設置路由配置數據庫的連接
 6      */
 7     void setRouteJdbc(NamedParameterJdbcTemplate jdbcRouteTemplate);
 8     
 9     /**
10      * 
11      * @param orgId
12      * @param appId
13      * @return
14      * 得到指定機構與指定應用對應的數據庫操作模板類
15      */
16     NamedParameterJdbcTemplate getJDBCTemplate(String orgId, String appId);
17     
18     /**
19      * @param orgId
20      * @param appId
21      * @return
22      * 得到指定機構與指定應用對應的數據庫事務模版類
23      */
24     public TransactionTemplate getTransactionTemplate(String orgId, String appId);
25 
26 }

接口實現類

public class JDBCRouteHelperImp implements JDBCRouteHelper {

    //緩存所有業務數據庫的jdbc模版
    private final static Map<String, NamedParameterJdbcTemplate> mapJdbc = new ConcurrentHashMap<>();
    //緩存所有業務數據庫的datasource
    private final static Map<String, DataSource> mapDataSource = new ConcurrentHashMap<>();
    //緩存所有業務數據庫的事務模版
    private final static Map<String, TransactionTemplate> mapTx = new ConcurrentHashMap<>();
    private final static Object lockDS = new Object();
    private final static Object lockJDBC = new Object();
    private final static Object lockTX = new Object();
    //路由配置庫的連接
    NamedParameterJdbcTemplate jdbcRouteTemplate;
    //查詢路由配置庫的連接屬性
    static String sqlGetDS = "select * from bas_datasource where " + "org_id=:orgId and app_id=:appId and dbs_type=1";
    
    JDBCRouteHelperImp(NamedParameterJdbcTemplate jdbcRouteTemplate) {
        this.jdbcRouteTemplate = jdbcRouteTemplate;
    }

    @Override
    public void setRouteJdbc(NamedParameterJdbcTemplate jdbcRouteTemplate) {
        this.jdbcRouteTemplate = jdbcRouteTemplate;
    }

    @Override
    public NamedParameterJdbcTemplate getJDBCTemplate(String orgId, String appId) {
        String dbKey = orgId + "_" + appId;
        if (mapJdbc.get(dbKey) == null) {
            synchronized (lockJDBC) {
                if (mapJdbc.get(dbKey) == null) {
                    DataSource dataSource = getDataSource(orgId, appId);
                    NamedParameterJdbcTemplate jdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
                    mapJdbc.put(dbKey, jdbcTemplate);
                }
            }
        }

        return mapJdbc.get(dbKey);
    }
    
    @Override
    public TransactionTemplate getTransactionTemplate(String orgId, String appId) {
        String dbKey = orgId + "_" + appId;
        if (mapTx.get(dbKey) == null) {
            synchronized (lockTX) {
                if (mapTx.get(dbKey) == null) {
                    DataSource dataSource = getDataSource(orgId, appId);
                    DataSourceTransactionManager txManager = new DataSourceTransactionManager(dataSource);
                    TransactionTemplate transactionTemplate = new TransactionTemplate(txManager);
                    mapTx.put(dbKey, transactionTemplate);
                }
            }
        }
        return mapTx.get(dbKey);

    }
    
    /**
     * @param orgId
     * @param appId
     * @return
     * 得到指定機構與指定應用對應的DataSource
     */
    private DataSource getDataSource(String orgId, String appId) {
        String dbKey = orgId + "_" + appId;
        Map<String, String> paramMap = new HashMap<>();
        paramMap.put("orgId", orgId);
        paramMap.put("appId", appId);
        List<Map<String, Object>> lst = jdbcRouteTemplate.query(sqlGetDS, paramMap, new ColumnMapRowMapper());
        if (!lst.isEmpty()) {
            Map<String, Object> dsItem = lst.get(0);
            BasicDataSource dataSource = new BasicDataSource();
            dataSource.setDriverClassName((String) dsItem.get("DBS_DriverClassName"));
            dataSource.setUrl((String) dsItem.get("DBS_URL"));
            dataSource.setUsername((String) dsItem.get("DBS_UserName"));
            dataSource.setPassword((String) dsItem.get("DBS_Password"));
            mapDataSource.put(dbKey, dataSource);
        } else {
            throw new RuntimeException("在配置庫中沒有發現數據庫連接配置:orgId:[" + orgId + "]  appId:" + appId);
        }
        return mapDataSource.get(dbKey);
    }

}

通過spring 的JavaConfig將實現類放入容器

@Configuration
@PropertySource("classpath:mysql_route.properties")
public class JDBCRouteConfig {
    @Autowired
    Environment env;

    @Bean("jdbcRouteHelper")
    JDBCRouteHelper routeHelper() {
        BasicDataSource dsRoute = new BasicDataSource();
        dsRoute.setDriverClassName(env.getProperty("mysql.driverClassName"));
        dsRoute.setUrl(env.getProperty("mysql.url"));
        dsRoute.setUsername(env.getProperty("mysql.username"));
        dsRoute.setPassword(env.getProperty("mysql.password"));
        NamedParameterJdbcTemplate jdbcRouteTemplate = new NamedParameterJdbcTemplate(dsRoute);
        return new JDBCRouteHelperImp(jdbcRouteTemplate);
    }

}

業務系統加載helper的bean

 1 public class App 
 2 {
 3     public static void main( String[] args ) throws IOException
 4     {
 5         @SuppressWarnings("resource")
 6         AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
 7         ctx.register(JDBCRouteConfig.class);
 8         ctx.refresh();
 9         System.out.println("任意鍵退出");
10         System.in.read();
11     }
12 }

如果是web容器,也可以在web.xml中配置加載:

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.linsen.jbdc.JdbcRouteConfig
</context-param>

<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>

這里只是展示實現機制,如果是用於開發框架,進一步封裝可以不用業務方法直接調用獲取jdbc模版類方法,而是有框架封裝好,業務類直接注入框架組裝好的jdbc模版類.實現思路是在每個請求的filter中獲取具體業務庫的jdbc模板類,並刷到spring容器中.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM