flea-db使用之JPA分库分表实现

Posted Huazie

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了flea-db使用之JPA分库分表实现相关的知识,希望对你有一定的参考价值。

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. 名词解释

名词解释
模板库名用作模板的数据库名
模板库持久化单元名模板库下的持久化单元名,一般和模板库相同
模板库事物名模板库下的事物管理器名 ,分库配置中可查看 <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事物模板,参考 SpringTransactionTemplate,它是简化程序化事务划分和事务异常处理的模板类。其核心方法是 execute , 参数是实现事物回调接口的事务代码。此模板收敛了处理事务生命周期和可能的异常的逻辑,因此事物回调接口的实现和调用代码都不需要显式处理事务。

下面将贴出其核心方法 execute,如下:

	@Override
    public 以上是关于flea-db使用之JPA分库分表实现的主要内容,如果未能解决你的问题,请参考以下文章

flea-db使用之主键生成器表介绍

ShardingSphere技术专题ShardingJDBC实现分库分表

分库分表之Mycat实现

使用sharding做分库分表,使用jpa,发生的save不报错,数据库缺插不进去数据的问题

分库分表之Sharding-JDBC

256变4096:分库分表扩容如何实现平滑数据迁移?