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.

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:
  1. 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.

  2. 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.
By default, JPA uses ordinal-based mapping. However, this can be changed by adding the @Enumerated annotation to the entity class. Using the ordinal position of the enum type is not good programming practice, since you could add a new enum in the middle which would break all your existing persisted data. 

Using Java enums with JPA

To use Java enums with JPA, follow these steps:
  1. Declare an enum in your Java code. For example, consider the following Status enum:javaCopy code
    
    public enum Status {
    	ACTIVE, INACTIVE, SUSPENDED
    }
    
    
  2. Declare a field in your JPA entity class that uses the enum type. For example, consider the following entity class:javaCopy code
    
    @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
    }
    
    
    Note that the status field is declared with the @Enumerated annotation, which tells JPA to use name-based mapping.

  3. 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();
    	
    }
    
    
Here, we're using the Status.ACTIVE value to match the status field in the database. Because we're using name-based mapping, JPA will automatically convert the Status.ACTIVE enum value to the string "ACTIVE" in the database query.

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.
  1. convertToDatabaseColumn. This override tells Spring and JPA which field to persist in the database.
  2. 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.

Conclusion

Using Java enums with JPA is a powerful and flexible way to store and retrieve data in your database. By following the steps outlined in this article, you can easily map your enums to database columns and use them in your JPA queries. Remember to choose the appropriate mapping strategy based on your needs, and to test your queries thoroughly to ensure that they're working as expected.

Comments

Popular posts from this blog

Max Upload File Size in Spring Framework

Spring Security part 5 - Freemarker Security Tags