JPA分庫分表實作
在開始本篇的講解之前,我先來說下之前寫過的兩篇博文【現在已棄用】:
flea-frame-db使用之基于EntityManager實作JPA分表的資料庫操作【舊】
flea-frame-db使用之基于FleaJPAQuery實作JPA分表查詢【舊】
這兩篇都與分表相關,之是以被棄用,是因為在并發場景中這一版的分表存在問題。雖然并發場景有問題,但與之相關的分表配置、分表實作也确實為本篇的分庫分表提供了一些基礎能力,這些不能被忽視,将會在本篇中一一介紹。
經過重構之後,目前 flea-db 子產品的結構如下圖所示:

子產品 | 描述 |
---|---|
flea-db-common | 分庫配置、分表配置、SQL模闆配置、異常 和 工具類等代碼 |
flea-db-eclipselink | 基于EclipseLink版的JPA實作而定制化的代碼 |
flea-db-jdbc | 基于 JDBC 開發的通用代碼 |
flea-db-jpa | 基于 JPA 開發的通用代碼 |
1. 名詞解釋
名詞 | 解釋 |
---|---|
模闆庫名 | 用作模闆的資料庫名 |
模闆庫持久化單元名 | 模闆庫下的持久化單元名,一般和模闆庫相同 |
模闆庫事物名 | 模闆庫下的事物管理器名 ,分庫配置中可檢視 标簽 |
分庫名 | 以模闆庫名為基礎,根據分庫規則得到的資料庫名 |
分庫持久化單元名 | 以模闆庫持久化單元名為基礎,根據分庫規則得到的持久化單元名,一般和分庫名相同 |
分庫事物名 | 以模闆庫事物名為基礎,根據分庫規則得到的事物名 |
分庫轉換 | 以模闆庫名為基礎,根據分庫規則得到資料庫名的過程 |
分庫序列鍵 | 分庫規則中 标簽中 seq 的值,組成分庫名表達式的一部分;如果是分庫分表,也對應着分表規則中 标簽中 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&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使用之主鍵生成器表介紹》,不再贅述。
分庫1
分庫2
具體的SQL檔案,請參考 fleaorder.sql,fleaorder1.sql,fleaorder2.sql
4.2 各實體類
實體表名 | 描述 |
---|---|
OldOrder | 舊訂單 |
OldOrderAttr | 舊訂單屬性 |
Order | 訂單 |
OrderAttr | 訂單屬性 |
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層代碼:
4.5 各實體的SV層接口和實作
可至 GitHub 檢視如下 SV層代碼:
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檢視如下代碼 :
這裡貼出關鍵使用代碼如下:
@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);
}