如何将不同记录的数据添加到单个记录中?

时间:2013-10-03 00:33:00

标签: java hibernate java-ee hibernate-mapping

如果没有时间,请看一下示例

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

临时用户使用系统作为访客只需提供他们的名字并使用它,但系统需要跟踪它们。

永久用户是那些已注册且永久用户。

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

课程如下,

@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'为密钥...”,我无法首先删除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的收藏夹列表中。并且如上所述,用户可能具有许多不同的不相关的临时记录

9 个答案:

答案 0 :(得分:4)

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

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

某些控制器可以处理从临时到永久的“转换”,确保isTemporary = false和永久用户的其他属性得到适当设置。这将完全支持克隆问题,并且在您的数据库上会更容易。

答案 1 :(得分:3)

我遇到了同样的问题。我一直在像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...
 }

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

我希望我能传播一些想法。对不起我的悲惨英语我很抱歉。

答案 2 :(得分:2)

我同意先前的评论,如果有可能你应该重新评估这些实体,但如果不可能,我建议你从数据库中返回一般用户,然后将该用户称为PermUser或TempUser根据某些标准的存在,成为用户的扩展。

答案 3 :(得分:1)

问题的第2部分:

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

答案 4 :(得分:1)

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

1)我不知道 TempUser 何时想成为 PermUser 。所以我将永远将所有 TempUsers 放在单独的表中。

2)如果用户总是希望 TempUser ,我该怎么办?我仍然有单独的 TempUser 表来引用..

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

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

此外,如果您保留TempUsers TempUser实际成为永久,您有多少人会知道{{1}}成为永久性的平均时间

答案 5 :(得分:1)

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

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

Class'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;
}

答案 6 :(得分:1)

有些人已经建议我使用继承+浅层副本(共享引用)或使用库进行深度克隆来解决这个问题,这些库允许我排除/操纵自动生成的id(当你想要复制项目时)。 / p>

由于您不希望过度弯曲数据库模型,因此请使用具有公共属性的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收集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时,级联的单向关系应该可以正常工作。

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

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文档:

  

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

我在几个项目中使用过Dozer,例如,在一个项目中有一个需要映射到JPA模型层的JAXB层。他们足够接近,但不幸的是我不能弯曲。 Dozer工作得很好,很容易学习,并且使我无法编写70%的无聊代码。我可以根据个人经验深入克隆推荐这个库。

答案 7 :(得分:0)

从纯粹的OO角度来看,实例从一种类型转换为另一种类型,无论是否是Hibernate都没有意义。听起来您可能想要独立于其数据库表示重新考虑对象模型。例如,FourWD看起来更像是汽车的属性,而不是专业化。

答案 8 :(得分:-1)

对此进行建模的一种好方法是创建类似UserData类的内容,以使TempUser具有UserDataPermUser具有UserData 。你也可以让TempUser有一个PermUser,虽然这不太清楚。如果您的应用程序需要交替使用它们(您使用继承所获得的东西),那么这两个类都可以实现一个返回UserData的接口(或第二个选项getPermUserPermUser返回的地方)。

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