为代码库设计可扩展实体



我目前正在创建一个产品,也就是说,我指的是一个应用程序,它拥有自己的应用程序,但它的结构可以为不同的客户定制。

在后台使用spring可以很容易地更改所使用的服务/控制器的实现,我只是重载了bean。客户端我有angularJS,我可能需要重组模块并提供更多的灵活性,但这也应该很容易。

我真正唯一的问题在于JPA/Hibernate实体。

以下是我的配置:

<bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="packagesToScan">
        <array>
            <value>com.xxx.entity</value>
        </array>
    </property>
    <property name="persistenceUnitName" value="MyPU" />
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter" ref="jpaVendorAdapter" />
    <property name="jpaDialect" ref="jpaDialect" />
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.show_sql">${hibernate.show.sql}</prop>
            <prop key="hibernate.format_sql">${hibernate.show.sql}</prop>
            <prop key="hibernate.dialect">${database.dialect}</prop>
            <prop key="hibernate.hbm2ddl.auto">validate</prop>
            <!-- <prop key="hibernate.transaction.factory_class">org.hibernate.engine.transaction.internal.jdbc.JdbcTransactionFactory</prop> -->
        </props>
    </property>
    <!-- <property name="sharedCacheMode" value="ENABLE_SELECTIVE" /> -->
</bean>

现在,让我们考虑产品中的以下对象,并为两个不同的客户进行两次定制:

// let's say this one is the basic one
class People{ String firstName;String lastName;}
// one client want us to add one felds
class PeopleClientOne extends People{String address;}
// another define two type of very different people while having one more field common for them
class People{String firstName; String lastName;String anotherField;}
class PeopleOneClientTwo extends People{...}
class PeopleTwoClientTwo extends People{...}

因此,我的问题是,考虑到以下限制,处理这一问题的最佳方式是什么:

  • 没有完整的抽象数据库模型(比如有一个实体的大表和一个属性的大表)
  • 我正在使用jackson,所以当我有继承时,我必须注释/更改超类的注释
  • JPA/Jackson注释在同一个类上,我不想尽可能复制我的模型的类,并使其集中

以下是我的想法:

可扩展属性映射:

  • 为支持此功能的每个项目类型再添加一个表
  • 数据库可读性较差
  • 无法在数据库中应用任何约束

背书用途:

  • 重写具有相同包和相同名称的实体,并将其放在背书文件夹中,当然,它们只能添加新字段
  • 数据库仍然可读且干净
  • 一般来说,不建议使用背书,但它是可用的,所以它是有价值的

注意,我目前正在使用hibernate4,但如果需要,我可以升级到5。同样,如果您的解决方案是使用注释而不是XML,我可能稍后会改用注释,但今天不会。

欢迎任何替代贡献,即使它不符合我的要求。

我最终能够做到的是:

去掉只固定到一个包的数组

将数组定义为列表:

<util:list id="entityPackages">
    <value>my.package.com.entity</value>
</util:list>

注入时使用EL转换为数组:

<property name="packagesToScan" value="#{entityPackages.toArray()}"/>

必要时在自定义客户端模块中过载:

<!-- either import file containing the old one or use profile -->
<util:list id="entityPackages">
    <value>my.package.com.entity</value><!-- need to repeat that -->
    <value>my.package.com.subentity</value>
</util:list>

因此,即使不在同一个包中,您也可以添加新的实体。

向实体添加继承

为了添加属性/扩展对象,您需要添加继承性。对于层次结构JOIN继承,只要将唯一约束和外键正确添加到适当的字段中,就不会花费太多。

@Inheritance(strategy=InheritanceType.JOINED)

如果你现在不能,因为你不能修改超类,你可能有两个选择:

  • 使用Hibernate的XML配置
  • 获取源(原始或.class)或类,添加注释,将其封装到一个jar中,并将其放入服务器/应用程序的认可文件夹中,不要忘记启用认可机制

严格来说,使用背书是危险的,必须非常小心。对于那些不知道的人,当您启用认可机制时,您可以指定一个文件夹,在每个类之前,将加载中的所有jar文件。即使是JRE,也可以确保该文件夹中的类将是加载到其他JAR中的类。

Jackson:使用mix-in进行继承

通常,对于Jackson,您需要添加注释@JsonSubTypes,这会迫使您将母类与子类链接起来。为了使这个类独立于这个孩子,你可以使用mixIn注释:

// make that bean get injected the instance of jackson's mapper and called either during the initialization or just after the spring's application context was loaded.
public afterStartup() {
    myJackSonMapper.addMixIn(Site.class, SiteInheritanceMixIn.class);
}
// considering here that SubSite is inheriting from Site 
@JsonSubTypes({
    @JsonSubTypes.Type(value = SubSite.class, name = "subSite")
})
public abstract class SiteInheritanceMixIn{
}

最新更新