tags:

views:

12374

answers:

5

What is the smartest way to get an entity with a field of type List get persisted?

Command.java

package persistlistofstring;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Basic;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Persistence;

@Entity
public class Command implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    Long id;
    @Basic
    List<String> arguments = new ArrayList<String>();

    public static void main(String[] args) {
        Command command = new Command();

        EntityManager em = Persistence
                .createEntityManagerFactory("pu")
                .createEntityManager();
        em.getTransaction().begin();
        em.persist(command);
        em.getTransaction().commit();
        em.close();

        System.out.println("Persisted with id=" + command.id);
    }
}


This code produces:

> Exception in thread "main" javax.persistence.PersistenceException: No Persistence provider for EntityManager named pu: Provider named oracle.toplink.essentials.PersistenceProvider threw unexpected exception at create EntityManagerFactory: 
> oracle.toplink.essentials.exceptions.PersistenceUnitLoadingException
> Local Exception Stack: 
> Exception [TOPLINK-30005] (Oracle TopLink Essentials - 2.0.1 (Build b09d-fcs (12/06/2007))): oracle.toplink.essentials.exceptions.PersistenceUnitLoadingException
> Exception Description: An exception was thrown while searching for persistence archives with ClassLoader: sun.misc.Launcher$AppClassLoader@11b86e7
> Internal Exception: javax.persistence.PersistenceException: Exception [TOPLINK-28018] (Oracle TopLink Essentials - 2.0.1 (Build b09d-fcs (12/06/2007))): oracle.toplink.essentials.exceptions.EntityManagerSetupException
> Exception Description: predeploy for PersistenceUnit [pu] failed.
> Internal Exception: Exception [TOPLINK-7155] (Oracle TopLink Essentials - 2.0.1 (Build b09d-fcs (12/06/2007))): oracle.toplink.essentials.exceptions.ValidationException
> Exception Description: The type [interface java.util.List] for the attribute [arguments] on the entity class [class persistlistofstring.Command] is not a valid type for a serialized mapping. The attribute type must implement the Serializable interface.
>         at oracle.toplink.essentials.exceptions.PersistenceUnitLoadingException.exceptionSearchingForPersistenceResources(PersistenceUnitLoadingException.java:143)
>         at oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider.createEntityManagerFactory(EntityManagerFactoryProvider.java:169)
>         at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:110)
>         at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:83)
>         at persistlistofstring.Command.main(Command.java:30)
> Caused by: 
> ...
+6  A: 
toolkit
+9  A: 

Lists of objects inside a model object are generally considered "OneToMany" relationships with another object. However, a String is not (by itself) an allowable client of a One-to-Many relationship, as it doesn't have an ID.

So, you should convert your list of Strings to a list of Argument-class JPA objects containing an ID and a String. You could potentially use the String as the ID, which would save a little space in your table both from removing the ID field and by consolidating rows where the Strings are equal, but you would lose the ability to order the arguments back into their original order (as you didn't store any ordering information).

Alternatively, you could convert your list to @Transient and add another field (argStorage) to your class that is either a VARCHAR() or a CLOB. You'll then need to add 3 functions: 2 of them are the same and should convert your list of Strings into a single String (in argStorage) delimited in a fashion that you can easily separate them. Annotate these two functions (that each do the same thing) with @PrePersist and @PreUpdate. Finally, add the third function that splits the argStorage into the list of Strings again and annotate it @PostLoad. This will keep your CLOB updated with the strings whenever you go to store the Command, and keep the argStorage field updated before you store it to the DB.

I still suggest doing the first case. It's good practice for real relationships later.

Bill James
Changing from ArrayList<String> to String with comma separated values worked for me.
ChrisAD
But this forces you to use (imho) ugly like statements when querying that field.
Willi
Yes, as I said... do the first option, it's better. If you just can't bring yourself to do it, option 2 can work.
Bill James
A: 

When using the Hibernate implementation of JPA , I've found that simply declaring the type as an ArrayList instead of List allows hibernate to store the list of data.

Clearly this has a number of disadvantages compared to creating a list of Entity objects. No lazy loading, no ability to reference the entities in the list from other objects, perhaps more difficulty in constructing database queries. However when you are dealing with lists of fairly primitive types that you will always want to eagerly fetch along with the entity, then this approach seems fine to me.

@Entity
public class Command implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    Long id;

    ArrayList<String> arguments = new ArrayList<String>();


}
Thanks. This work with all JPA implementations, Arraylist is Serializable is saved in a BLOB field.The problems with this method are that 1) the BLOB size is fixed 2) you can search or index the array elements 3) only a client aware about the Java serialization format can read these elements.
Andrea Francia
+4  A: 

Use some JPA 2 implementation: it adds a @CollectionOfElements annotation, similar to the Hibernate one, that does exactly what you need. There's one example here: link text.

Thiago H. de Paula Figueiredo
Page not found!
HappyCoder
should be @ElementCollection
Dapeng
+1  A: 

I had the same problem so I invested the possible solution given but at the end I decided to implement my ';' separated list of String.

so I have

// a ; separated list of arguments
String arguments;

public List<String> getArguments() {
    return Arrays.asList(arguments.split(";"));
}

This way the list is easily readable/editable in the database table;

Anthony
This is totally valid but consider the growth of your application and the schema evolution. Sometime in the (near) future you might eventually switch to the entity based approach.
Willi
I agree, that is totally valid. However, I do suggest to fully review the logic as well as the implementation of the code. If String `arguments` is a list of access permissions, then having a special character, a `separator`, might be vulnerable for privilege escalation attacks.
Harry Pham