如果没有时间,请看一下示例
我有两种类型的用户,临时用户和永久用户。
临时用户使用系统作为访客只需提供他们的名字并使用它,但系统需要跟踪它们。
永久用户是那些已注册且永久用户。
一旦用户为自己创建永久记录,我需要将用户作为访客时所跟踪的所有信息复制到他的永久记录中。
课程如下,
@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
的收藏夹列表中。并且如上所述,用户可能具有许多不同的不相关的临时记录
答案 0 :(得分:4)
如果我遗漏了某些内容,请原谅我,但我不认为TempUser
和PermUser
应该是不同的类。 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...
然后让PermUser
和TempUser
都继承自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());
使用推土机,您可以通过annotations,API或XML配置映射,例如排除字段,输入类型等。
模拟映射:
<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
具有UserData
和PermUser
具有UserData
。你也可以让TempUser
有一个PermUser
,虽然这不太清楚。如果您的应用程序需要交替使用它们(您使用继承所获得的东西),那么这两个类都可以实现一个返回UserData
的接口(或第二个选项getPermUser
, PermUser
返回的地方)。
如果你真的想使用继承,最简单的方法可能是使用“每个类层次结构”映射它,然后使用直接JDBC直接更新鉴别器列。