天天看點

flea-db使用之JPA分庫分表實作JPA分庫分表實作

JPA分庫分表實作

在開始本篇的講解之前,我先來說下之前寫過的兩篇博文【現在已棄用】:

flea-frame-db使用之基于EntityManager實作JPA分表的資料庫操作【舊】

flea-frame-db使用之基于FleaJPAQuery實作JPA分表查詢【舊】

這兩篇都與分表相關,之是以被棄用,是因為在并發場景中這一版的分表存在問題。雖然并發場景有問題,但與之相關的分表配置、分表實作也确實為本篇的分庫分表提供了一些基礎能力,這些不能被忽視,将會在本篇中一一介紹。

經過重構之後,目前 flea-db 子產品的結構如下圖所示:

flea-db使用之JPA分庫分表實作JPA分庫分表實作
子產品 描述
flea-db-common 分庫配置、分表配置、SQL模闆配置、異常 和 工具類等代碼
flea-db-eclipselink 基于EclipseLink版的JPA實作而定制化的代碼
flea-db-jdbc 基于 JDBC 開發的通用代碼
flea-db-jpa 基于 JPA 開發的通用代碼

1. 名詞解釋

名詞 解釋
模闆庫名 用作模闆的資料庫名
模闆庫持久化單元名 模闆庫下的持久化單元名,一般和模闆庫相同
模闆庫事物名 模闆庫下的事物管理器名 ,分庫配置中可檢視

<transaction>

标簽
分庫名 以模闆庫名為基礎,根據分庫規則得到的資料庫名
分庫持久化單元名 以模闆庫持久化單元名為基礎,根據分庫規則得到的持久化單元名,一般和分庫名相同
分庫事物名 以模闆庫事物名為基礎,根據分庫規則得到的事物名
分庫轉換 以模闆庫名為基礎,根據分庫規則得到資料庫名的過程
分庫序列鍵 分庫規則中

<split>

标簽中 seq 的值,組成分庫名表達式的一部分;如果是分庫分表,也對應着分表規則中

<split>

标簽中 seq 的值
分庫序列值 分庫序列鍵對應的值,在分庫轉換中使用
模闆表名 用作模闆的表名
分表名 以模闆表名為基礎,根據分表規則得到的表名
分表轉換 以模闆表名為基礎,根據分表規則得到表名的過程

2. 配置講解

2.1 分庫配置

分庫配置檔案預設路徑:flea/db/flea-lib-split.xml

<?xml version="1.0" encoding="UTF-8"?>
<flea-lib-split>
    <libs>
        <!-- 分庫配置
            name : 模闆庫名
            count: 分庫總數
            exp  : 分庫名表達式 (模闆庫名)(分庫序列鍵)
        -->
        <lib name="fleaorder" count="2" exp="(FLEA_LIB_NAME)(SEQ)" desc="flea訂單庫分庫規則">
            <!-- 分庫事物配置
                name : 模闆事物名
                exp  : 分庫事物名表達式 (模闆事物名)(分庫序列鍵)
            -->
            <transaction name="fleaOrderTransactionManager" exp="(FLEA_TRANSACTION_NAME)(SEQ)"/>
            <splits>
                <!-- 分庫轉換實作配置
                    key : 分庫轉換類型關鍵字【可檢視 LibSplitEnum】
                    seq : 分庫序列鍵【】
                    implClass : 分庫轉換實作類【可自行定義,需實作com.huazie.fleaframework.db.common.lib.split.ILibSplit】
                    注意:
                    (1)key不為空,implClass可不填
                    (2)key為空,implClass必填
                    (3)key 和 implClass 都不為空,implClass需要和分庫轉換類型枚舉中分庫轉換實作類對應上
                -->
                <split key="DEC_NUM" seq="SEQ"/>
            </splits>
        </lib>
    </libs>

    <!-- 其他子產品分庫配置檔案引入 -->
    <!--<import resource=""/>-->

</flea-lib-split>
           

分庫規則相關實作代碼,可以移步 GitHub 檢視 FleaSplitUtils##getSplitLib

2.2 分表配置

分表配置檔案預設路徑:flea/db/flea-table-split.xml

分庫分表案例中,實體類中 @Table 注解定義的表名,我們可以了解為模闆表名;實際的分表,根據模闆表名和分表規則确定,後面将慢慢講解。

<?xml version="1.0" encoding="UTF-8"?>
<flea-table-split>
    <tables>

        <!-- 分表配置
			name : 分表對應的模闆表名
            lib  : 分表對應的模闆庫名
            exp  : 分表名表達式 (FLEA_TABLE_NAME)_(列名大寫)_(列名大寫)
        -->
        <table name="order" lib="fleaorder" exp="(FLEA_TABLE_NAME)_(ORDER_ID)" desc="Flea訂單資訊表分表規則">
            <splits>
                <!-- 分表轉換實作配置
                    key    : 分表轉換類型關鍵字【可檢視 TableSplitEnum】
                    column : 分表屬性列字段名
                    seq    : 分庫序列鍵【若不為空,值需對應flea-lib-split.xml中<split seq="SEQ" />】
                    implClass : 分表轉換實作類【可自行定義,需實作com.huazie.fleaframework.db.common.table.split.ITableSplit】
                    注意:
                    (1)key不為空,implClass可不填
                    (2)key為空,implClass必填
                    (3)key 和 implClass 都不為空,implClass需要和分表轉換類型枚舉中分表轉換實作類對應上
                -->
                <split key="ONE" column="order_id" seq="SEQ"/>
            </splits>
        </table>

        <table name="order_attr" lib="fleaorder" exp="(FLEA_TABLE_NAME)_(ORDER_ID)" desc="Flea訂單屬性表分表規則">
            <splits>
                <split key="ONE" column="order_id" seq="SEQ"/>
            </splits>
        </table>

    </tables>

    <!-- 其他子產品分表配置檔案引入 -->
    <!--<import resource=""/>-->

</flea-table-split>
           

分表規則相關實作代碼,可以移步 GitHub 檢視 FleaSplitUtils##getSplitTable

2.3 JPA持久化單元配置

JPA持久化單元,包含了一組實體類的命名配置 和 資料源配置。實際使用中,一個 JPA持久化單元 一般對應一個資料庫,其中

<properties>

标簽指定具體的資料庫配置,包含驅動名、位址、使用者和密碼;

<class>

标簽指定該資料庫下的表對應的實體類。

<exclude-unlisted-classes>

标簽,當設定為 true 時,隻有列出的類和 jars 将被掃描持久類,否則封閉 jar 或目錄也将被掃描。

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">

	<persistence-unit name="fleaorder" transaction-type="RESOURCE_LOCAL">
		<!-- provider -->
		<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
		<!-- Connection JDBC -->
		<class>com.huazie.fleadbtest.jpa.split.entity.Order</class>
		<class>com.huazie.fleadbtest.jpa.split.entity.OrderAttr</class>
		<class>com.huazie.fleadbtest.jpa.split.entity.OldOrder</class>
		<class>com.huazie.fleadbtest.jpa.split.entity.OldOrderAttr</class>
		<exclude-unlisted-classes>true</exclude-unlisted-classes>

		<properties>
			<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
			<property name="javax.persistence.jdbc.url"
				value="jdbc:mysql://localhost:3306/fleaorder?useUnicode=true&amp;characterEncoding=UTF-8" />
			<property name="javax.persistence.jdbc.user" value="root" />
			<property name="javax.persistence.jdbc.password" value="root" />
			<!--<property name="eclipselink.ddl-generation" value="create-tables"/> -->
		</properties>
	</persistence-unit>
</persistence>
           

分庫場景,模闆庫和分庫都需要有一個對應的持久化單元配置,詳見 接入示範的持久化單元配置。

2.4 JPA相關Spring Bean配置

首先是JPA固定的Spring Bean配置,可檢視 fleajpabeans-spring.xml ,配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="defaultPersistenceManager"
          class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager">
        <property name="persistenceXmlLocations">
            <!-- 可以配置多個持久單元 -->
            <list>
                <value>classpath:META-INF/fleajpa-persistence.xml</value>
                <value>classpath:META-INF/fleaorder-persistence.xml</value>
                <value>classpath:META-INF/fleaorder1-persistence.xml</value>
                <value>classpath:META-INF/fleaorder2-persistence.xml</value>
            </list>
        </property>
    </bean>

    <bean id="defaultPersistenceProvider" class="org.eclipse.persistence.jpa.PersistenceProvider"/>

    <bean id="defaultVendorAdapter" class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
        <property name="showSql" value="true"/>
    </bean>

    <bean id="defaultJpaDialect" class="org.springframework.orm.jpa.vendor.EclipseLinkJpaDialect"/>

</beans>
           

與持久化單元對應的 Bean配置,可檢視 fleaorder-spring.xml,配置 如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- FleaOrder TransAction Manager JPA -->
    <bean id="fleaOrderEntityManagerFactory"
          class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="persistenceUnitManager" ref="defaultPersistenceManager"/>
        <property name="persistenceUnitName" value="fleaorder"/>
        <property name="persistenceProvider" ref="defaultPersistenceProvider"/>
        <property name="jpaVendorAdapter" ref="defaultVendorAdapter"/>
        <property name="jpaDialect" ref="defaultJpaDialect"/>
        <property name="jpaPropertyMap">
            <map>
                <entry key="eclipselink.weaving" value="false"/>
                <entry key="eclipselink.logging.thread" value="true"/>
            </map>
        </property>
    </bean>

    <bean id="fleaOrderTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="fleaOrderEntityManagerFactory"/>
    </bean>

    <tx:annotation-driven transaction-manager="fleaOrderTransactionManager"/>

    <!-- FleaOrder1 TransAction Manager JPA -->
    <!-- 省略 -->

    <!-- FleaOrder2 TransAction Manager JPA -->
    <!-- 省略 -->

</beans>
           

3. 實作講解

3.1 Flea自定義事物切面 -- FleaTransactionalAspect

Flea自定義事物切面,攔截由自定義事物注解标記的 Spring注入 的方法,

實作在方法調用之前開啟事物,調用成功後送出事物,出現異常復原事務。

Flea自定義事物注解主要标記在兩類方法上:

  • 一類方法是,AbstractFleaJPADAOImpl 的子類的增删改方法;這些方法一般在 某某資料源DAO層實作類 中,注解中需要指定事物名。
  • 另一類方法是,除了上一類方法的其他 Spring注入 的方法上;需要特别注意的是,自定義事物注解上不僅需要指定事物名、而且還需要指定持久化單元名;

如果存在分庫的場景,在調用之前,需要設定目前線程下的分庫序列值。

// 設定目前線程下的分庫序列值
 	FleaLibUtil.setSplitLibSeqValue("SEQ", "123123123");
  	// 調用自定義事物注解标記的方法
           

下面我貼出Flea自定義事物切面的代碼,如下:

@Aspect
@Component
public class FleaTransactionalAspect {

    private static final String METHOD_NAME_GET_ENTITY_MANAGER = "getEntityManager";

    @Around("@annotation(com.huazie.fleaframework.db.jpa.transaction.FleaTransactional)")
    public Object invokeWithinTransaction(final ProceedingJoinPoint joinPoint) throws CommonException, FleaException, NoSuchMethodException {
        // 擷取目前連接配接點上的方法
        Method method = FleaAspectUtils.getTargetMethod(joinPoint);
        // 擷取目前連接配接點方法上的自定義Flea事物注解上對應的事物名稱
        String transactionName = FleaEntityManager.getTransactionName(method);
        // 擷取連接配接點方法簽名上的參數清單
        Object[] args = joinPoint.getArgs();
        // 擷取标記Flea事物注解的目标對象
        Object tObj = joinPoint.getTarget();

        // 擷取最後一個參數【實體對象】
        FleaEntity fleaEntity = null;
        if (ArrayUtils.isNotEmpty(args)) {
        	// 從最後一個參數中擷取 Flea實體對象
            fleaEntity = getFleaEntityFromLastParam(args);
        }

        EntityManager entityManager;

        // 标記Flea事物注解的目标對象 為 AbstractFleaJPADAOImpl 的子類
        if (ObjectUtils.isNotEmpty(fleaEntity) && tObj instanceof AbstractFleaJPADAOImpl) {
            // 擷取實體管理器
            entityManager = (EntityManager) ReflectUtils.invoke(tObj, METHOD_NAME_GET_ENTITY_MANAGER, fleaEntity, Object.class);
            // 擷取分表資訊
            SplitTable splitTable = fleaEntity.get(DBConstants.LibTableSplitConstants.SPLIT_TABLE, SplitTable.class);
            // 擷取分庫資訊
            SplitLib splitLib = fleaEntity.get(DBConstants.LibTableSplitConstants.SPLIT_LIB, SplitLib.class);
            if (ObjectUtils.isNotEmpty(splitTable)) {
                splitLib = splitTable.getSplitLib();
            }
            // 分庫場景
            if (ObjectUtils.isNotEmpty(splitLib) && splitLib.isExistSplitLib()) {
                transactionName = splitLib.getSplitLibTxName();
            }
        } else {
            // 擷取目前連接配接點方法上的自定義Flea事物注解上對應的持久化單元名
            String unitName = FleaEntityManager.getUnitName(method);
            // 擷取分庫對象
            SplitLib splitLib = FleaSplitUtils.getSplitLib(unitName, FleaLibUtil.getSplitLibSeqValues());
            // 分庫場景
            if (splitLib.isExistSplitLib()) {
                transactionName = splitLib.getSplitLibTxName();
                unitName = splitLib.getSplitLibName();
            }
            entityManager = FleaEntityManager.getEntityManager(unitName, transactionName);
        }

        // 根據事物名,擷取配置的事物管理者
        PlatformTransactionManager transactionManager = (PlatformTransactionManager) FleaApplicationContext.getBean(transactionName);
        // 事物名【{0}】非法,請檢查!
        ObjectUtils.checkEmpty(transactionManager, DaoException.class, "ERROR-DB-DAO0000000015", transactionName);
        // 建立事物模闆對象,用于處理事務生命周期和可能的異常
        FleaTransactionTemplate transactionTemplate = new FleaTransactionTemplate(transactionManager, entityManager);
        return transactionTemplate.execute(new TransactionCallback<Object>() {
            @Override
            public Object doInTransaction(TransactionStatus status) {
                try {
                    return joinPoint.proceed();
                }  catch (Throwable throwable) {
                    ExceptionUtils.throwFleaException(FleaDBException.class, "Proceed with the next advice or target method invocation occurs exception : \n", throwable);
                }
                return null;
            }
        });
    }
}
           

在上述代碼中,事物名 和 實體管理器 的擷取是重點,因Flea自定義事物注解标記在兩類不同的方法上,這兩者的擷取也不一樣。通過事物名可直接從Spring配置中擷取定義的事物管理器,事物名對應着spring配置中

transaction-manager

對應的屬性值,詳見 2.4中 fleaorder-spring.xml 。

最後使用 Flea事物模闆,來實作标記

@FleaTransactional

的方法調用之前開啟事物,調用成功後送出事物,出現異常復原事物。

3.2 Flea事物模闆 -- FleaTransactionTemplate

Flea事物模闆,參考 Spring 的 TransactionTemplate,它是簡化程式化事務劃分和事務異常處理的模闆類。其核心方法是

execute

, 參數是實作事物回調接口的事務代碼。此模闆收斂了處理事務生命周期和可能的異常的邏輯,是以事物回調接口的實作和調用代碼都不需要顯式處理事務。

下面将貼出其核心方法

execute

,如下:

@Override
    public <T> T execute(TransactionCallback<T> action) throws TransactionException {
        if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
            return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
        } else {
            // 開啟Flea自定義事物
            TransactionStatus status = FleaJPASplitHelper.getHandler().getTransaction(this, transactionManager, entityManager);
            T result;
            try {
                result = action.doInTransaction(status);
            } catch (RuntimeException | Error ex) {
                rollbackOnException(status, ex);
                throw ex;
            } catch (Throwable ex) {
                rollbackOnException(status, ex);
                throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
            }
            this.transactionManager.commit(status);
            return result;
        }
    }

    /**
     * 執行復原,正确處理復原異常。
     */
    private void rollbackOnException(TransactionStatus status, Throwable ex) throws TransactionException {
        try {
            this.transactionManager.rollback(status);
        } catch (TransactionSystemException ex2) {
            ex2.initApplicationException(ex);
            throw ex2;
        } catch (RuntimeException ex2) {
            throw ex2;
        } catch (Error err) {
            throw err;
        }
    }
           

3.3 Flea實體管理器 -- FleaEntityManager

Flea 實體管理器工具類,提供了擷取持久化上下文互動的實體管理器接口、持久化單元名、事物名、分表資訊、各持久化上下文互動接口的靜态方法【如: getFleaNextValue,find,remove,merge,persist,flush】。

下面我們來看下整體的代碼:

public class FleaEntityManager {

    private static final ConcurrentMap<String, EntityManager> entityManagerMap = new ConcurrentHashMap<>();

    private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("EntityManager resources");

    private FleaEntityManager() {
    }

    /**
     * 擷取指定場景下的實體管理類
     */
    public static EntityManager getEntityManager(String unitName, String transactionName) throws CommonException {

        StringUtils.checkBlank(unitName, DaoException.class, "ERROR-DB-DAO0000000002", "libName");
        StringUtils.checkBlank(transactionName, DaoException.class, "ERROR-DB-DAO0000000002", "transactionName");

        if (!entityManagerMap.containsKey(unitName)) {
            synchronized (entityManagerMap) {
                if (!entityManagerMap.containsKey(unitName)) {
                    // 根據事物名,擷取配置的事物管理者
                    JpaTransactionManager manger = (JpaTransactionManager) FleaApplicationContext.getBean(transactionName);
                    // 事物名【{0}】非法,請檢查!
                    ObjectUtils.checkEmpty(manger, DaoException.class, "ERROR-DB-DAO0000000015", transactionName);
                    // 擷取實體管理者工廠類
                    EntityManagerFactory entityManagerFactory = manger.getEntityManagerFactory();
                    // 建立實體管理者
                    EntityManager entityManager = SharedEntityManagerCreator.createSharedEntityManager(entityManagerFactory);
                    entityManagerMap.put(unitName, entityManager);
                }
            }
        }
        return entityManagerMap.get(unitName);
    }

    /**
     * 從指定類的成員變量上,擷取持久化單元名稱。在 <b> flea-db </b> 子產品中,
     * 該名稱一般定義在 {@code AbstractFleaJPADAOImpl} 的子類的成員變量上,由 注解
     * {@code PersistenceContext} 或 注解 {@code FleaPersistenceContext} 進行辨別。
     */
    public static String getPersistenceUnitName(Class<?> daoImplClazz) {
        // 省略。。
    }

    /**
     * 從指定類的第一個成員方法上,擷取事物名。在 <b> flea-db </b> 子產品中,
     * 該名稱一般定義在 {@code AbstractFleaJPADAOImpl} 的子類的成員方法上,
     * 由注解 {@code Transactional}或{@code FleaTransactional} 進行辨別。
     */
    public static String getTransactionName(Class<?> daoImplClazz) {
        // 省略。。
    }

    /**
     * 從指定類的成員方法上,擷取事物名。在 <b> flea-db </b> 子產品中,
     * 該名稱一般定義在 {@code AbstractFleaJPADAOImpl} 的子類的成員方法上,
     * 由注解 {@code Transactional}或{@code FleaTransactional} 進行辨別。
     */
    public static String getTransactionName(Method method) {
        // 省略。。
    }

    /**
     * 從指定類的成員方法上,擷取持久化單元名。在 <b> flea-db </b> 子產品中,
     * 該名稱定義在注解{@code FleaTransactional} 中,用于啟動自定的事物。
     */
    public static String getUnitName(Method method) {
        // 省略。。
    }

    /**
     * 根據實體對象,擷取實體對應的分表資訊
     */
    public static SplitTable getSplitTable(Object entity) throws CommonException {
        // 省略。。
    }

    /**
     * 傳回綁定到目前線程的所有資源
     */
    public static Map<Object, Object> getResourceMap() {
        // 省略。。
    }

    /**
     * 檢查是否存在綁定到目前線程的給定鍵的資源。
     */
    public static boolean hasResource(Object key) {
        // 省略。。
    }

    /**
     * 檢索綁定到目前線程的給定鍵的資源。
     */
    public static Object getResource(Object key) {
        // 省略。。
    }

    /**
     * 實際檢查給定鍵綁定的資源的值。
     */
    private static Object doGetResource(Object actualKey) {
        // 省略。。
    }

    /**
     * 将給定鍵的給定資源綁定到目前線程。
     */
    public static void bindResource(Object key, Object value) throws IllegalStateException {
        // 省略。。
    }

    /**
     * 從目前線程解除給定鍵的資源綁定。
     */
    public static Object unbindResource(Object key) throws IllegalStateException {
        // 省略。。。
    }

    /**
     * 從目前線程解除給定鍵的資源綁定。
     */
    public static Object unbindResourceIfPossible(Object key) {
        return doUnbindResource(key);
    }

    /**
     * 實際删除為給定鍵綁定的資源的值。
     */
    private static Object doUnbindResource(Object actualKey) {
        // 省略。。
    }

    /**
     * 擷取下一個主鍵值
     */
    public static <T> Number getFleaNextValue(EntityManager entityManager, Class<T> entityClass, T entity) {
        return FleaJPASplitHelper.getHandler().getNextValue(entityManager, entityClass, entity);
    }

    /**
     * 根據主鍵查找表資料
     */
    public static <T> T find(EntityManager entityManager, Object primaryKey, Class<T> entityClass, T entity) {
        return FleaJPASplitHelper.getHandler().find(entityManager, primaryKey, entityClass, entity);
    }

    /**
     * 删除實體類對應的一條資料
     */
    public static <T> boolean remove(EntityManager entityManager, T entity) {
        return FleaJPASplitHelper.getHandler().remove(entityManager, entity);
    }

    /**
     * 将給定實體的狀态合并(即更新)到目前持久化上下文中。
     * <p> 注意:調用該方法後,待修改的資料還未更新到資料庫中。
     */
    public static <T> T merge(EntityManager entityManager, T entity) {
        return FleaJPASplitHelper.getHandler().merge(entityManager, entity);
    }

    /**
     * 将實體類添加到持久化上下文中,并管理該實體類
     * <p> 注意:調用該方法後,待儲存的資料還未添加到資料庫中。
     */
    public static <T> void persist(EntityManager entityManager, T entity) {
        FleaJPASplitHelper.getHandler().persist(entityManager, entity);
    }

    /**
     * 将持久化上下文同步到底層資料庫。
     */
    public static <T> void flush(EntityManager entityManager, T entity) {
        FleaJPASplitHelper.getHandler().flush(entityManager, entity);
    }
}

           

3.4 Flea JPA分庫分表處理接口 -- IFleaJPASplitHandler

從上面 3.3中,我們可以看到 Flea實體管理器中的各持久化上下文互動接口的靜态方法【如: getFleaNextValue,find,remove,merge,persist,flush】都是調用FleaJPASplitHelper.getHandler() 的對應方法實作的,也就是 IFleaJPASplitHandler 的對應方法。

Flea JPA 分庫分表處理者接口,包含分庫分表相關的處理接口方法、增删改查的資料操作接口方法。

下面我們來看看 Flea JPA分庫分表處理接口 都有哪些處理方法?

public interface IFleaJPASplitHandler {

    /**
     * 使用标準化查詢時,處理分庫分表資訊
     */
    void handle(FleaJPAQuery query, Object entity) throws CommonException;

    /**
     * 使用标準化查詢時,存在分表場景,具體的JPA查詢對象重新設定持久化資訊
     */
    void handle(FleaJPAQuery query, TypedQuery typedQuery) throws CommonException;

    /**
     * 使用持久化接口時,處理分庫分表資訊
     */
    EntityManager handle(EntityManager entityManager, Object entity, boolean flag) throws CommonException;

    /**
     * 分表場景下,取事物管理器中的實體管理器工廠類,并将其作為鍵,
     * 綁定實體管理器對應的包裝類資源到目前線程。以支援JPA的增删改操作。
     */
    TransactionStatus getTransaction(TransactionDefinition definition, PlatformTransactionManager transactionManager, EntityManager entityManager);

    /**
     * 擷取下一個主鍵值
     */
    <T> Number getNextValue(EntityManager entityManager, Class<T> entityClass, T entity);

    /**
     * 根據主鍵查找表資料
     */
    <T> T find(EntityManager entityManager, Object primaryKey, Class<T> entityClass, T entity);

    /**
     * 删除給定的實體資料
     */
    <T> boolean remove(EntityManager entityManager, T entity);

    /**
     * 将給定實體的狀态合并(即更新)到目前持久化上下文中。
     * <p> 注意:調用該方法後,待修改的資料還未更新到資料庫中。
     */
    <T> T merge(EntityManager entityManager, T entity);

    /**
     * 将實體類添加到持久化上下文中,并管理該實體類
     * <p> 注意:調用該方法後,待儲存的資料還未添加到資料庫中。
     */
    <T> void persist(EntityManager entityManager, T entity);

    /**
     * 将持久化上下文同步到底層資料庫。
     */
    <T> void flush(EntityManager entityManager, T entity);
}
           

3.5 EclipseLink分庫分表處理實作 -- EclipseLinkLibTableSplitHandler

EclipseLink 分庫分表處理者,由自定義的實體管理器實作類處理增删改查等操作。

在講解EclipseLink分庫分表處理者之前,我們先了解下其父類 FleaLibTableSplitHandler,該類實作了通用的分庫分表處理 和 增删改查操作,同時定義了抽象的内部方法由子類實作具體的操作。

下面我們來看一下具體的代碼,如下:

public abstract class FleaLibTableSplitHandler implements IFleaJPASplitHandler {

    @Override
    public void handle(FleaJPAQuery query, Object entity) throws CommonException {

        if (ObjectUtils.isEmpty(query) || ObjectUtils.isEmpty(entity) || !(entity instanceof FleaEntity)) {
            return;
        }

        FleaEntity fleaEntity = (FleaEntity) entity;

        // 擷取分表資訊(包括模闆表名 和 分表名 【如果存在分表傳回】)
        SplitTable splitTable = FleaEntityManager.getSplitTable(entity);

        SplitLib splitLib;
        // 存在分表,需要查詢指定分表
        if (splitTable.isExistSplitTable()) {
            splitLib = splitTable.getSplitLib();
            // 設定分表資訊
            fleaEntity.put(DBConstants.LibTableSplitConstants.SPLIT_TABLE, splitTable);
        } else {
            // 擷取預設庫名,這裡的對象池名為持久化單元名【通常對應着庫名】
            String libName = query.getPoolName();
            if (ObjectUtils.isEmpty(libName)) {
                return;
            }
            splitLib = FleaSplitUtils.getSplitLib(libName, FleaLibUtil.getSplitLibSeqValues());
        }

        // 分庫場景,重新擷取對應分庫下的實體管理類
        EntityManager splitEntityManager = handleInner(splitLib);

        EntityManager entityManager;
        if (ObjectUtils.isEmpty(splitEntityManager)) {
            entityManager = query.getEntityManager();
        } else {
            entityManager = splitEntityManager;
        }

        // 分表場景 或 分表場景 或 目前線程存在自定義的Flea實體管理器實作, 直接擷取
        if (isFleaEntityManagerImpl(entityManager, splitTable, splitLib)) {
            splitEntityManager = getFleaEntityMangerImpl(entityManager);
        }

        if (ObjectUtils.isNotEmpty(splitEntityManager)) {
            // 重新初始化Flea JPA查詢對象
            query.init(splitEntityManager, query.getSourceClazz(), query.getResultClazz());
        }
    }

    @Override
    public void handle(FleaJPAQuery query, TypedQuery typedQuery) {
        SplitTable splitTable = getSplitTableFromEntity(query.getEntity());
        // 分表資訊為空或不存在分表,預設不處理
        if (!splitTable.isExistSplitTable()) {
            return;
        }
        // 處理類型查詢接口的分表資訊
        handleInner(query, typedQuery, splitTable);
    }

    @Override
    public EntityManager handle(EntityManager entityManager, Object entity, boolean flag) throws CommonException {

        if (ObjectUtils.isEmpty(entityManager) || ObjectUtils.isEmpty(entity) || !(entity instanceof FleaEntity)) {
            return entityManager;
        }

        // 擷取分表資訊(包括模闆表名 和 分表名 【如果存在分表傳回】)
        SplitTable splitTable = FleaEntityManager.getSplitTable(entity);

        FleaEntity fleaEntity = (FleaEntity) entity;

        SplitLib splitLib;
        // 存在分表,則需要操作具體分表
        if (splitTable.isExistSplitTable()) {
            splitLib = splitTable.getSplitLib();
            // 設定分表資訊
            fleaEntity.put(DBConstants.LibTableSplitConstants.SPLIT_TABLE, splitTable);
        } else {
            // 擷取預設庫名
            String libName = fleaEntity.get(DBConstants.LibTableSplitConstants.FLEA_LIB_NAME, String.class);
            if (ObjectUtils.isEmpty(libName)) {
                return entityManager;
            }
            splitLib = FleaSplitUtils.getSplitLib(libName, FleaLibUtil.getSplitLibSeqValues());
            // 設定分庫資訊
            fleaEntity.put(DBConstants.LibTableSplitConstants.SPLIT_LIB, splitLib);
        }

        // 如果是getFleaNextValue擷取實體管理器,并且主鍵生成器表在模闆庫中,直接傳回實體管理器
        if (flag && splitTable.isGeneratorFlag()) {
            return entityManager;
        }

        // 分庫場景,重新擷取對應分庫下的實體管理類
        EntityManager splitEntityManager = handleInner(splitLib);
        if (ObjectUtils.isNotEmpty(splitEntityManager)) {
            // 分庫場景,重新初始化實體管理類
            entityManager = splitEntityManager;
        }
        return entityManager;
    }

    /**
     * 分庫場景,重新擷取對應分庫下的實體管理類
     */
    private EntityManager handleInner(SplitLib splitLib) throws CommonException {
        EntityManager entityManager = null;
        if (ObjectUtils.isNotEmpty(splitLib) && splitLib.isExistSplitLib()) {
            String unitName = splitLib.getSplitLibName();
            String transactionName = splitLib.getSplitLibTxName();
            entityManager = FleaEntityManager.getEntityManager(unitName, transactionName);
        }
        return entityManager;
    }

    @Override
    public TransactionStatus getTransaction(TransactionDefinition definition, PlatformTransactionManager transactionManager, EntityManager entityManager) {
        // JPA事物管理器
        JpaTransactionManager jpaTransactionManager = (JpaTransactionManager) transactionManager;
        Object obj = TransactionSynchronizationManager.getResource(jpaTransactionManager.getEntityManagerFactory());
        if (ObjectUtils.isEmpty(obj)) {
            // 擷取Flea實體管理器實作類
            EntityManager fleaEntityManagerImpl = getFleaEntityMangerImpl(entityManager);
            // 建立實體管理器包裝類資源,持有Flea實體管理器實作類
            EntityManagerHolder entityManagerHolder = new EntityManagerHolder(fleaEntityManagerImpl);
            // 将實體管理器工廠類的實體管理器包裝類資源綁定到目前線程
            TransactionSynchronizationManager.bindResource(jpaTransactionManager.getEntityManagerFactory(), entityManagerHolder);
        }
        // 擷取事物狀态對象,并開啟事物
        return jpaTransactionManager.getTransaction(definition);
    }

    @Override
    public <T> Number getNextValue(EntityManager entityManager, Class<T> entityClass, T entity) {
        SplitTable splitTable = getSplitTableFromEntity(entity);
        return getNextValueInner(entityManager, entityClass, splitTable);
    }

    @Override
    public <T> T find(EntityManager entityManager, Object primaryKey, Class<T> entityClass, T entity) {
        SplitTable splitTable = getSplitTableFromEntity(entity);
        SplitLib splitLib = getSplitLibFromEntity(entity);

        // 分表場景 或 分表場景 或 目前線程存在自定義的Flea實體管理器實作, 直接擷取
        T t;
        if (isFleaEntityManagerImpl(entityManager, splitTable, splitLib)) {
            t = findInner(entityManager, primaryKey, entityClass, splitTable);
        } else {
            t =  entityManager.find(entityClass, primaryKey);
        }
        return t;
    }

    @Override
    public <T> boolean remove(EntityManager entityManager, T entity) {
        SplitTable splitTable = getSplitTableFromEntity(entity);
        SplitLib splitLib = getSplitLibFromEntity(entity);

        // 分表場景 或 分表場景 或 目前線程存在自定義的Flea實體管理器實作, 直接擷取
        if (isFleaEntityManagerImpl(entityManager, splitTable, splitLib)) {
            // 使用自定義的Flea實體管理器實作,删除實體資料
            removeInner(entityManager, entity);
        } else {
            if (!entityManager.contains(entity)) {
                entity = registerObject(entityManager, entity);
            }
            entityManager.remove(entity);
        }
        return true;
    }

    @Override
    public <T> T merge(EntityManager entityManager, T entity) {
        SplitTable splitTable = getSplitTableFromEntity(entity);
        SplitLib splitLib = getSplitLibFromEntity(entity);

        // 分表場景 或 分表場景 或 目前線程存在自定義的Flea實體管理器實作, 直接擷取
        if (isFleaEntityManagerImpl(entityManager, splitTable, splitLib)) {
            // 使用自定義的Flea實體管理器實作,合并實體資料的狀态至目前持久化上下文中
            return mergeInner(entityManager, entity);
        } else {
            return entityManager.merge(entity);
        }
    }

    @Override
    public <T> void persist(EntityManager entityManager, T entity) {
        SplitTable splitTable = getSplitTableFromEntity(entity);
        SplitLib splitLib = getSplitLibFromEntity(entity);

        // 分表場景 或 分表場景 或 目前線程存在自定義的Flea實體管理器實作, 直接擷取
        if (isFleaEntityManagerImpl(entityManager, splitTable, splitLib)) {
            // 使用自定義的Flea實體管理器實作,向工作單元注冊對象
            persistInner(entityManager, entity);
        } else {
            entityManager.persist(entity);
        }
    }

    @Override
    public <T> void flush(EntityManager entityManager, T entity) {
        SplitTable splitTable = getSplitTableFromEntity(entity);
        SplitLib splitLib = getSplitLibFromEntity(entity);

        // 分表場景 或 分表場景 或 目前線程存在自定義的Flea實體管理器實作, 直接擷取
        if (isFleaEntityManagerImpl(entityManager, splitTable, splitLib)) {
            // 使用自定義的Flea實體管理器實作,将持久性上下文同步到基礎資料庫。
            flushInner(entityManager);
        } else {
            entityManager.flush();
        }
    }

    /**
     * 是否使用自定義的Flea實體管理器實作
     */
    private boolean isFleaEntityManagerImpl(EntityManager entityManager, SplitTable splitTable, SplitLib splitLib) {
        return (splitTable.isExistSplitTable() || splitLib.isExistSplitLib() || FleaEntityManager.hasResource(entityManager.getEntityManagerFactory()));
    }

    /**
     * 從實體類對象中擷取分表資訊
     */
    private SplitTable getSplitTableFromEntity(Object entity) {
        SplitTable splitTable = null;
        if (ObjectUtils.isNotEmpty(entity) && (entity instanceof FleaEntity)) {
            // 擷取分表資訊
            FleaEntity fleaEntity = (FleaEntity) entity;
            splitTable = fleaEntity.get(DBConstants.LibTableSplitConstants.SPLIT_TABLE, SplitTable.class);
        }
        if (ObjectUtils.isEmpty(splitTable)) {
            splitTable = new SplitTable();
            splitTable.setExistSplitTable(false);
        }
        return splitTable;
    }

    /**
     * 從實體類對象中擷取分庫資訊
     */
    private SplitLib getSplitLibFromEntity(Object entity) {
        SplitLib splitLib = null;
        if (ObjectUtils.isNotEmpty(entity) && (entity instanceof FleaEntity)) {
            // 擷取分庫資訊
            FleaEntity fleaEntity = (FleaEntity) entity;
            splitLib = fleaEntity.get(DBConstants.LibTableSplitConstants.SPLIT_LIB, SplitLib.class);
        }
        if (ObjectUtils.isEmpty(splitLib)) {
            splitLib = new SplitLib();
            splitLib.setExistSplitLib(false);
        }
        return splitLib;
    }

    /**
     * 擷取自定義的Flea實體管理器實作
     */
    protected abstract EntityManager getFleaEntityMangerImpl(EntityManager entityManager);

    /**
     * 處理類型查詢接口的分表資訊
     */
    protected abstract void handleInner(FleaJPAQuery query, TypedQuery typedQuery, SplitTable splitTable);

    /**
     * 自定義的實體管理器實作,擷取下一個主鍵值
     */
    protected abstract <T> Number getNextValueInner(EntityManager entityManager, Class<T> entityClass, SplitTable splitTable);

    /**
     * 使用自定義的實體管理器實作,根據主鍵查詢實體資料
     */
    protected abstract <T> T findInner(EntityManager entityManager, Object primaryKey, Class<T> entityClass, SplitTable splitTable);

    /**
     * 使用自定義的實體管理器實作,删除實體資料
     */
    protected abstract <T> void removeInner(EntityManager entityManager, T entity);

    /**
     * 使用自定義的實體管理器實作,合并實體資料的狀态至目前持久化上下文中
     */
    protected abstract <T> T mergeInner(EntityManager entityManager, T entity);

    /**
     * 使用自定義的實體管理器實作,向工作單元注冊對象
     */
    protected abstract <T> void persistInner(EntityManager entityManager, T entity);

    /**
     * 使用自定義的實體管理器實作,将持久化上下文同步到底層資料庫。
     */
    protected abstract void flushInner(EntityManager entityManager);

    /**
     * 注冊實體對象
     */
    protected abstract <T> T registerObject(EntityManager entityManager, T entity);
}
           

好,上面已經基本實作分表處理者的各項接口方法,剩下一些inner方法,需要由特定的JPA實作來定制化,本例中是EclipseLink。

下面我們來看看相關代碼,如下:

public class EclipseLinkLibTableSplitHandler extends FleaLibTableSplitHandler {

    @Override
    protected void handleInner(FleaJPAQuery query, TypedQuery typedQuery, SplitTable splitTable) {
        // 擷取實體類型
        EntityType entityType = query.getRoot().getModel();
        // 擷取實體類對應的持久化資訊
        ClassDescriptor classDescriptor = ((EntityTypeImpl) entityType).getDescriptor();
        // 分表場景,這裡的entityManager已經重新設定為 FleaEntityManagerImpl
        AbstractSession abstractSession = ((FleaEntityManagerImpl) query.getEntityManager()).getDatabaseSession();
        classDescriptor = ClassDescriptorUtils.getSplitDescriptor(classDescriptor, abstractSession, splitTable);
        // 擷取内部DatabaseQuery對象
        ReadAllQuery readAllQuery = typedQuery.unwrap(ReadAllQuery.class);
        // 重新設定實體類的描述符資訊
        readAllQuery.setDescriptor(classDescriptor);
        // 重新設定實體類的描述符資訊
        readAllQuery.getExpressionBuilder().setQueryClassAndDescriptor(classDescriptor.getJavaClass(), classDescriptor);
    }

    @Override
    protected EntityManager getFleaEntityMangerImpl(EntityManager entityManager) {
        return FleaEntityManagerImpl.getFleaEntityManagerImpl(entityManager);
    }

    @Override
    protected <T> Number getNextValueInner(EntityManager entityManager, Class<T> entityClass, SplitTable splitTable) {
        return FleaEntityManagerImpl.getFleaEntityManagerImpl(entityManager).getNextValue(entityClass, splitTable);
    }

    @Override
    protected <T> T findInner(EntityManager entityManager, Object primaryKey, Class<T> entityClass, SplitTable splitTable) {
        return FleaEntityManagerImpl.getFleaEntityManagerImpl(entityManager).find(entityClass, primaryKey, splitTable);
    }

    @Override
    protected <T> void removeInner(EntityManager entityManager, T entity) {
        FleaEntityManagerImpl.getFleaEntityManagerImpl(entityManager).remove(entity);
    }

    @Override
    protected <T> T mergeInner(EntityManager entityManager, T entity) {
        return FleaEntityManagerImpl.getFleaEntityManagerImpl(entityManager).merge(entity);
    }

    @Override
    protected <T> void persistInner(EntityManager entityManager, T entity) {
        FleaEntityManagerImpl.getFleaEntityManagerImpl(entityManager).persist(entity);
    }

    @Override
    protected void flushInner(EntityManager entityManager) {
        FleaEntityManagerImpl.getFleaEntityManagerImpl(entityManager).flush();
    }

    @Override
    @SuppressWarnings("unchecked")
    protected <T> T registerObject(EntityManager entityManager, T entity) {
        // 如果已經注冊過了,直接傳回待注冊對象
        if (entityManager.contains(entity) || ObjectUtils.isEmpty(entity)) {
            return entity;
        }

        return (T) entityManager.unwrap(UnitOfWork.class).registerObject(entity);
    }
}

           

上面具體的inner方法實作,我們可以看到都使用了 FleaEntityManagerImpl ,這就是下面将要介紹的 Flea實體管理器EclipseLink版實作。

3.6 Flea實體管理器EclipseLink版實作 -- FleaEntityManagerImpl

Flea實體管理器EclipseLink版實作,繼承了 EclipseLink 的 EntityManagerImpl,它需要有 一個 EntityManager 入參來構造。

下面我們來看一下相關代碼,如下:

public final class FleaEntityManagerImpl extends EntityManagerImpl {

    /**
     * 擷取指定JPA實體管理器工廠類對應的自定義的Flea實體管理器實作
     */
    public static FleaEntityManagerImpl getFleaEntityManagerImpl(EntityManager entityManager) {
        EntityManagerFactory entityManagerFactory = entityManager.getEntityManagerFactory();
        FleaEntityManagerImpl fleaEntityManagerImpl = (FleaEntityManagerImpl) FleaEntityManager.getResource(entityManagerFactory);
        if (ObjectUtils.isEmpty(fleaEntityManagerImpl)) {
            fleaEntityManagerImpl = new FleaEntityManagerImpl(entityManager);
            FleaEntityManager.bindResource(entityManagerFactory, fleaEntityManagerImpl);
        }
        return fleaEntityManagerImpl;
    }

    /**
     * 通過EntityManagerImpl建構FleaEntityManagerImpl
     */
    private FleaEntityManagerImpl(EntityManager entityManager) {
        super(entityManager.getEntityManagerFactory().unwrap(JpaEntityManagerFactory.class).unwrap(), entityManager.getProperties(), null);
    }

    /**
     * 分表場景下,根據主鍵查找實體資料
     */
    public <T> T find(Class<T> entityClass, Object primaryKey, SplitTable splitTable) {
        return find(entityClass, primaryKey, null, getQueryHints(entityClass, OperationType.FIND), splitTable);
    }

    /**
     * 分表場景下,根據主鍵查找實體資料
     */
    public <T> T find(Class<T> entityClass, Object primaryKey, Map<String, Object> properties, SplitTable splitTable) {
        return find(entityClass, primaryKey, null, properties, splitTable);
    }

    /**
     * 分表場景下,根據主鍵查找實體資料
     */
    public <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockMode, SplitTable splitTable) {
        return find(entityClass, primaryKey, lockMode, getQueryHints(entityClass, OperationType.FIND), splitTable);
    }

    /**
     * 分表場景下,根據主鍵查找實體資料
     */
    @SuppressWarnings({"unchecked"})
    public <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockMode, Map<String, Object> properties, SplitTable splitTable) {
        try {
            verifyOpen();
            if (ObjectUtils.isNotEmpty(lockMode) && !lockMode.equals(LockModeType.NONE)) {
                checkForTransaction(true);
            }
            AbstractSession session = this.databaseSession;
            ClassDescriptor descriptor = session.getDescriptor(entityClass);
            if (descriptor == null || descriptor.isDescriptorTypeAggregate()) {
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("unknown_bean_class", new Object[]{entityClass}));
            }
            if (!descriptor.shouldBeReadOnly() || !descriptor.isSharedIsolation()) {
                session = (AbstractSession) getActiveSession();
            } else {
                session = (AbstractSession) getReadOnlySession();
            }
            // 確定從目前會話中擷取實體類的持久化資訊描述符
            if (descriptor.hasTablePerMultitenantPolicy()) {
                descriptor = session.getDescriptor(entityClass);
            }
            // 擷取分表對應的實體類的持久化資訊描述符
            descriptor = ClassDescriptorUtils.getSplitDescriptor(descriptor, session, splitTable);
            // 複用實體管理器實作類的内部方法
            return (T) findInternal(descriptor, session, primaryKey, lockMode, properties);
        } catch (LockTimeoutException e) {
            throw e;
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }

    /**
     * 擷取指定實體類對應的下一個主鍵值
     */
    public <T> Number getNextValue(Class<T> entityClass, SplitTable splitTable) {
        // 擷取實體類的持久化資訊描述符
        AbstractSession session = this.databaseSession;
        ClassDescriptor descriptor = session.getDescriptor(entityClass);
        if (ObjectUtils.isEmpty(descriptor)) {
            throw new IllegalArgumentException(ExceptionLocalization.buildMessage("unknown_bean_class", new Object[]{entityClass}));
        }
        // 擷取分表對應的實體類的持久化資訊描述符
        descriptor = ClassDescriptorUtils.getSplitDescriptor(descriptor, session, splitTable);

        Number nextValue;
        if (ObjectUtils.isNotEmpty(splitTable) && splitTable.isExistSplitTablePkColumn()) {
            String sequenceName = splitTable.getSplitTablePkColumnValue();
            Sequencing sequencing = session.getSequencing();
            FleaSequencingManager fleaSequencingManager = FleaSequencingManager.getFleaSequencingManager(sequenceName, sequencing, descriptor);
            nextValue = fleaSequencingManager.getNextValue(sequenceName);
        } else {
            nextValue = session.getNextSequenceNumberValue(entityClass);
        }
        return nextValue;
    }

    @Override
    public RepeatableWriteUnitOfWork getActivePersistenceContext(Object txn) {
        // 覆寫,詳細請檢視GitHub
    }
}
           

4. 接入講解

4.1 資料庫和表

模闆庫

flea_id_generator 為 主鍵生成器表,可檢視筆者的這篇博文《flea-db使用之主鍵生成器表介紹》,不再贅述。

flea-db使用之JPA分庫分表實作JPA分庫分表實作

分庫1

flea-db使用之JPA分庫分表實作JPA分庫分表實作

分庫2

flea-db使用之JPA分庫分表實作JPA分庫分表實作

具體的SQL檔案,請參考 fleaorder.sql,fleaorder1.sql,fleaorder2.sql

4.2 各實體類

實體表名 描述
OldOrder 舊訂單
OldOrderAttr 舊訂單屬性
Order 訂單
OrderAttr 訂單屬性
flea-db使用之JPA分庫分表實作JPA分庫分表實作

4.3 FleaOrder資料源DAO層父類 -- FleaOrderDAOImpl

該類繼承 AbstractFleaJPADAOImpl,成員變量 entityManager ,由 PersistenceContext 注解标記 持久化單元名,這裡為模闆持久化單元名

public class FleaOrderDAOImpl<T> extends AbstractFleaJPADAOImpl<T> {

    @PersistenceContext(unitName="fleaorder")
    private EntityManager entityManager;

    @Override
    @FleaTransactional("fleaOrderTransactionManager")
    public Number getFleaNextValue(T entity) throws CommonException {
        return super.getFleaNextValue(entity);
    }

    @Override
    @FleaTransactional(value = "fleaOrderTransactionManager", unitName = "fleaorder")
    public boolean remove(long entityId) throws CommonException {
        return super.remove(entityId);
    }

    @Override
    @FleaTransactional(value = "fleaOrderTransactionManager", unitName = "fleaorder")
    public boolean remove(String entityId) throws CommonException {
        return super.remove(entityId);
    }

    @Override
    @FleaTransactional("fleaOrderTransactionManager")
    public boolean remove(T entity) throws CommonException {
        return super.remove(entity);
    }

    @Override
    @FleaTransactional("fleaOrderTransactionManager")
    public boolean remove(long entityId, T entity) throws CommonException {
        return super.remove(entityId, entity);
    }

    @Override
    @FleaTransactional("fleaOrderTransactionManager")
    public boolean remove(String entityId, T entity) throws CommonException {
        return super.remove(entityId, entity);
    }

    @Override
    @FleaTransactional("fleaOrderTransactionManager")
    public T update(T entity) throws CommonException {
        return super.update(entity);
    }

    @Override
    @FleaTransactional("fleaOrderTransactionManager")
    public List<T> batchUpdate(List<T> entities) throws CommonException {
        return super.batchUpdate(entities);
    }

    @Override
    @FleaTransactional("fleaOrderTransactionManager")
    public void save(T entity) throws CommonException {
        super.save(entity);
    }

    @Override
    @FleaTransactional("fleaOrderTransactionManager")
    public void batchSave(List<T> entities) throws CommonException {
        super.batchSave(entities);
    }

    @Override
    @FleaTransactional("fleaOrderTransactionManager")
    public int insert(String relationId, T entity) throws CommonException {
        return super.insert(relationId, entity);
    }

    @Override
    @FleaTransactional("fleaOrderTransactionManager")
    public int update(String relationId, T entity) throws CommonException {
        return super.update(relationId, entity);
    }

    @Override
    @FleaTransactional("fleaOrderTransactionManager")
    public int delete(String relationId, T entity) throws CommonException {
        return super.delete(relationId, entity);
    }

    @Override
    protected EntityManager getEntityManager() {
        return entityManager;
    }

}
           

4.4 各實體的DAO層接口和實作

可至 GitHub 檢視如下 DAO層代碼:

flea-db使用之JPA分庫分表實作JPA分庫分表實作

4.5 各實體的SV層接口和實作

可至 GitHub 檢視如下 SV層代碼:

flea-db使用之JPA分庫分表實作JPA分庫分表實作

5. 單元測試

測試之前,先添加主鍵生成器表中的資料如下:

id_generator_key id_generator_value
pk_old_order 999999999
pk_order 999999999

5.1 分庫分表測試 -- OrderTest

下面我們來看下,分庫分表的新增、查詢、更新 和 查詢,代碼如下:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class OrderTest {

    private static final Logger LOGGER = FleaLoggerProxy.getProxyInstance(OrderTest.class);

    @Resource(name = "orderSV")
    private IOrderSV orderSV;

    @Test
    public void testInsertOrder() throws Exception {

        Order order = new Order();
        order.setOrderName("測試訂單");
        order.setOrderPrice(0L);
        order.setOrderState(0);
        order.setOrderDate(DateUtils.getCurrentTime());
		// 擷取下一個主鍵值
        Long orderId = (Long) orderSV.getFleaNextValue(null);
        order.setOrderId(orderId);

        orderSV.save(order);
    }

    @Test
    public void testQueryOrder() throws Exception {

        long orderId = 1000000000L;
        Order order = new Order();
        order.setOrderId(orderId);

        order = orderSV.query(orderId, order);

        LOGGER.debug("Order = {}", order);
    }

    @Test
    public void testUpdateOrder() throws Exception {

        long orderId = 1000000000L;
        Order order = new Order();
        order.setOrderId(orderId);

        Set<String> attrNames = new HashSet<>();
        // orderId 為實體類Order中的變量,實際對應表中 order_id 字段
        attrNames.add("orderId");
        List<Order> orderList = orderSV.query(attrNames, order);

        if (CollectionUtils.isNotEmpty(orderList)) {
            order = orderList.get(0);

            LOGGER.debug("Before : {}", order);

            order.setOrderName("修改訂單");
            order.setOrderPrice(100L);
            order.setOrderState(1);
			// 更新資料
            orderSV.update(order);
        }

        Order order1 = new Order();
        order1.setOrderId(orderId);

        order1 = orderSV.query(orderId, order1);

        LOGGER.debug("After : {}", order1);
    }

    @Test
    public void testDeleteOrder() throws Exception {
        long orderId = 1000000000L;
        Order order = new Order();
        order.setOrderId(orderId);

        Set<String> attrNames = new HashSet<>();
        attrNames.add("orderId");
        List<Order> orderList = orderSV.query(attrNames, order);

        if (CollectionUtils.isNotEmpty(orderList)) {
            Order order1 = orderList.get(0);
            LOGGER.error("Before : {}", order1);
			// 删除資料
            orderSV.remove(order1);
        }

        Order order2 = orderSV.query(orderId, order);
        LOGGER.error("After : {}", order2);
    }
}
           

5.2 分庫測試

如果隻分庫,不分表,需要再執行具體的增删改查之前,設定分庫序列值。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class OldOrderTest {

    private static final Logger LOGGER = FleaLoggerProxy.getProxyInstance(OldOrderTest.class);

    @Resource(name = "oldOrderSV")
    private IOldOrderSV oldOrderSV;

    @Test
    public void testInsertOldOrder() throws Exception {

        OldOrder oldOrder = new OldOrder();
        oldOrder.setOrderName("測試舊訂單1");
        oldOrder.setOrderPrice(200L);
        oldOrder.setOrderState(0);
        oldOrder.setOrderDate(DateUtils.getCurrentTime());
		// 擷取下一個主鍵值
        Long orderId = (Long) oldOrderSV.getFleaNextValue(null);
        oldOrder.setOrderId(orderId);

        // 設定分庫序列值
        FleaLibUtil.setSplitLibSeqValue("SEQ", orderId);

        oldOrderSV.save(oldOrder);
    }

    @Test
    public void testQueryOldOrder() throws Exception {

        long orderId = 1000000000L;
        OldOrder oldOrder = new OldOrder();
        //oldOrder.setOrderId(orderId);

        // 設定分庫序列值
        FleaLibUtil.setSplitLibSeqValue("SEQ", orderId);

        // 分庫場景,需要實體類,為了後續從中擷取預設庫名
        oldOrder = oldOrderSV.query(orderId, oldOrder);

        LOGGER.debug("Order = {}", oldOrder);
    }

    @Test
    public void testUpdateOldOrder() throws Exception {

        long orderId = 1000000000L;

        // 設定分庫序列值
        FleaLibUtil.setSplitLibSeqValue("SEQ", orderId);

        OldOrder oldOrder = new OldOrder();
        oldOrder.setOrderId(orderId);

        Set<String> attrNames = new HashSet<>();
        attrNames.add("orderId");
        List<OldOrder> oldOrderList = oldOrderSV.query(attrNames, oldOrder);

        if (CollectionUtils.isNotEmpty(oldOrderList)) {
            oldOrder = oldOrderList.get(0);

            LOGGER.debug("Before : {}", oldOrder);

            oldOrder.setOrderName("修改舊訂單1");
            oldOrder.setOrderPrice(200L);
            oldOrder.setOrderState(2);

            oldOrderSV.update(oldOrder);
        }

        OldOrder oldOrder1 = new OldOrder();
        //oldOrder1.setOrderId(orderId);

        oldOrder1 = oldOrderSV.query(orderId, oldOrder1);

        LOGGER.debug("After : {}", oldOrder1);
    }

    @Test
    public void testDeleteOldOrder() throws Exception {

        long orderId = 1000000000L;

        // 設定分庫序列值
        FleaLibUtil.setSplitLibSeqValue("SEQ", orderId);

        OldOrder oldOrder = new OldOrder();
        oldOrder.setOrderId(orderId);

        Set<String> attrNames = new HashSet<>();
        attrNames.add("orderId");
        List<OldOrder> orderList = oldOrderSV.query(attrNames, oldOrder);

        if (CollectionUtils.isNotEmpty(orderList)) {
            OldOrder oldOrder1 = orderList.get(0);
            LOGGER.error("Before : {}", oldOrder1);
            oldOrderSV.remove(oldOrder1);
        }

        OldOrder oldOrder2 = new OldOrder();
        oldOrder2 = oldOrderSV.query(orderId, oldOrder2);
        LOGGER.error("After : {}", oldOrder2);
    }

}
           

5.3 JPA事物示範

首先 我們先看下如何在 除了資料源DAO層實作類之外 的方法上使用自定的事物注解

@FleaTransactional

,可至GitHub檢視如下代碼 :

flea-db使用之JPA分庫分表實作JPA分庫分表實作

這裡貼出關鍵使用代碼如下:

@Override
    @FleaTransactional(value = "fleaOrderTransactionManager", unitName = "fleaorder")
    public void orderTransaction(Long orderId) throws CommonException {
    	// 省略。。。
    }
           
@Test
    public void testTransaction() throws Exception {

        long orderId = 1000000000L;

        // 設定分庫序列值
        FleaLibUtil.setSplitLibSeqValue("SEQ", orderId);

        fleaOrderModuleSV.orderTransaction(orderId);
    }
           

6. 總結

繼續閱讀