使用JPA / Hibernate进行继承映射

时间:2010-07-13 16:16:57

标签: hibernate inheritance oop jpa seam

这是一个相当冗长(并不过于复杂)的设计问题,所以请耐心等待。我正在尝试使用POJO和JPA实现人员/角色管理系统。我是ORM的新手,这主要是一个映射问题。

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

我的实现基于Decorator(GoF)和Person Role Object模式(Baumer / Riehle等人)。所有角色都是硬编码的,并且不支持运行时添加新角色,因为它需要更改代码以扩展行为。我将使用用户组来实现安全性和权限。

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

还有一个抽象类Role,它还实现了Person接口(用于装饰器替换等价),以及一个扩展它的RoleImpl类。 Role类包含对person实例的引用,使用它来为person接口上的任何方法/属性调用提供服务,这意味着Role的所有子类都可以处理Person接口。角色构造函数将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);
    }
    .
    .
    .
}

此图显示了班级关系:

(如果您无法看到内联图,请在此处查看via yUML

我会像这样使用这些类:

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)这种实施是否是不必要的复杂,如果是这样,那么什么是更简单的方法?请注意,这是一个非平凡的业务应用程序而不是kiddy项目,我认为角色子类化对于在Employee,Manager等情况下利用行为很重要。

(b)如何在JPA / Hibernate中映射它?

(c)是否可以映射,以便我也可以利用Seam身份管理? (我对角色的定义显然与Seam不相似)

(d)如果我使用table-per-subclass(InheritanceType.JOINED)映射策略,我将PersonImpl映射为PERSONS,将RoleImpl映射为ROLES表,并将RoleImpl的每个子类(例如Employee和Parent)映射到它们拥有EMPLOYEES和PARENTS的表格。

然后,我将在PERSONS和ROLES(PersonImpl中的角色集合)之间建立@ManyToMany关系,并使用连接表PERSON_ROLES。

现在的问题是EMPLOYEES和PARENTS表等只有ROLE_ID引用,因为继承映射策略显然认为它们是Roles(ROLES)的扩展,而我需要它们作为PERSON_ROLES的附录,并要求要正确解析的USER_ID + ROLE_ID键,或至少是USER_ID。

我更喜欢一个规范化的数据库,伴随着对额外连接的依赖,而不是非规范化的数据库,这将难以维护并且可能会堆积大量未使用和不相关的字段,这就是为什么我认为每个子类的表是要走的路。

或者是一个每个类的表层次结构(InheritanceType.SINGLE_TABLE),在这种情况下带有一个鉴别器列OK(从数据库维护的角度来看)?请注意,某些角色可能包含许多属性/字段。

(e)有更好的替代方案吗?

非常感谢任何想法/建议。

1 个答案:

答案 0 :(得分:6)

如上所述

  

所以请耐心等待我

所以我的回答将基于你所说的

  

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

如果有一个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提供程序,请记住它不支持discriminator列。请参阅here
  • 一个简单的查询将UNION ALL为每个子类定义的所有表。也许您只想检索一个子类,您将看到性能问题。 Here解决了这个问题。

<强> InheritanceType.SINGLE_TABLE

  • 子类具有简单的映射。没有那么多的属性和最少量的其他关系
  • 运行时具体类的解析
  • 一旦所有子类共享相同的表
  • ,它会处理许多可空列
  • 更快的表现

现在让我们看看

  

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

好吧,如果我看到类似 addRole 的方法,我认为Person与AbstractOole类有@OneToMany或@ManyToMany关系。我知道,我知道......你有@ManyToMany关系,但我真的建议你将@ManyToMany分成@OneToMany - @ManyToOne关系。请参阅here如何

@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 Identity Management 允许您实现自己的行为,无论是否使用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}"/>

关于您的映射它取决于业务需求,客户需求等......但我认为它可以更简单,如上所示

祝你好运!