使用JPA/Hibernate进行继承映射

ala*_*n-p 8 oop inheritance hibernate seam jpa

这是一个相当冗长(并不过于复杂)的设计问题,所以请耐心等待.我正在尝试使用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);
    }
    .
    .
    .
}
Run Code Online (Sandbox Code Playgroud)

此图显示了类关系:

(如果你看不到内联图,可以通过yUML在这里查看)

我会像这样使用这些类:

Person p = new Person("Doe", "John", Person.MALE, ...);
// assuming Employee extends Role
Employee e = (Employee)p.addRole(Employee.class);
Run Code Online (Sandbox Code Playgroud)

由于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
}
Run Code Online (Sandbox Code Playgroud)

我的问题是:

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

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

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

(d)如果我使用table-per-subclass(InheritanceType.JOINED)映射策略,我将PersonImpl映射为PERSONS,将RoleImpl映射为ROLES表,并将RoleImpl的每个子类(例如Employee和Parent)映射到它们自己的表中.员工和家长.

然后,我将在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)这种设计有更好的替代方案吗?

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

Art*_*ald 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

}
Run Code Online (Sandbox Code Playgroud)

现在员工(父母和学生使用相同的方法)

@Entity
public class Employee extends AbstractRole {

    private String specificEmployeeProperty;

    // getter's and setter's

}
Run Code Online (Sandbox Code Playgroud)

但是使用哪种继承策略?

InheritanceType.JOINED

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

InheritanceType.SINGLE_TABLE

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

现在让我们看看

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

好吧,如果我看到类似addRole方法的东西,我认为Person与AbstractOole类有@OneToMany或@ManyToMany关系.我知道,我知道......你有@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

}
Run Code Online (Sandbox Code Playgroud)

最后

它是否可以映射,因此我也可以利用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
          */
    }

}
Run Code Online (Sandbox Code Playgroud)

并在/WEB-INF/components.xml中定义您的authenticate-method

<security:identity authenticate-method="#{authenticationManager.authenticate}"/>
Run Code Online (Sandbox Code Playgroud)

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

祝你好运!