8

如果没有时间请看示例

我有两种类型的用户,临时用户和永久用户。

临时用户以访客身份使用系统,只需提供他们的姓名并使用它,但系统需要跟踪他们。

永久用户是已注册的永久用户。

一旦用户为自己创建了永久记录,我需要将用户作为访客时跟踪的所有信息复制到他的永久记录中。

课程如下,

@Entity
public class PermUser{
    @Id
    @GeneratedValue
    private long id;

    @OneToMany
    private List Favorites favorites;    
    ....

}

@Entity
public class Favorites {
    @Id
    @GeneratedValue
    private long id;

    @OneToMany (cascade = CascadeType.ALL)
    @LazyCollection(LazyCollectionOption.FALSE)
    private List <FavoriteItems> items;

    ...
 }

 @Entity
   public class FavoriteItems {
     @Id
     @GeneratedValue
     private long id;

     private int quantity;

     @ManyToOne
     private Ball ball;
     ..
   }


@Entity
public class TempUser extends PermUser{
    private String date;
    ....
}

问题是:

如果我克隆 tempUser 对象,我也会复制 id 参数,因此在保存 perm 用户对象时,它会显示一条消息,如“Duplicate entry '10' for key ...”,我无法先删除 tempUser 然后保存permUser 好像保存 permUser 失败我会错过数据。如果我尝试在没有项目 ID 的情况下分别复制每个收藏项目球,那将不是一种有效的方法。

示例(一句话中的问题:如图所示,一个用户可能有多个 TempUser 记录和一个 PermUser 记录,因此我需要将所有 TempUser 记录的信息添加到该单个 PermUser 记录。)

  Type of record    | name      | favorites         | date 
                    |           |                   |
1)TempUser          | Jack      | 2 items           | 1/1/2013
2)TempUser          | Jack      | 3 items           | 1/4/2013
  ---------------------------------------------------------------------------
  PermUser          | Jack      | 5 items ( 2 + 3 items from his temp records)

*请注意,我需要找到解决方案,并且不在乎是否尝试新的解决方案而不是克隆对象。

我有两个不同的类的原因是它tempUser的附加属性很少,我可能还需要将几个收藏夹添加tempUsers到一个收藏夹列表中permUser。并且如上所述,用户可能有许多不同的不相关的临时记录

4

9 回答 9

4

Forgive me if I'm missing something, but I don't think that TempUser and PermUser should be different classes. TempUser extends PermUser, which is an "is-a" relationship. Clearly, temporary users are not a type of permanent user. Your question doesn't give enough information to justify making them different -- perhaps they're the same class, and the difference can be expressed as a few new attributes? Eg:

@Entity
public class User{
    @OneToMany(cascade = CascadeType.ALL)
    private List Favorites favorites;
    private boolean isTemporary;
    ....
}

The "transition" from temporary to permanent can be handled by some controller, making sure that isTemporary = false and that the other properties of a permanent user are appropriately set. This would completely side-step the cloning issue and would be much easier on your database.

于 2013-10-09T04:54:10.827 回答
3

我只是有同样的问题。我一直在像 SO 这样的板上挖掘许多有趣的文章和问题,直到我有足够的灵感。

起初我还想为不同类型的用户创建子类。事实证明,这个想法本身就是一个设计缺陷:

不要使用继承来定义角色!

更多信息在这里微妙的设计:继承与角色

将用户视为一个大容器,其中仅包含其他实体,例如凭据、偏好、联系人、项目、用户信息等。

考虑到这一点,您可以轻松更改某些用户的某些能力/行为,

当然,您可以定义许多用户可以扮演的角色。扮演相同角色的用户将具有相同的功能。

如果您有许多相互依赖的实体/对象,您应该考虑一种以明确定义的方式设置某个用户角色的构建机制/模式。

一些想法:JPA实体实例化的正确方法

如果你有一个用户的建造者/工厂,你的其他问题就不会那么复杂了。

示例(真的很基础,不要期望太多!)

public void changeUserRoleToPermanent (User currentUser) {
    UserBuilder builder = new UserBuilder();
    builder.setRole(Role.PERMANENT); // builder internally does all the plumping
    // copy the stuff you want to keep
    builder.setId(user.getId);
    builder.setPrefences();
    // ... 
    User newRoleUser = builder.build();
    newRoleUser = entityManager.merge(newRoleUser);
    entitymanager.detach(currentUser);
    // delete old stuff
    entityManager.remove(currentUser.getAccountInfo()); // Changed to different implementaion...
 }

我承认,这是一些工作,但是一旦你准备好基础设施,你就会有很多可能性!然后,您可以非常快速地“发明”新东西!

我希望我能传播一些想法。我为我糟糕的英语感到抱歉。

于 2013-10-11T01:37:02.547 回答
2

正如我同意之前的评论一样,如果可能的话,您应该重新评估这些实体,但如果不可能,我建议您从数据库中返回一个普通用户,然后将该用户强制为 PermUser 或 TempUser,这两者都是用户,基于某些标准的存在。

于 2013-10-10T16:37:31.663 回答
1

我认为您应该进行手动深度克隆。不完全是克隆,因为您必须将来自多个 tempUser 的数据合并到单个 permUser。您可以使用反射和可选的注释来自动复制信息。

要将字段从现有对象自动复制到新对象,您可以按照此示例进行操作。它不是深度克隆,但可以作为起点帮助您。

类“c”用作参考。src 和 dest 必须是“c”的实例或“c”的子类的实例。该方法将复制“c”中定义的属性和“c”的超类。

public static <E>  E copyObject(E dest, E src, Class<?> c) throws IllegalArgumentException, IllegalAccessException{
  // TODO: You may want to create new instance of 'dest' here instead of receiving one as parameter
    if (!c.isAssignableFrom(src.getClass())) 
    {
        throw new IllegalArgumentException("Incompatible classes: " + src.getClass() + " - " + c);
    }
    if (!c.isAssignableFrom(dest.getClass())) 
    {
        throw new IllegalArgumentException("Incompatible classes: " + src.getClass() + " - " + c);
    }
    while (c != null && c != Object.class) 
    {
        for (Field aField: c.getDeclaredFields()) 
        {                   
            // We skip static and final
            int modifiers = aField.getModifiers();
            if ( Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers)) 
            {
                continue;
            }

            // We skip the fields annotated with @Generated and @GeneratedValue
            if (aField.getAnnotation(GeneratedValue.class) == null && 
                aField.getAnnotation(Generated.class) == null) 
            {

                aField.setAccessible(true);
                Object value = aField.get(src);
                if (aField.getType().isPrimitive() ||
                    String.class == aField.getType()    || 
                    Number.class.isAssignableFrom(aField.getType()) ||
                    Boolean.class == aField.getType()   ||
                    Enum.class.isAssignableFrom(aField.getType()))
                {
                    try
                    {
                        // TODO: You may want to recursive copy value too
                        aField.set(dest, value);
                    }
                    catch(Exception e)
                    {
                        e.printStackTrace();
                    }
                }
            }   
        }
        c = c.getSuperclass();  
    }

    return dest;
}
于 2013-10-18T09:09:06.610 回答
1

对于问题的第 2 部分:

您正在使用CascadeType.ALL关系favorites。这包括CascadeType.REMOVE,这意味着对用户的删除操作将级联到该实体。CascadeType因此,请指定一个不包含的值数组CascadeType.REMOVE。请参阅http://webarch.kuzeko.com/2011/11/hibernate-understanding-cascade-types/

于 2013-10-09T23:40:44.110 回答
1

我要建议的可能不是那个 OO,但希望是有效的。我很高兴将PermUserTempUser分开,不要扩展它,也不要将它们绑定到 is-a 关系中。因此,我将在数据库中有两个单独的表,一个用于TempUser,一个用于PermUser ,从而将它们视为两个单独的实体。许多人会发现它是多余的......但请继续阅读......我们都知道......有时冗余是好的......所以现在......

1) 我不知道TempUser 何时想成为PermUser。因此,我将始终将所有TempUsers放在单独的表中。

2)如果用户总是想成为TempUser ..,我会怎么做?我仍然有单独的 TempUser表可以参考..

3)我假设当TempUser想要成为PermUser时,您正在阅读他的TempUser名称以获取他的记录作为TempUser

所以现在你的工作很容易。所以现在当一个TempUser想要成为PermUser时,你要做的就是复制TempUser对象,填充你需要的属性并用它创建一个新的PermUser对象。之后,您可以保留您的TempUser记录,如果您想要或删除它.. :)

此外,如果您保留它,您将有多少TempUsers实际成为永久的历史,并且还知道 aTempUser成为永久的平均时间。

于 2013-10-16T07:58:24.723 回答
1

就像一些人已经建议的那样,我将使用继承 + 浅拷贝(共享引用)或深度克隆来解决这个问题,这些库可以让我排除/操作自动生成的 id(当你想要复制项目时)。

由于您不想过多地弯曲数据库模型,因此请从具有公共属性的映射超类开始。这根本不会反映在您的数据库中。如果可以的话,我会使用映射接近您的模型的单表继承(但可能需要对数据库层进行一些调整)。

@MappedSuperclass
public abstract class User {
    @Id
    @GeneratedValue
    private long id;
    // Common properties and relationships...

然后拥有 bothPermUserTempUser继承 from User,这样它们就会有很多共同的状态:

@Entity
@Table(name="USER")
public class PermUser extends User {
  // Specific properties
}

现在有几种可能的方法,如果你的类没有很多状态,你可以,例如,创建一个构造函数来构建PermUser一个 List 的收集数据TempUsers

模拟代码:

@Entity
@Table(name="PERMANENT_USER")
public class PermUser extends User {
  public PermUser() {} // default constructor
  public PermUser(List<TempUser> userData) {
     final Set<Favorites> f = new LinkedHashSet<>();

     // don't set the id
     for(TempUser u : userData) {
        this.name = u.getName();
        // Shallow copy that guarants uniqueness and insertion order
        // Favorite must override equals and hashCode
        f.addAll(u.getFavorites());
     } 
     this.favorites = new ArrayList<>(f);
     // Logic to conciliate dates
  }
}

当您坚持PermUser它时,它将生成一个新的 id,级联的单向关系应该可以正常工作。

另一方面,如果你的类有很多属性和关系,而且在很多情况下你确实需要复制对象,那么你可以使用像Dozer这样的 Bean Mapping 库(但要注意,克隆对象是代码气味)。

Mapper mapper = new DozerBeanMapper();
mapper.map(tempUser.getFavorites(), user.getFavorites());

使用推土机,您可以通过注解APIXML对诸如排除字段、类型转换等操作进行配置映射。

模拟映射:

<mapping>
  <class-a>my.object.package.TempUser</class-a>
  <class-b>my.object.package.PermUser</class-b>

  <!-- common fields with the same name will be copied by convention-->

  <!-- exclude ids and fields exclusive to temp
  <field-exclude> 
    <a>fieldToExclude</a> 
    <b>fieldToExclude</b> 
  </field-exclude>           

</mapping> 

例如,您可以排除 id,或者将permUser.id所有克隆的双向关系复制回用户(如果有的话)等。

另外,请注意克隆集合默认是累积操作。

从推土机文档:

如果您要映射到已经初始化的类,Dozer 将“添加”或“更新”对象到您的列表。如果您的 List 或 Set 中已经有对象,dozer 会检查映射的 List、Set 或 Array 并调用 contains() 方法以确定它是否需要“添加”或“更新”。

我在几个项目中使用过 Dozer,例如,在一个项目中,有一个 JAXB 层需要映射到 JPA 模型层。它们离得很近,但不幸的是我也不能弯曲。Dozer 工作得很好,很容易学习,让我免于编写 70% 的无聊代码。我可以深深克隆出于个人经验推荐这个库。

于 2013-10-21T01:02:43.200 回答
0

从纯粹的 OO 角度来看,实例从一种类型转变为另一种类型(无论是否休眠)都没有任何意义。听起来您可能想独立于其数据库表示重新考虑对象模型。例如,四轮驱动似乎更像是汽车的财产,而不是专业化。

于 2013-10-04T04:21:52.750 回答
-1

对此进行建模的一个好方法是创建类似于has-a和has-a 的UserData类。您也可以制作has-a ,尽管这会不太清楚。如果您的应用程序需要可互换地使用它们(您将通过正在使用的继承获得),那么这两个类都可以实现一个返回(或在第二个选项中,返回自身)的接口。 TempUserUserDataPermUserUserDataTempUserPermUserUserDatagetPermUserPermUser

如果您真的想使用继承,最简单的方法可能是使用“每个类层次结构表”映射它,然后使用直接 JDBC 直接更新鉴别器列。

于 2013-10-04T04:05:23.653 回答