Use Java Enums with JPA
Java Persistence API (JPA) is a widely used standard for object-relational mapping (ORM) in Java applications. One of the common challenges when working with JPA is mapping database columns to Java enums. This can be especially challenging if the database column is not a string, such as when using an integer or a byte as the database column type. In this article, we will explore how to use Java enums with JPA.
JPA provides two basic ways to map Java enums to database columns:
Mapping Java enums to database columns
Before we dive into the details of how to use Java enums with JPA, let's first discuss how JPA maps Java enums to database columns.JPA provides two basic ways to map Java enums to database columns:
- Ordinal-based mapping: This maps the enum's ordinal value (the position of each enum value in the enum declaration) to the database column. For example, if we have an enum named Status with values ACTIVE, INACTIVE, and SUSPENDED, ACTIVE will have an ordinal value of 0, INACTIVE will have an ordinal value of 1, and so on.
- Name-based mapping: This maps the enum's name to the database column. For example, if we have an enum named Status with values ACTIVE, INACTIVE, and SUSPENDED, the database column will store the name of the selected enum value.
Using Java enums with JPA
To use Java enums with JPA, follow these steps:- Declare an enum in your Java code. For example, consider the following Status enum:javaCopy code
public enum Status { ACTIVE, INACTIVE, SUSPENDED }
- Declare a field in your JPA entity class that uses the enum type. For example, consider the following entity class:javaCopy code
Note that the status field is declared with the @Enumerated annotation, which tells JPA to use name-based mapping.@Entity @Table(name = "employees") public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @Enumerated(EnumType.STRING) private Status status; // getters and setters }
- Use the enum field in your JPA queries and embed the enum directly in the query. For example, use the Status.ACTIVE enum to find all active employees.
public interface EmployeeRepository extends JpaRepository<Employee, Long> { @Query("SELECT e from Employee e WHERE e.status = com.example.om.type.Status.ACTIVE") List<Employee> fetchActiveEmployees(); }
But persisting long enum strings and ordinal positions in a database has its challenges. You could add identifiers and codes to your enums to make it more robust but then you have the challenge of mapping to/from the enum and adding additional Transient fields to an object for the mapping just feels klunky.
Luckily, Spring has a sleek and useful alternative called AttributeConverter. First, first extend our Status enum by giving it an identifier and title.
public enum Status {
INACTIVE(0, "Inactive"),
ACTIVE(1, "Active"),
SUSPENDED(2, "Suspended");
private final Integer id;
private final String title;
private EmployeeStatus(final Integer d, final String title) {
this.id = id;
this.title = title;
}
// getters
}
Now we implement an AttributeConverter for the Status class and override two methods.
- convertToDatabaseColumn. This override tells Spring and JPA which field to persist in the database.
- convertToEntityAttribute. This function allows Spring and JPA to quickly translate the stored database field to a valid enum value.
@Converter(autoApply = true)
public class EmployeeStatusConverter implements AttributeConverter<Status, Integer> {
@Override
public Integer convertToDatabaseColumn(Status status) {
if (status == null) {
return null;
}
return status.getId();
}
@Override
public EmployeeStatus convertToEntityAttribute(Integer id) {
if (id == null) {
return null;
}
return Stream.of(Status.values()).filter(c -> c.getId().equals(id)).findFirst().orElseThrow(IllegalArgumentException::new);
}
}
Finally, annotate the entity field with @Convert to tell it to use the new EmployeeStatus attribute converter.
@Entity
@Table(name = "employees")
public class Employee {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
private String name;
@Convert(converter = EmployeeStatusConverter.class)
private Status status;
// getters and setters
}
And that's it. A very clean approach without the additional overhead of transient attributes.
Comments
Post a Comment