首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Spring 多資料源路由實現分庫存儲:AbstractRoutingDataSource 實戰詳解

     編輯:Java 綜合教學

Spring 多資料源路由實現分庫存儲:AbstractRoutingDataSource 實戰詳解

當單庫資料量過大導致效能瓶頸時,分庫(Sharding)是常見解決方案。Spring 提供 AbstractRoutingDataSource 讓我們以極低成本實現動態資料源切換。本文完整示範:基於用戶 ID 取模路由、ThreadLocal 上下文傳遞、Spring XML/JavaConfig 配置、MyBatis 整合、註解驅動自動切換兩種模式。

1. 核心路由類:繼承 AbstractRoutingDataSource

public class RoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DBContext.getDBKey(); // 從 ThreadLocal 獲取當前資料源鍵
    }
}

2. 上下文持有者:ThreadLocal 隔離執行緒

public class DBContext {
    private static final int DB_COUNT = 2; // 資料庫數量
    private static final ThreadLocal<String> tlDbKey = new ThreadLocal<>();

    public static String getDBKey() { return tlDbKey.get(); }
    public static void setDBKey(String key) { tlDbKey.set(key); }
    public static void clear() { tlDbKey.remove(); }

    // 根據用戶 ID 取模決定資料源
    public static String getDBKeyByUserId(int userId) {
        int idx = userId % DB_COUNT;
        return "db_" + (idx + 1); // db_1, db_2
    }
}

3. JDBC 屬性檔:定義多資料源連線資訊

jdbc.driver=com.mysql.jdbc.Driver
jdbc.key-1=db_1
jdbc.url-1=jdbc:mysql://localhost:3306/db_1?useUnicode=true&characterEncoding=utf8
jdbc.user-1=test
jdbc.password-1=123456

jdbc.key-2=db_2
jdbc.url-2=jdbc:mysql://localhost:3306/db_2?useUnicode=true&characterEncoding=utf8
jdbc.user-2=test
jdbc.password-2=123456

4. Spring XML 配置:資料源定義與路由組裝

<bean id="parentDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="filters" value="stat"/>
    <property name="maxActive" value="20"/>
    <!-- 其它連線池參數 -->
</bean>

<bean id="dataSource1" parent="parentDataSource">
    <property name="url" value="${jdbc.url-1}"/>
    <property name="username" value="${jdbc.user-1}"/>
    <property name="password" value="${jdbc.password-1}"/>
</bean>

<bean id="dataSource2" parent="parentDataSource">
    <property name="url" value="${jdbc.url-2}"/>
    <property name="username" value="${jdbc.user-2}"/>
    <property name="password" value="${jdbc.password-2}"/>
</bean>

<!-- 路由資料源:核心 -->
<bean id="dataSource" class="com.example.utils.RoutingDataSource">
    <property name="targetDataSources">
        <map key-type="java.lang.String">
            <entry key="${jdbc.key-1}" value-ref="dataSource1"/>
            <entry key="${jdbc.key-2}" value-ref="dataSource2"/>
        </map>
    </property>
    <property name="defaultTargetDataSource" ref="dataSource1"/> <!-- 可選預設 -->
</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mapperLocations" value="classpath:mybatis/*.xml"/>
</bean>

5. Service 層手動切換資料源

@Service
public class UserServiceImpl implements UserService {
    @Autowired UserEntityMapper userEntityMapper;

    @Override
    public void addUser(UserEntity user) {
        DBContext.setDBKey(DBContext.getDBKeyByUserId(user.getUserId()));
        try {
            userEntityMapper.insertSelective(user);
        } finally {
            DBContext.clear(); // 防止 ThreadLocal 洩漏
        }
    }

    @Override
    public UserEntity getUser(int userId) {
        DBContext.setDBKey(DBContext.getDBKeyByUserId(userId));
        try {
            return userEntityMapper.selectByPrimaryKey(userId);
        } finally {
            DBContext.clear();
        }
    }
}

6. 進階:註解驅動自動切換(多 SqlSessionFactory)

新增第三個資料源 db_main(如訂單庫),為特定 Mapper 綁定專屬 SqlSessionFactory,無需手動切換。

<bean id="dataSourceMain" parent="parentDataSource">
    <property name="url" value="${jdbc.url-main}"/>
    <property name="username" value="${jdbc.user-main}"/>
    <property name="password" value="${jdbc.password-main}"/>
</bean>

<bean id="sqlSessionFactoryMain" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSourceMain"/>
    <property name="mapperLocations" value="classpath:mybatis/order/*.xml"/>
</bean>

<bean id="orderScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.example.mapper.order"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryMain"/>
</bean>

Mapper 介面綁定

@Resource(name = "orderScanner")
public interface OrderEntityMapper extends Mapper<OrderEntity> { }

Service 直接使用,無需切換

@Autowired OrderEntityMapper orderMapper;

public void addOrder(OrderEntity order) {
    orderMapper.insertSelective(order); // 自動走 db_main
}

7. 驗證分庫效果

分庫存儲驗證

插入用戶 ID 1、2、3... 後,db_1 存單號,db_2 存雙號,分庫生效。

8. 注意事項與最佳實踐

  • ✅ ThreadLocal 必須清理:使用 try-finally 或 Filter/Interceptor 在請求結束時 DBContext.clear()
  • ✅ 事務管理:分庫場景下跨庫事務需引入分佈式事務(Seata、Atomikos)或避免跨庫寫入
  • ✅ 查詢聚合:跨庫查詢需應用層聚合或引入中間件
  • ✅ 擴展性:資料源鍵可從配置中心動態刷新,支援線上擴容

Spring AbstractRoutingDataSource + ThreadLocal 是實現多資料源路由的經典輕量方案,適合中小型分庫場景;大規模分庫建議使用 ShardingSphere、MyCat 等專業中間件。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved