Pull to refresh

Spring Data JPA entity enum converter

Reading time4 min
Views5.4K
Want an entity enum converter!?
Want an entity enum converter!?

Faced with situation when had to map STRING VALUE coming from database TO ENUM. The problem happens when value stored in database differs from enum name. And here we need a workaround to make a mapping. I will describe main points on how I was able to realize it.


AttributeConverter can be used to convert an entity attribute to a database value and vice versa!

If you ever worked with Java enums in JPA you are definitely aware of their limitations and traps. Using Enum as a property of your @Entity is often very good choice, however JPA prior to 2.1 didn’t handle them very well.

Dependencies needed for this case are below. You can adjust versions and scopes with regards to your project case and dependency requirements.

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-core</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
        </dependency>

Now let's introduce AttributeConverter interface implementation to be able to write our own realization of mapping. Contract contains 2 methods:

  1. convertToDatabaseColumn - to get String from Enum;

  2. convertToEntityAttribute - to get Enum from String.

import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;

import javax.persistence.AttributeConverter;
import java.util.concurrent.atomic.AtomicReference;

@Slf4j
public class OperationTypeConverter implements AttributeConverter<OperationType, String> { // need to implement AttributeConverter interface 

    @Override
    public String convertToDatabaseColumn(@NonNull OperationType operation) { // Lombok @NonNull to prevent NPE
        return operation.getOperationType(); // get operation type as STRING from enum 
    }

    @Override
    public OperationType convertToEntityAttribute(@NonNull String operationType) { // Lombok @NonNull to prevent NPE
        
        Flux<OperationType> operationTypes = Flux.just(OperationType.values()); // start Flux of all string values from enum for mapping 

        AtomicReference<OperationType> mappedOperation = new AtomicReference<>(); // instead of block operation on Reactive. See https://habr.com/en/articles/741464/ 

        operationsTypes
                .parallel() // we can run in parallel here as there is no dependency of data in runtime
                .runOn(Schedulers.fromExecutor(Executors.newWorkStealingPool()))
                .filter(value -> value.getOperationType().equals(operationType)) // filter by incoming value
                .subscribe(mappedDctOperation::set, // set the mappedOperation AtomicReference field
                        throwable -> { // to throw the problem 
                            log.error("Error during converter execution for operation type {}.", operationType); // logging is important, especially on debug level
                            throw new OperationTypeConverterException(throwable, operationType); // dedicated exception class
                        });

        return mappedOperation.get();
    }
}

In convertToEntityAttribute you can see how mapping is done via filtration by values using Spring Reactor Flux. In this case it is used parallel run, and Flux by default work in ASYNC mode. If you don't need concurrent parallel execution you can remove these lines:

.parallel() .runOn(Schedulers.fromExecutor(Executors.newWorkStealingPool()))

Removing this option will switch your Flux to default sequential asynchronous mode.

You can also set the AttributeConverter to work automatically using annotation on converter class:

@Converter(autoApply = true)

This way JPA will automatically apply the conversion logic to all mapped attributes of a your ENUM type.

But in this case I put @Converter annotation directly on the DatabaseOperation entity's field.

On below screenshot you can see special ICON on the left bar near the class declaration. Just click on it - and IDEA will navigate you to entity in which this converter is used:

You can click on left ICON to open field in Entity which use this converter.
You can click on left ICON to open field in Entity which use this converter.

OperationType enum contains 2 options. Important! Name of ENUM not matches to value which stored in database. And here our converter comes to help us in mapping between DB and ENUM!

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum OperationType {

    READ("optimized read"),
    WRITE("turbo write");
  
    private final String operationType;
}

DatabaseOperation entity contains operationType field on which we will enable our functional of AttributeConverter. To enable it - just put annotation @Converter(converter = OperationTypeConverter.class) on field.

import lombok.*;
import org.hibernate.Hibernate;

import javax.persistence.*;
import java.util.Objects;

@Getter
@Setter
@ToString
@NoArgsConstructor
@Entity
@Table
public class DatabaseOperation {

    @Id
    @Column
    private Long id;

    // rest fields...
  
    @Column
    @Convert(converter = OperationTypeConverter.class) // to enable our AttributeConverter
    private OperationType operationType; // the golden field :)

    @Override
    public boolean equals(Object o) { // safe reazation of equals for entity
        if (this == o) return true;
        if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
        DatabaseOperation that = (DatabaseOperation) o;
        return Objects.equals(id, that.id);
    }

    @Override
    public int hashCode() { // safe reazation of hashCode for entity
        return getClass().hashCode();
    }
}

Here I also override equals and hashCode to make it safe for JPA calls during runtime execution.


Hope I described my case well with examples which you can use in your project. AttributeConverter are really helpful thing for mapping values from DB to Java enum when working with Spring Data JPA and Hibernate.

If you have what to say about the topic and want to give negative rating - please feel free to write your comments to understand your opinion. Otherwise it is not serious and it is not clear why you vote negatively.

And of course If you like this article, please vote UP - this will support me to write more such posts with real code examples!

Tags:
Hubs:
Rating0
Comments0

Articles