views:

40

answers:

1

Currently, my project uses @Enumerated(EnumType.ORDINAL), so when I sort by this column, it is ordering based on the order in the enum, which works fine. But I need to add some additional values to the enum, which need to be inserted at different locations in the list of enum values and can't be just added to the bottom to maintain the correct sort order.

If I do this, my database will be messed up. I'll have to write some scripts to translate all these ordinal values to the correct new ordinal. There is a possibility that more status will have to be added later. Since I have to fix all the data in the database, I'd like to only have to do it once, as it will be a big task.

So I'm thinking of switching to EnumType.STRING to not have to remap ordinal values in the database again. But if I do this, then how do I sort properly? The alphabetical order of the enum strings is not the order I need.

Using the classes below, when I sort by the property "status", the results come out in this order:

hql = "from Project order by status"

Development
Planning
Production

I'd like them to come out in this order, without using EnumType.ORDINAL:

Planning
Development
Production

Is this possible without creating a table for the enum, or adding an additional column to the Project class? I've tried this, but it throws an exception:

hql = "from Project order by status.sort"

The enum:

public enum Status {

    PLANNING("Planning", 0),
    DEVELOPMENT("Development", 1),
    PRODUCTION("Production", 2);

    private final String name;
    private final int sort;
    private Status(String name, int sort) {
        this.name = name;
        this.sort = sort;
    }
    @Override
    public String toString() {
        return name;
    }
}

The entity:

@Entity
public class Project {
    private Long id;
    private Status status;

    @Id
    @GeneratedValue
    public Long getId() {
        return this.id;
    }
    private void setId(Long id) {
        this.id = id;
    }
    @Enumerated(EnumType.STRING)
    public Status getStatus() {
        return status;
    }
    public void setStatus(Status status) {
        this.status = status;
    }
}
+1  A: 

So I'm thinking of switching to EnumType.STRING to not have to remap ordinal values in the database again. But if I do this, then how do I sort properly? The alphabetical order of the enum strings is not the order I need.

I personally totally avoid using the evil EnumType.ORDINAL as just changing the order of the constants would brake the persistence logic. Evil. That said, EnumType.STRING is indeed not always appropriate.

In your case, here is what I would do (with standard JPA): I would persist an int at the entity level and perform the enum conversion in getter/setters. Something like this. First, the enum:

public enum Status {
    PLANNING("Planning", 100),
    DEVELOPMENT("Development", 200),
    PRODUCTION("Production", 300);

    private final String label;
    private final int code;

    private Status(String label, int code) {
        this.label = label;
        this.code = code;
    }

    public int getCode() { return this.code; }

    private static final Map<Integer,Status> map;
    static {
        map = new HashMap<Integer,Status>();
        for (Status v : Status.values()) {
            map.put(v.code, v);
        }
    }
    public static Status parse(int i) {
        return map.get(i);
    }
}

So basically, the idea is to be able to get a Status by its code. And we keep some room between constants so that adding values is possible (it's not pretty but, well, it will work and should be safe for some time).

And then in the entity:

@Entity
public class Project {
    private Long id;
    private int statusCode;

    @Id @GeneratedValue
    public Long getId() {
        return this.id;
    }
    private void setId(Long id) {
        this.id = id;
    }

    @Transient
    public Status getStatus () {
        return Status.parse(this.statusCode);
    }
    public void setStatus(Status status) {
        this.statusCode = status.getCode();
    }

    protected int getStatusCode() {
        return statusCode;
    }
    protected void setStatusCode(int statusCode) {
        this.statusCode = statusCode;
    }
}

The getter/setter for the int representation are protected. The public getter/setter deal with the conversion.

An alternative solution would be to use a custom type (at the cost of portability).

Related questions

Pascal Thivent
@Pascal: Very nice, this is just the kind of solution I was hoping for. My problem seemed like the kind of thing that would require a bit of a hack, and your "hack" seems as elegant as possible. I'll give it a try. Thanks!
Tauren
@Pascal: Works perfectly! Of course, I had to change my queries to reference the property `statusCode` with values like `Status.PLANNING.getCode()` instead of `status` and `Status.PLANNING`.
Tauren
Why is `EnumType.ORDINAL` evil? It's small, it's fast. Ok, you (really) shouldn't change the order in the enum declaration, but a comment in the file would do it. `EnumType.STRING` has the same flaw, you can't change the names of the enums. Both approaches have their problems.
Willi
This technique will work fine when you add new enum values, but it won't let you reorder existing values. It also requires the database to be remapped to use the new codes. So wouldn't it be better to leave the field as-is, then implement a `Comparator<Status>` class?
gutch
@Tauren You're welcome. @Will I find it extremely dangerous (and size shouldn't be a problem nowadays) @gutch This technique solves one problem: being able to add values and to order results on the database side which is *much more efficient* than doing it on the Java side. Reordering values is of course a totally different story (but less likely IMO, e.g. PRODUCTION will never precede PLANNING). And if you don't like it, don't use it :)
Pascal Thivent
@Will each time I've used `ORDINAL` it has come back to bite me, and this was yet another instance. If I had used `STRING`, at least it wouldn't require database updates. I've found I'm more likely to need to add or remove elements in an enum than to rename them. @Pascal's solution may not be best for everyone, but its perfect for my situation.
Tauren