【问题标题】:How to add data of different records to a single record?如何将不同记录的数据添加到单个记录中?
【发布时间】:2013-10-09 13:11:02
【问题描述】:

如果没有时间请看示例

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

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

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

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

类如下,

@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 用户对象时,它会显示一条消息,如“重复条目 '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 的收藏夹列表中。并且如上所述,用户可能有许多不同的不相关的临时记录

【问题讨论】:

  • 我想我不明白这个问题,尤其是“删除一个类的记录”或“保留其相关记录”是什么意思。否则我会说,因为它在 Java 中很典型,你总是可以只使用超类 Car 而永远不知道任何四轮驱动。不知何故,这听起来太容易了。和冬眠有什么关系?
  • @Trilarion 问题已更新。谢谢
  • 我从来不明白这些问题。 17.5 年的 Java 和我从来没有克隆过一个对象,除了实验。
  • @EJP 那么你如何实现这样的要求呢?
  • @J888 perm 用户是否包含临时用户拥有的所有字段/属性?

标签: java hibernate jakarta-ee hibernate-mapping


【解决方案1】:

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

由于您不想过多地弯曲数据库模型,因此请从具有通用属性的Mapped Superclass 开始。这根本不会反映在您的数据库中。如果可以的话,我会选择Single Table Inheritance,它映射接近您的模型(但可能需要对数据库层进行一些调整)。

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

那么让PermUserTempUser都继承自User,这样它们就会有很多共同的状态:

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

现在有几种可能的方法,如果您的类没有很多状态,例如,您可以创建一个构造函数来构建 PermUser 收集 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());

使用推土机,您可以通过annotationsAPIXML 配置映射,以执行排除字段、类型转换等操作。

模拟映射:

<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 复制到所有克隆的双向关系回用户(如果有的话),等等。

另外,请注意cloning collections 默认情况下是累积操作。

来自推土机文档:

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

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

【讨论】:

    【解决方案2】:

    我认为您应该进行手动深度克隆。不完全是克隆,因为您必须将来自多个 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;
    }
    

    【讨论】:

      【解决方案3】:

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

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

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

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

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

      此外,如果您保留TempUsers,您将知道有多少实际变为永久,并且还知道TempUser 变为永久的平均时间.

      【讨论】:

        【解决方案4】:

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

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

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

        更多信息在这里 Subtle design: inheritance vs roles

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

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

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

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

        一些想法:A proper way for JPA entities instantiation

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

        示例(非常基础,不要期望太高!)

        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...
         }
        

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

        我希望我能传播一些想法。对不起我糟糕的英语。

        【讨论】:

        • 谢谢你的回答,你的英文很好,请问setRole是做什么的?我无法为用户分配角色,因为 tempUser 几乎没有额外的属性。
        【解决方案5】:

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

        【讨论】:

        • 谢谢,但我不能将它们作为一个实体,因为 tempUser 几乎没有额外的字段。问题是如何将 tempUser 记录的字段复制到 permUser 记录,而不仅仅是检索它们。
        • @J888 所以我很清楚,Temp 和 Perm 用户是否都存在于数据库的同一个表中?意思是,表中有一个日期列,仅由 TempUser 使用,对于 PermUser 为空?
        • 没有,如图所示,PermUser 和 TempUser 类上有一个@Entity,这意味着它们保存在两个不同的表中。
        【解决方案6】:

        对于问题的第 2 部分:

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

        【讨论】:

          【解决方案7】:

          如果我遗漏了什么,请原谅我,但我不认为 TempUserPermUser 应该是不同的类。 TempUser 扩展 PermUser,这是一种“is-a”关系。显然,临时用户不是永久用户的一种。您的问题没有提供足够的信息来证明使它们不同的合理性-也许它们是同一类,并且可以将差异表示为一些新属性?例如:

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

          从临时到永久的“转换”可以由一些控制器来处理,确保isTemporary = false 和永久用户的其他属性被正确设置。这将完全避开克隆问题,并且在您的数据库上更容易。

          【讨论】:

          • 感谢您的回答,我之所以使它们不同,是因为 tempUser 的附加属性很少。
          • 感谢您的更新。我想我不明白 TempUser 和 PermUser 之间的关系。你什么时候有两个?如果有人登录 X.com,以 TempUser 身份将东西添加到购物车,然后登录,并且即使他们是 PermUser,仍需要查看购物车,我可能会看到需要。这就是我们要处理的类型吗?
          • bstempi,请忽略原因,帮我将那些tempUser在他们的文件中的任何内容复制到permUser记录,然后删除tempUser记录。
          • 原因是我已经告诉用户可能有不同的 tempUser 记录,因此应用您的方法没有意义,因为我需要一种方法来添加特定用户的所有 tempUser 记录的信息到他的 permUser 记录。
          • 所以,想一想那句话:“一个用户可能有不同的 tempUser 记录。”现在,看看你的代码。临时用户记录是一种类型 perm 用户记录。如果这里没有继承,您可以简单地将数据从一个复制到另一个。因为它们共享相同的表,所以这变得更加困难。很抱歉,除了改变两人的关系,我想不出解决办法。
          【解决方案8】:

          对此进行建模的一个好方法是创建类似UserData 的类,使得TempUser 具有UserDataPermUser 具有UserData。你也可以让TempUser has-a PermUser,虽然这会不太清楚。如果您的应用程序需要可互换地使用它们(您将通过您使用的继承获得),那么这两个类都可以实现一个返回 UserData 的接口(或者在第二个选项中,getPermUser,其中 PermUser返回自身)。

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

          【讨论】:

            【解决方案9】:

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2021-11-17
              • 1970-01-01
              • 2013-04-21
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多