8

I'm trying to do the following using Hibernate:

  • When an object is saved to the DB, it is saved as a the value of the enum's name property.
  • When an object is retrieved from the DB, the object reads in the string from the database, and instantiates the enum by the value of the enum's name property.

Let me expound.

This is the enum:

public enum Position {

    UNSPECIFIED(1L, "Unspecified"),
    SPECIALIST(2L, "Specialist"),
    NOT_SPECIALIST(3L, "Not Specialist");

    private Long id;
    private String name;

    Position(Long id, String name) {
        this.id = id;
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public static Position from(Long id) {
        for(Position position: values()) {
            if(position.getId().equals(id))
                return position;
        }

        throw new IllegalArgumentException("Cannot get position for ID " + id);
    }

    public static Position from(String name) {
        for(Position position: values()) {
            if(position.getName().toUpperCase().equals(name.toUpperCase()))
                return position;
        }

        throw new IllegalArgumentException("No such position " + name);
    }
}

This is a class that uses the enum:

@Entity(name = "worker")
public class Worker {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String firstName;
    private String lastName;
    private Position position;
    private String email;
}

This is how the data appears in the database:

worker
id      first_name      last_name       position        email
0       Adam            Applegate       Unspecified     adam@company.com
1       Bob             Barrett         Specialist      bob@company.com

So basically, when the I call workerRepository.findById(0); the worker object that I get back have the following values:

id --> 0
firstName --> Adam
lastName --> Applegate
position --> Position.UNSPECIFIED
email --> adam@company.com

Now, let's say I create a new worker object with the following values:

firstName --> Chad
lastName --> Carlton
position --> Position.NOT_SPECIFIED
email --> chad@company.com

After calling workerRepository.save(newWorker); the database should look like this:

id      first_name      last_name       position        email
0       Adam            Applegate       Unspecified     adam@company.com
1       Bob             Barrett         Specialist      bob@company.com
2       Chad            Carlton         Not Specialist  chad@company.com

Note that the position column has the value of Position.NOT_SPECIALIST.getName().

Is there any way to do this in hibernate?

teuber789
  • 1,527
  • 16
  • 28
  • 2
    I think you want an `AttributeConverter`. – chrylis -cautiouslyoptimistic- Jan 13 '17 at 01:07
  • @chrylis a quick google search showed that AttributeConverter could be exactly what I'm looking for! I'll have more time to look at it closer later, so I'll let you know how it goes. – teuber789 Jan 13 '17 at 03:57
  • Do note, by the way, that this will have terrible search performance unless you can use a database enum type (I know Postgres supports this). – chrylis -cautiouslyoptimistic- Jan 13 '17 at 04:07
  • Does the performance hit occur every time the workerRepository is queried? Or would it only happen if I queried by the position property? (i.e., where position='Unspecified') – teuber789 Jan 13 '17 at 05:29
  • It's just a normal ordinary string column with the associated performance issues. Unlikely to be much of an issue except on query (but keep in mind it'll also take up extra space; your database might externalize it, for example). – chrylis -cautiouslyoptimistic- Jan 13 '17 at 05:37
  • K, I'll keep that in mind. So far, the queries seem to be coming back lightning fast, even with an AttributeConverter in place. – teuber789 Jan 13 '17 at 06:57
  • Btw, an AttributeConverter was exactly what I needed. It worked like a charm :) If you would like, you can write that as an answer and I'll mark it correct. – teuber789 Jan 13 '17 at 06:58

2 Answers2

6

As @chrylis suggested, the answer to my problem was to use an AttributeConverter, like so:

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

@Converter(autoApply = true)
public class PositionConverter implements AttributeConverter<Position, String> {

    @Override
    public String convertToDatabaseColumn(Position attribute) {
        return attribute.getName();
    }

    @Override
    public VolleyballPlayerPosition convertToEntityAttribute(String dbData) {
        return Position.from(dbData);
    }
}
teuber789
  • 1,527
  • 16
  • 28
0

I suggest to avoid AttributeConverter and go for simple @Enumerated(STRING).

Obviously, this is only applicable if you can stand UPPERCASE and underscored position values.

public enum Position 
{
    UNSPECIFIED,
    SPECIALIST,
    NOT_SPECIALIST;
}

@Entity(name = "worker")
public class Worker 
{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String firstName;

    private String lastName;

    @NotNull
    @Enumerated(STRING)
    @Column(nullable = false)
    private Position position = Position.UNSPECIFIED;

    private String email;
}

worker
id      first_name      last_name       position        email
0       Adam            Applegate       UNSPECIFIED     adam@company.com
1       Bob             Barrett         SPECIALIST      bob@company.com

It's also unclear why you need Position.getId() while you already have Position.ordinal(): your Position.from(id) is equivalent to Position.values()[id].

Finally, it's not a good practice to declare "labels" hardcoded in enums (in compiled code, generally): just use a ResourceBundle, you'll be ready for i18n.

Michele Mariotti
  • 7,372
  • 5
  • 41
  • 73
  • Unfortunately, that would mean that "UNSPECIFIED" would be written to the database instead of "Unspecified". As it is, the values defined by the position.name property already exist in the DB, and I can't change them. This is why I need to get hibernate to key off of the position.name value. – teuber789 Jan 14 '17 at 00:41
  • As far as the ```Position.ordinal()``` goes, the position.id property exists bc the ordinal value is one off: ```Position.values[1]``` would return ```Position.SPECIALIST``` when you would expect ```Position.UNSPECIFIED```, etc. So instead of doing ```Position.values[1 - 1]``` we've decided to do it this way. It's the easiest way when considering the constraints of the system that I'm working with. – teuber789 Jan 14 '17 at 00:47