8

If do not have time please have a look at the example

I have two types of users, temporary users and permanent users.

Temporary users use the system as guest just provide their name and use it but system needs to track them.

Permanent users are those that are registered and permanent.

Once user create a permanent record for himself, I need to copy all the information that has been tracked while user was a guest to his permanent record.

Classes are as following,

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

Problems is :

If I clone the tempUser object, I am copying the id parameters as well so when saving the perm user object it shows a message like "Duplicate entry '10' for key ...", I can not remove the tempUser first then save the permUser as if saving permUser failed I will miss the data. If I try to copy each ball of favoriteitems separately without id of item it would not be an efficient way.

Example (Question in one sentence: As shown blew a user may have more than one TempUser record and just one PermUser record, therefore I need to add information of all the TempUser records to that single PermUser record.)

  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)

*Please note, I need to find a solution, and do not care if try a new solution rather than cloning the object.

The reason that I have two different classes is that tempUser has few additional attributes, I may also need to add favorites of few tempUsers to favorites list of one permUser. and also as mentioned above a user may have many different not related temp records

Roman C
  • 49,761
  • 33
  • 66
  • 176
J888
  • 1,944
  • 8
  • 42
  • 76
  • I think I don't understand the question, especially what "remove the record of a class" or "keep its related record" mean. Otherwise I would say that as it is typical in Java you can always just use the superclass Car and never know of any FourWD. This sounds too easy somehow. And what is the connection to hibernating? – NoDataDumpNoContribution Oct 08 '13 at 13:25
  • @Trilarion question is updated. thanks – J888 Oct 08 '13 at 22:29
  • 1
    I never understand these questions. 17.5 years of Java and I've never cloned an object at all, except experimentally. – user207421 Oct 09 '13 at 03:24
  • @EJP so how do you implement such requirements? – J888 Oct 09 '13 at 22:04
  • @J888 does perm user contain all the fields/attributes that a temp user has? – Prateek Oct 09 '13 at 22:34
  • @Prateek No temp has two additional attributes, also I may need to add favorites of few tempUsers to one permUser. – J888 Oct 09 '13 at 22:40
  • @EJP question is updated, please let me know what would be your solution, thanks – J888 Oct 09 '13 at 22:41
  • What does the DB model look like? Or is this something you can choose? – herman Oct 09 '13 at 23:27
  • Why are you using `CascadeType.ALL` for the `favorites` relation if you don't want to cascade deletes? – herman Oct 09 '13 at 23:34
  • @herman I can remove the cascadeType, I can choose the db model – J888 Oct 09 '13 at 23:35

9 Answers9

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.

bstempi
  • 2,023
  • 1
  • 15
  • 27
  • Thanks for your answer, the reason that I made them different is that tempUser has few additional attributes. – J888 Oct 09 '13 at 22:29
  • Thanks for the update. I guess I don't understand the relationship between a TempUser and a PermUser. When would you have both? I could see a need if someone logged onto X.com, added stuff to a shopping cart as a TempUser, and then logged in, and needed to still see the shopping cart even though they're a PermUser. Is that the type of thing we're dealing with? – bstempi Oct 10 '13 at 16:33
  • bstempi, please ignore the reason, just help me to copy whatever those tempUser have in their fileds to permUser record, then delete tempUser records. – J888 Oct 11 '13 at 04:41
  • the reason is that as I have told a user may have different tempUser records, so it does not make sense to apply your approach as I need a way to add info of all tempUser records of a particular user to his permUser record. – J888 Oct 14 '13 at 23:05
  • So, think about that statement for a moment: "a user may have different tempUser records." Now, look at your code. A temp user record is a *type of* perm user record. If here was no inheritance, you could simply copy data from one to the other. Because they share the same tables, this becomes much more difficult. I'm sorry, but I cannot think of a solution beyond altering the relationship of the two. – bstempi Oct 15 '13 at 13:47
  • I've noticed that but what you are suggesting, (have a isTemporary field) is not efficient, as it would be difficult to add new records to it. The reason that tempuser class is inheriting permuser is that everything is the same as permuser except those extra fields that tempUser has. – J888 Oct 15 '13 at 23:32
  • Just because they have the same fields does not mean that they have an "is a" relationship. – bstempi Oct 16 '13 at 00:28
  • if I have them as separate classes I have to redo all the addTOfavorite methods etc. – J888 Oct 16 '13 at 00:35
  • While that may stink, it'll be "correct" and solve your relationship problem. Alternatively, you could have a `User` class that super-classes `PermUser` and `TempUser` so that you don't have to redefine those methods. – bstempi Oct 16 '13 at 00:54
3

I just had the same problem. I've been digging through many interesting articles and questions in boards like SO untill I had enough inspiration.

At first I also wanted to have sub classes for different types of users. It turns out that the idea itself is a design flaw:

Don't use inheritance for defining roles!

More Information here Subtle design: inheritance vs roles

Think of an user as a big container which just harbors other entities like credentials, preferences, contacts, items, userinformation, etc.

With this in mind you can easily change certain abilities/behaviour of certain users,

Of course you can define a role many users can play. Users playing the same role will have the same features.

If you have many entities/objects depending on each other you shoukd think of a building mechanism/pattern that sets up a certain user role in a well defined way.

Some thoughts: A proper way for JPA entities instantiation

If you had a builder/factory for users, your other problem wouldn't be that complex anymore.

Example (really basic, do not expect too much!)

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

I admit, it is some work but you will have many possibilities once you have the infrastructure ready! You can then "invent" new stuff really fast!

I hope I could spread some ideas. I'm sorry for my miserable english.

Doe Johnson
  • 1,374
  • 13
  • 34
  • thanks for your answer, your English is good, may I know what setRole does? I can not assign roles to users as tempUser has few extra attributes. – J888 Oct 11 '13 at 04:34
2

As I agree with prior comments that if it is possible you should reevaluate these entities, but if that is not possible I suggest that you return a general User from the database and then caste that user as either PermUser or TempUser which both would be extensions of User, based on the presence of certain criteria.

Joe
  • 661
  • 2
  • 8
  • 15
  • Thanks but I can not have them as a single entity as tempUser has few extra fields. The problem is how to copy fields of tempUser records to permUser record not just retreiving them. – J888 Oct 11 '13 at 04:50
  • @J888 So that I am clear, do both Temp and Perm Users exist in the same table in the database? Meaning, that there is a column for date in the table which is only used by a TempUser and is null for a PermUser? – Joe Oct 11 '13 at 13:19
  • no as shown there is a @Entity over PermUser and TempUser classes,means they are kept in two different tables. – J888 Oct 13 '13 at 22:13
1

For part 2 of your problem:

You are using CascadeType.ALL for the favorites relation. This includes CascadeType.REMOVE, which means a remove operation on the user will cascade to that entity. So specify an array of CascadeType values that doesn't include CascadeType.REMOVE. See http://webarch.kuzeko.com/2011/11/hibernate-understanding-cascade-types/.

herman
  • 11,740
  • 5
  • 47
  • 58
1

What I am going to suggest might not be that OO but hope will be effective. I am happy to keep PermUser and TempUser separate do not extend it, not binding them into is-a relationship also. So I will have two separate tables in database one for TempUser and one for PermUser thereby treating them as two seperate entities. Many will find it to be redundant.. but read on... we all know.. sometimes redundancy is good.. So now...

1) I don't know when a TempUser would want to become PermUser. So I will always have all TempUsers in separate table.

2) What would I do if a user always wants to be TempUser..? I still have separate TempUser table to refer to..

3) I am assuming that when a TempUser wants to become a PermUser you are reading his TempUser name to get his records as TempUser.

So now your job is easy. So now when a TempUser want to become PermUser all you would do is copy TempUser objects,populate your required attributes and create a new PermUser object with it. After that you can keep your TempUser record if you want to or delete it.. :)

Also you would have a history how many of your TempUsers actually become permanent if you keep it and also know in what average time a TempUser becomes permanent.

DarkHorse
  • 2,740
  • 19
  • 28
1

I think you should do a manual deep clone. Not exactly a clone since you have to merge data from several tempUsers to a single permUser. You can use reflection and optionally annotations to automate the copy of information.

To automatically copy fields from an existing object to a new one you can follow this example. It is not a deep clone but may help you as starting point.

Class 'c' is used as reference. src and dest must be instances of 'c' or instance of subclases of 'c'. The method will copy the attributes defined in 'c' and superclasses of '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;
}
aalku
  • 2,860
  • 2
  • 23
  • 44
1

Like some have already suggested I would tackle this problem using inheritance + either shallow copies (to share references) or deep cloning with libraries that let me exclude / manipulate the auto-generated ids (when you want to duplicate items).

Since you don't want to bend your database model too much, start with a Mapped Superclass with common attributes. This will not be reflected in your database at all. If you could I would go with Single Table Inheritance which maps close to your model (but may require some adjusts on the database layer).

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

Then have both PermUser and TempUser inherit from User, so that they will have a lot of common state:

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

Now there are several possible approaches, if your classes don't have a lot of state, you can, for instance, make a constructor that builds a PermUser collecting data of a List of TempUsers.

Mock code:

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

When you persist the PermUser it will generate a new id, cascaded unidirectional relationships should work fine.

On the other hand, if your class have a lot of attributes and relationships, plus there are a lot of situations in which you really need to duplicate objects, then you could use a Bean Mapping library such as Dozer (but be warned, cloning objects is a code smell).

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

With dozer you can configure Mappings through annotations, API or XML do to such things as excluding fields, type casting, etc.

Mock mapping:

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

You can, for example, exclude ids, or maybe copy permUser.id to all of the cloned bidirectional relationships back to User (if there is one), etc.

Also, notice that cloning collections is a cumulative operation by default.

From Dozer documentation:

If you are mapping to a Class which has already been initialized, Dozer will either 'add' or 'update' objects to your List. If your List or Set already has objects in it dozer checks the mapped List, Set, or Array and calls the contains() method to determine if it needs to 'add' or 'update'.

I've used Dozer in several projects, for example, in one project there were a JAXB layer that needed to be mapped to a JPA model layer. They were close enough, but unfortunately I couldn't bend neither. Dozer worked quite well, was easy to learn and spare me from writing 70% of the boring code. I can deeply clone recommend this library out of personal experience.

Anthony Accioly
  • 21,918
  • 9
  • 70
  • 118
0

From a pure OO perspective it does not really make sense for an instance to morph from one type into another, Hibernate or not. It sounds like you might want to reconsider the object model independently of its database representation. FourWD seems more like a property of a car than a specialization, for example.

Judge Mental
  • 5,209
  • 17
  • 22
-1

A good way to model this is to create something like a UserData class such that TempUser has-a UserData and PermUser has-a UserData. You could also make TempUser has-a PermUser, though that's going to be less clear. If your application needs to use them interchangeably (something you'd get with the inheritance you were using), then both classes can implement an interface that returns the UserData (or in the second option, getPermUser, where PermUser returns itself).

If you really want to use inheritance, easiest might be to map it using the "Table per class hierarchy" and then using straight JDBC to update the discriminator column directly.

David Weinberg
  • 543
  • 3
  • 9