JPA/Hibernate中的继承映射

8

这是一个相当冗长(并不是过于复杂)的设计问题,请耐心听我讲解。我正在尝试使用POJO和JPA实现人员/角色管理系统。我对ORM比较陌生,这主要是一个映射问题。

我已经将其作为POJO工作,并且对调用者级别的API感到满意,但现在想在Seam环境中使用JPA或Hibernate将其映射到数据库。

我的实现基于装饰器(GoF)和人员角色对象模式(Baumer/Riehle等)。所有角色都是硬编码的,不支持运行时添加新角色,因为这需要更改代码扩展行为。我将使用用户组来实现安全性和权限。

有一个Person接口,其中包含角色管理方法,例如addRole()、removeRole()、hasRole()、getRole()、getRoles()等。具体实现由PersonImpl类提供。

还有一个抽象类Role,它也实现了Person接口(用于装饰器替换等价性),以及扩展它的RoleImpl类。Role类保存对person实例的引用,使用它来服务于任何在person接口上的方法/属性调用,这意味着Role的所有子类都可以处理Person接口。role构造函数将person对象作为参数。

这些是接口/类:

public interface Person {
    public String getFirstName();

    public void setFirstName(String firstName);
    .
    .
    .
    public boolean isEnabled();
    public void setEnabled(boolean enabled);
    public Set<Role> getRoles();
    public Role addRole(Class<? extends Role> roleType);
    public void removeRole(Class<? extends Role> roleType);
    public boolean hasRole(Class<? extends Role> roleType);
    public Role getRole(Class<? extends Role> roleType);

    public enum Gender {MALE, FEMALE, UNKNOWN};
}

public class PersonImpl implements Person {
    .
    .
    .
}

public abstract class Role implements Person {
protected PersonImpl person;

    @Transient
    protected abstract String getRoleName();
    protected Role() {}
    public Role(PersonImpl person) {
        this.person = person;
    }
    public String getFirstName() {
        return person.getFirstName();
    }
    public void setFirstName(String firstName) {
        person.setFirstName(firstName);
    }
    public Set<Role> getRoles() {
        return person.getRoles();
    }
    public Role addRole(Class<? extends Role> roleType) {
        return person.addRole(roleType);
    }
    .
    .
    .
}

public abstract class RoleImpl extends Role {
    private String roleName;

    protected RoleImpl() {}

    public RoleImpl(PersonImpl person) {
        super(person);
    }
    . 
    . 
    .
}

public class Employee extends RoleImpl {
    private Date joiningDate;
    private Date leavingDate;
    private double salary;

    public Employee(PersonImpl person) {
        super(person);
    }
    .
    .
    .
}

这个图表展示了类之间的关系: (如果您无法在此处看到图表,请在此处查看
我会这样使用这些类:
Person p = new Person("Doe", "John", Person.MALE, ...);
// assuming Employee extends Role
Employee e = (Employee)p.addRole(Employee.class);

由于Role类还实现了Person接口,因此我也可以执行以下操作:

// assuming Parent extends Role
Parent parent = new Parent((PersonImpl)p);

e.addRole(Parent.class);
e.getDateOfBirth();  // handled by the decorated person class

// assuming Manager extends Employee extends Role
Manager m = (Manager)p.getRole(Manager);

if (m.hasRole(Employee.class) { 
    // true since Manager derives from Employee
}

我有以下问题:
(a) 这个实现是否过于复杂,如果是的话,有什么更简单的方法?请注意,这是一个非平凡的商业应用程序,而不是一个儿童项目,我认为子类化角色在员工、经理等情况下利用行为很重要。
(b) 如何在JPA / Hibernate中映射它?
(c) 它是否可以映射,以便我也可以利用Seam身份管理?(我的角色定义显然与Seam的角色定义不同)
(d) 如果我采用表格-每个子类(InheritanceType.JOINED)映射策略,我将PersonImpl映射为PERSONS表,RoleImpl映射为ROLES表,并将RoleImpl的每个子类(例如Employee和Parent)映射到自己的表(如EMPLOYEES和PARENTS)。
然后,在PERSONS和ROLES之间(在PersonImpl的roles集合上)有一个@ManyToMany关系,使用join-table PERSON_ROLES。
现在的问题是,EMPLOYEES和PARENTS表只有ROLE_ID引用,因为继承映射策略显然认为它们是Roles(ROLES)的扩展,而我需要它们作为PERSON_ROLES的附录,并且需要正确解析USER_ID + ROLE_ID键,或者至少是USER_ID。
我更喜欢规范化的数据库,依赖于额外的连接,而不是难以维护且可能会堆积大量未使用和无关的字段的非规范化数据库,这就是为什么我认为表格-每个子类是正确的方法。
或者,在这种情况下,表格-类层次结构(InheritanceType.SINGLE_TABLE)是否有更好的选择?请注意,某些角色可能具有数十个属性/字段。
(e) 这个设计是否有更好的替代方案?
非常感谢任何想法/建议。

如果每个角色对象都适用于一个单独的人员对象,就像您的代码所示,那么为什么在PERSONS和ROLES之间有@ManyToMany关系?难道不应该是@OneToMany吗? - Tom Anderson
一个人可能有多个角色。每个角色可能有多个人。我认为Arthur关于@OneToMany和@ManyToOne的观点可能有一些价值。必须检查一下... - alan-p
1个回答

6

如所述

所以请耐心等待

我的答案将基于您所说的内容

所有角色都是硬编码的...存在一个抽象类Role...

如果存在AbstractRole类,则我认为AbstractRole类中定义的某些属性会被所有子类继承

@Entity
/**
  * Which strategy should we use ??? keep reading
  */
@Inheritance
public abstract class AbstractRole implements Serializable {

    private Integer id;

    private String commonProperty;
    private String otherCommonProperty;
    private String anotherCommonProperty;

    @Id
    @GeneratedValue
    public Integer getId() {
        return this.id;
    }

    // getter's and setter's

}

现在的员工(同样适用于家长和学生)
@Entity
public class Employee extends AbstractRole {

    private String specificEmployeeProperty;

    // getter's and setter's

}

但是应该使用哪种继承策略呢?

InheritanceType.JOINED

  • 子类有复杂的映射:许多属性,其他关系如@OneToMany、@OneToOne、@ManyToMany等
  • 如果您使用Hibernate作为JPA提供程序,请记住它不支持鉴别器列。请参见此处
  • 一个简单的查询将UNION ALL所有为每个子类定义的表。也许您只想检索一个子类,您会看到性能问题。 这里有一个解决方法。

InheritanceType.SINGLE_TABLE

  • 子类有简单的映射。没有那么多的属性和最少量的其他关系
  • 运行时具体类的分辨率
  • 由于所有子类共享相同的表,它处理了大量可空列
  • 更快的性能

现在让我们来看看

有一个人员,有管理角色方法,例如addRole(),removeRole(),hasRole(),getRole(),getRoles()等

好吧,如果我看到像addRole方法这样的东西,我假设Person与AbstractRole类具有@OneToMany或@ManyToMany关系。 我知道,我知道...您有@ManyToMany关系,但是我真的建议将其拆分为@OneToMany-@ManyToOne关系。请参见此处如何进行操作。

@Entity
public class Person implements Serializable {

    private Integer id;

    private List<AbstractRole> roleList;

    @Id
    @GeneratedValue
    public Integer getId() {
        return this.id;
    }

    @OneToMany
    public List<AbstractRole> getRoleList() {
        return this.roleList; 
    }

    // getter's and setter's

}

最后

它能被映射吗,这样我也可以利用Seam身份管理的优势

Seam身份管理允许您实现自己的行为,无论是否使用JPA

@Name("authenticationManager")
public class AuthenticationManager {

   /**
     * Starting with Seam 2.1+, you should use Credentials instead of Identity
     * To collect your username and password
     *
     * Your JSF Form should looks like
     *
     * <h:inputText value="#{credentials.username}"/>
     * <h:inputSecret value="#{credentials.password}"/>
     */
    private @In org.jboss.seam.security.Credentials credentials;

    /**
      * Login method
      *     must take no arguments
      *     must return a boolean indicating whether the credentials could be verified
      *
      * method name does not mind
      */
    public boolean authenticate() {
        /**
          * Logic To verify whether credentials is valid goes here
          */
    }

}

并在/WEB-INF/components.xml中定义您的身份验证方法。

<security:identity authenticate-method="#{authenticationManager.authenticate}"/>

关于您的映射

它取决于业务需求、客户需求等等...但我认为它可以像上面展示的那样更简单。

祝好运!


我已添加了代码片段,展示了我的接口/类定义。您是正确的,假设Person中有一个角色集合。在回复之前,我会仔细阅读您的回复。谢谢。 - alan-p
@alan-p 请参考https://dev59.com/uHE95IYBdhLWcg3wHqWl,了解我们如何实现类似的场景。几点说明:我不会将Person作为AbstractRole的实现接口。我认为你的模型可以更简单。为什么不将Person作为一个简单的POJO而不是使用它作为接口?我知道你可能有一个复杂的人员/角色管理系统,但保持代码简单。如果可能的话,请使用领域驱动设计方法。请参考https://dev59.com/DXE85IYBdhLWcg3w03Dz。 - Arthur Ronald
我还没有完全做到这一点,但我认为你提出的很多观点都是正确的,并且非常有帮助,所以我接受了这个答案。谢谢。 - alan-p
@alan-p 请看这里:https://dev59.com/xU3Sa4cB1Zd3GeqPul-B。我是通过使用MapedSuperClass注解来实现继承,而非默认的继承注解。 - Arthur Ronald

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接