@ElementCollection of type enum

#1

Hi!

I have in an entity called User with the following field: @ElementCollection(fetch = FetchType.EAGER) private Set<UserPermission> permissions;

package x.x.x.x

public enum UserPermission {
  
    BASIC,
    COACH,
    ADMIN
  
}

Use case: there are users which contain the first 2 permissions (BASIC and COACH, so the first 2 entries).

Bug: If UserPermission enum is modified by adding, for example, BASIC231 between BASIC and COACH values, data in ObjectDB will be altered in a way that the users that previously had BASIC and COACH as permissions, now they would have BASIC and BASIC231.

Conclusion: @ElementCollection(fetch = FetchType.EAGER) private Set<UserPermission> permissions; saves enum values as ordinal, not String, therefore any modification of the order alters all entities in the DB.

Mention: I have seen this bug by checking the values in DB explorer, but also by printing logs directly in the code.

Desired case: @ElementCollection(fetch = FetchType.EAGER) containing enums should be order independent.

Thanks.

#2

By default, enum values are stored as ordinal values. This is the default in JPA, and it is also more efficient, since numbers are more efficient than strings (take less storage space and faster to save and load).

You can easily solve the problem by adding new enum values only at the end of the list.

Alternatively, if you prefer using enum values as strings, try annotating the collection with @Enumerated(STRING).

ObjectDB Support
#3

See also this section in the ObjectDB manual about using enum.

ObjectDB Support
#4

Hi!

I've already tried to use @Enumerated(STRING) on the @ElementCollection, but it didn't have any effect. It would have been nice to work.

I know how enums work, don't worry.

So, we just have to live with this limitation? 

#5

Try the following test case:

import java.util.*;

import javax.persistence.*;


public class T1319 {

    public static void main(String[] args) {
        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory(
                "objectdb:test.tmp");
       
        EntityManager em = emf.createEntityManager();
        em.getTransaction().begin();
        MyEntity entity = new MyEntity();
        entity.list = new ArrayList<MyEnum>();
        entity.list.add(MyEnum.V1);
        entity.list.add(MyEnum.V2);
        em.persist(entity);
        em.getTransaction().commit();
        em.close();
       
        em = emf.createEntityManager();
        TypedQuery<MyEntity> query =
            em.createQuery("SELECT e FROM MyEntity e", MyEntity.class);
        List<MyEntity> resultList = query.getResultList();
        for (MyEntity e : resultList) {
            System.out.println(e);
        }
        em.close();
       
        emf.close();
    }

    @Entity
    public static class MyEntity {
        @Id @GeneratedValue Long id;
       
        @ElementCollection @Enumerated(EnumType.STRING)
        ArrayList<MyEnum> list;
       
        @Override
        public String toString() {
            return "MyEntity#" + list;
        }
    }
   
    public static enum MyEnum {
        V1, V2, V3;
    }
}

You can run it several times, adding and removing values in MyEnum, and see that entity objects preserve their enum values (but values are changed when EnumType.STRING is not specified).

If you have a different scenario in which it doesn't work, please demonstrate it by changing this test case.

In general, you should always demonstrate such issues using a test case, as explained the posting instructions.

ObjectDB Support
#6

Your test seems to work all the time. Thank you and I'm happy that it works.

It's strange then why it didn't when I tried in my project. Maybe I didn't pay enough attention nor I cleaned the project before redeploying the war. Sorry for trouble.

Thank you!

Reply