动态数据源、多数据源配置和管理 [附源码]

动机

业务系统中经常会需要同时操作多个数据库。常见的场景有:

  • 数据量太大,需要分表分库存储。
  • 一些数据库操作类工具,比如:报表工具、自定义表单工具,需要随时添加和编辑数据源信息。

对应的处理方法已有两种:

  • 对多个数据源是已知的、固定的,可以通过spring的AbstractRoutingDataSource类来实现数据库的路由。先腾框架持久化模块中的动态数据源DynamicDataSource 对AbstractRoutingDataSource进行了封装,并通过AOP 的方式用注解实现了数据源的路由。
  • 对于数据源不确定的场景先腾框架中的数据基础操作模块中的DbcpConnectPools对数据库链接池进行了封装,使得对数据源的操作根简单,并且提供了事务执行框架TransactionHandler.java。

动态数据源DynamicDataSource

DynamicDataSource 设计思想

DynamicDataSource 动态数据源,通过对Spring的AbstractRoutingDataSource 的封装提供动态数据源获取支持。

原理:

DynamicDataSource 采用 AOP 机制拦截所有使用了注解 @TargetDataSource("...") 定义的类和方法,然后在此类中的方法和这些方法被调用之前自动切换当前数据源为 @TargetDataSource("...") 定义的数据源,为方法中使用提供适合的数据源(org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource 提供了多数据源注册和根据注册类型查找对应的数据源能力)。

DynamicDataSource 使用步骤

步骤1: 在Spring配置文件中配置多个数据源。

<bean id="ds1" class="org.apache.commons.dbcp2.BasicDataSource" ...>...</bean>
<bean id="ds2" class="org.apache.commons.dbcp2.BasicDataSource" ...>...</bean>
<bean id="ds3" class="org.apache.commons.dbcp2.BasicDataSource" ...>...</bean>
<bean id="ds4" class="org.apache.commons.dbcp2.BasicDataSource" ...>...</bean>

步骤2: 将这些数据源注册给

com.centit.framework.core.datasource.DynamicDataSource

<bean id="dynamic_datasource" class="com.centit.framework.core.datasource.DynamicDataSource">
    <!-- 注册目标多个数据源 -->
    <property name="targetDataSources">  
        <map key-type="java.lang.String">
           <!--这里的key值将是 @TargetDataSource("...") 的值-->  
           <entry key="ds1" value-ref="ds0"/> 
           <entry key="ds2" value-ref="ds1"/>
           <entry key="ds2" value-ref="ds2"/>
           <entry key="ds2" value-ref="ds3"/>
        </map>  
    </property>
    <!-- 如果出现了未知的数据源,则将使用这里配置的默认数据源替代 -->
    <property name="defaultTargetDataSource" ref="ds1"/>  
</bean>

步骤3: 配置生效 DynamicDataSourceAspect AOP切面拦截器。

<bean id="dynamicDataSourceAspect" class="com.centit.framework.core.datasource.DynamicDataSourceAspect">
</bean>

步骤4: 使用注解 @TargetDatasource("...") 定义类或方法要用到的数据源即可。

注解定义在类上,类中的所有方法都使用这个数据源。

/** 
 * use on class, all methods in this class will be use 'ds1' datasource.
 */
@Service("userService")
@TargetDataSource("ds1")
public class UserService {
    public List<User> findUsers() {
        // ...
    }
}

注解定义在方法上,这个方法使用这个数据源。

/** 
 * use on method, the method will be use 'ds4' datasource.
 */
@Service("orderService")
public class OrderService {

    @TargetDataSource("ds4")
    public List<OrderItem> findOrderItems() {
        // ...
    }
}

根据参数动态的计算模板库,需要将mapByParameter 设置为true,value中的值为四则运算表达式

/** 
 * 根据参数动态的计算数据源
 */
@Service("shoppingCartService")
public class ShoppingCartService {

    public List<ShoppingItem> findShoppingItems() {
        // ...
    }    

    @TargetDataSource(value = "'ds'+ (userId mod 4 + 1)", mapByParameter = true)
    public Address getDefaultDeliverAddress(long userId) {
        // ...
    }
}

动态链接池管理 DbcpConnectPools

动态连接池管理通过三个类实现:

数据源描述 DataSourceDescription

数据源通过数据的jdbc-url和用户名唯一确定,就是同一个url不同的用户也是认为不同的数据源的。每个数据源可以有自己的不同的配置信息。

public final class DataSourceDescription implements  Serializable{
    private String connUrl ;
    private String username ;
    private String driver ;
    private String password ;
    private DBType dbType;
    private int    maxTotal ;
    private int    maxIdle ;
    private int    minIdle ;
    private int    maxWaitMillis;
    private int    initialSize ;
    private String databaseCode;
    ...................
    @Override
    public boolean equals(Object dbco){
        ...........
    }

    @Override
    public int hashCode(){
        ...................
    }

动态数据源管理类DbcpConnectPools

这个类管理所有的数据源链接池;获取数据库链接式只需要调用 getDbcpConnect 方法就可以。

public static synchronized Connection getDbcpConnect(DataSourceDescription dsDesc)
 throws SQLException{
        BasicDataSource ds = dbcpDataSourcePools.get(dsDesc);
        if(ds==null)
            ds = addDataSource(dsDesc);
        Connection conn = null;
        conn = ds.getConnection();
        conn.setAutoCommit(false);  
        ///*dsDesc.getUsername(),dsDesc.getDbType(),*/
        return conn;
    }

事物执行框架 TransactionHandler

这个类只提供了一个方法executeInTransaction使用也很简单:

public  static void runInTransaction()  {
        DataSourceDescription dbc = new DataSourceDescription();
        dbc.setConnUrl("jdbc:oracle:thin:@192.168.131.81:1521:orcl");
        dbc.setUsername("fdemo2");
        dbc.setPassword("fdemo2");
        /**
         * 假设这个对象是你要保存的; 如果调用 OrmDaoUtils.saveNewObject 成功,这个对象上必须有jpa注解
         * 有jpa注解就不用自己写sql语句了,否则自己写insert语句也是可以的
         */
        Object userInfo = new Object();
        try {
            Integer ret = TransactionHandler.executeInTransaction(dbc, (conn) -> {
                /**
                 * 这两个操作是在一个事物中的
                 */
                DatabaseAccess.doExecuteSql(conn, "delete from table where a=? and b=?",
                        new Object[]{"a",5});
                return OrmDaoUtils.saveNewObject(conn, userInfo);
            });
            System.out.println(ret);
        }catch (SQLException e){
            System.out.println(e.getLocalizedMessage());
        }
    }

源码

猜你喜欢

转载自blog.csdn.net/code_fan/article/details/81363806
今日推荐