generator-jhipster icon indicating copy to clipboard operation
generator-jhipster copied to clipboard

Add Enum JPA AttributeConverter for key value enumeration declared in jdl model

Open ghost opened this issue 5 years ago • 15 comments
trafficstars

Overview of the feature request

The jdl allows you to declare enum with only name or optional string values. In the all cases the domain entity enum field is annotated with @Enumerated(EnumType.STRING), so the value saved on the sql database is the string enum name. It could be great to have a converter in order to write the enum value in the db.

Motivation for or Use Case

It could be useful to have this feature that extends the @Enumerated behavior (String or ordinal).

Related issues or PR
  • [x] Checking this box is mandatory (this is just to show you read everything)

ghost avatar Oct 08 '20 12:10 ghost

If we want, I can develop this feature.

ghost avatar Oct 12 '20 12:10 ghost

Can you provide an example? I don't get what's the issue here. Thanks

MathieuAA avatar Oct 13 '20 16:10 MathieuAA

@MathieuAA Ok. For example I have this jdl model:

enum Country {
  BELGIUM (MyBelgium),
  FRANCE (MyFrance),
  ITALY (MyItaly)
}

Person {
  name String,
  contry Country
}

Now jhipster generates the entity domain class in this way:

@Entity
@Table(name = "jhi_person")
public class Person  implements Serializable {

.....
@Enumerated(EnumType.STRING)
private Country country;
.....



The Enumerated annotation means that the field is saved on db with a toString of the enum name. For example for Country.BELGIUM we will have in the database the "BELGIUM" string name and not the "MyBelgium" (that is my enum value).

My feature is to generate an AttributeConverter class for every enum, that allows you to save on db the enum value. So for the entity in this case we will have:

@Entity
@Table(name = "jhi_person")
public class Person  implements Serializable {

.....
@Convert(converter = MyAttributeConverter.class)
private Country country;
.....


The attribute convert class will be generated under the enumeration package, and for example it will be as:

/**
 * The <%= enumName %>AttributeConverter class.
 */
@Converter
public class <%= enumName %>AttributeConverter implements AttributeConverter<<%= enumName %>, String> {

    @Override
    public String convertToDatabaseColumn(<%= enumName %> enumObject) {
        if (enumObject == null) {
            return null;
        }
        return enumObject.getValue();
    }

    @Override
    public <%= enumName %> convertToEntityAttribute(String value) {
        if (value == null) {
            return null;
        }

        return Stream.of(<%= enumName %>.values())
          .filter(c -> c.getValue().equals(value))
          .findFirst()
          .orElseThrow(IllegalArgumentException::new);
    }
}


So we could have on the database the "MyBelgium", "MyFrance" or "MyItaly" values. It is also an important feature if I want to save values with _ or number, for example with a jdl model:

enum Country {
  BELGIUM ("1_Belgium"),
  FRANCE ("3MyFrance"),
  ITALY ("_MyItaly")
}

Person {
  name String,
  contry Country
}

ghost avatar Oct 14 '20 16:10 ghost

I thought this was already the case, WDYT @mshima?

MathieuAA avatar Oct 15 '20 16:10 MathieuAA

This is probably because the changes for enums are only available on master, so not yet available for the blueprint

DanielFran avatar Oct 15 '20 16:10 DanielFran

@DanielFran, @MathieuAA you talk about this template committed on the main branch and not on the last 6.10.3 release?

ghost avatar Oct 15 '20 16:10 ghost

I have mixed feelings about this. If I had to take a position would be that it adds complexity to templates and adds an additional file for something that will be rarely used.

  • I personally don't think this useful since we are using the enum value as label, and doesn't make sense to me have it as db value.
  • I prefer hexagonal approach: create a separated PO that maps to the enum value. Related to https://github.com/jhipster/generator-jhipster/issues/12702 and https://github.com/jhipster/generator-jhipster/issues/12707.

IMO a way to add more information to enums and enum values will be required for this purpose with any kind of implementation. 👍 for adding more information to enums like:

BELGIUM (Belgium) as MyBelgium
@persistedValue(MyBelgium)
BELGIUM (Belgium)

mshima avatar Oct 15 '20 16:10 mshima

Just my opinion, I never liked this approach. If use Converter then might need to use JsonSerializer/etc, you just bring yourself more trouble.

Blackdread avatar Oct 16 '20 03:10 Blackdread

I have mixed feelings about this. If I had to take a position would be that it adds complexity to templates and adds an additional file for something that will be rarely used

Agree with @mshima : not sure it will be used a lot

pascalgrimaud avatar Oct 16 '20 05:10 pascalgrimaud

In a recent project, I preferred to map java enums to Postgresql enums using hibernate-types lib and I added an integration test to validate that both sides are sync'ed.

gmarziou avatar Oct 16 '20 07:10 gmarziou

@Blackdread the jackson mapping it is not required because the request is the same and value is parsed in all cases as a string (the converter is in the jpa repository layer).

@gmarziou I think that it is more complex to use hibernate-types or postgresql enums instead of a simple separeted converter class with generic jpa annotations already added in the microservice with sql database.

@pascalgrimaud ok, so we don't need this feature? In that case, we can consider resolved this issue. Thanks.

ghost avatar Oct 22 '20 07:10 ghost

jackson mapping -> depends I often saw this kind of enum definition that then use the string value of the custom variable of the enum then you need the jackson serializers (when also used in REST api, etc, then also need to define the Converter interface from Spring, even more thing to define...). Which is why I don't like, it just brings extra trouble just to have mismatch between the ENUM name and a custom value.

Blackdread avatar Oct 22 '20 07:10 Blackdread

@Blackdread don't worry, the simple converter template for the porpuose is the following, and you don't need to use jackson serializer/deserializer:

/**
 * The <%= enumName %>AttributeConverter class.
 */
@Converter
public class <%= enumName %>AttributeConverter implements AttributeConverter<<%= enumName %>, String> {

    @Override
    public String convertToDatabaseColumn(<%= enumName %> enumObject) {
        if (enumObject == null) {
            return null;
        }
        return enumObject.getValue();
    }

    @Override
    public <%= enumName %> convertToEntityAttribute(String value) {
        if (value == null) {
            return null;
        }

        return Stream.of(<%= enumName %>.values())
          .filter(c -> c.getValue().equals(value))
          .findFirst()
          .orElseThrow(IllegalArgumentException::new);
    }
}

It would be automatically genereted by jhipster, and the user should not implement anything. For the REST api I don't think that you need a serializer, because the parameter is always a String in any case, and the conversion mapping is performed in the JPA layer.

ghost avatar Oct 22 '20 07:10 ghost

This issue is stale because it has been open 30 days with no activity. Our core developers tend to be more verbose on denying. If there is no negative comment, possibly this feature will be accepted. We are accepting PRs :smiley:. Comment or this will be closed in 7 days

github-actions[bot] avatar Nov 22 '20 00:11 github-actions[bot]

An example of an old implementation. Is this something like this you wanted to implement @amanganiello90?

I had no issues with rest apis @Blackdread.

I needed to implement it because the enumerate values were already defined... What do you think @gmarziou @mshima @pascalgrimaud?

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.stream.Stream;

/**
 * The EntityFeedback enumeration.
 */
public enum EntityFeedback {
    ACTIVE_SUBSTANCE("active-substance"),
    THERAPEUTIC_REGIME("therapeutic-regime"),
    OUTCOME("outcome"),
    SYMPTOM("symptom");

    private final String value;

    EntityFeedback(String value) {
        this.value = value;
    }

    @JsonCreator
    public static EntityFeedback decode(final String value) {
        return Stream.of(EntityFeedback.values()).filter(targetEnum -> targetEnum.value.equals(value)).findFirst().orElse(null);
    }

    @JsonValue
    public String getValue() {
        return value;
    }

    @Override
    public String toString() {
        return this.value;
    }
}
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * Configure the converter for the Entity Feedback Enum.
 */
@Configuration
public class EntityFeedbackConfiguration implements WebMvcConfigurer {

    public EntityFeedbackConfiguration() {
        super();
    }

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringToEntityFeedbackEnumConverter());
    }
}
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import uc.dei.mse.supportivecare.domain.enumeration.EntityFeedback;

/**
 * Convert String to Entity Feedback Enum.
 */
@Component
public class StringToEntityFeedbackEnumConverter implements Converter<String, EntityFeedback> {

    @Override
    public EntityFeedback convert(String source) {
        return EntityFeedback.decode(source);
    }
}
import java.util.Optional;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import org.springframework.stereotype.Component;
import uc.dei.mse.supportivecare.domain.enumeration.EntityFeedback;

/**
 * {@code AttributeConverter<EntityFeedback, String>}. Implements the following methods :
 * <ul>
 * <li>convertToDatabaseColumn : (given an Enum returns a String)
 * <li>convertToEntityAttribute : (given a String returns an Enum)
 * </ul>
 */
@Component
@Converter(autoApply = true)
public class EntityFeedbackEnumToAttributeConverter implements AttributeConverter<EntityFeedback, String> {

    @Override
    public String convertToDatabaseColumn(final EntityFeedback entityFeedback) {
        return Optional.ofNullable(entityFeedback).map(EntityFeedback::getValue).orElse(null);
    }

    @Override
    public EntityFeedback convertToEntityAttribute(final String dbEntityFeedback) {
        return EntityFeedback.decode(dbEntityFeedback);
    }
}
import java.io.Serializable;
import javax.persistence.*;
import javax.validation.constraints.*;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import uc.dei.mse.supportivecare.domain.enumeration.EntityFeedback;

/**
 * Feedback.
 */
@Entity
@Table(name = "feedback")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Feedback extends AbstractAuditingEntity implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "gen_feedback_id_seq")
    @SequenceGenerator(name = "gen_feedback_id_seq", sequenceName = "feedback_id_seq", initialValue = 1, allocationSize = 1)
    private Long id;

    /**
     * Tipo da entidade.
     */
    @NotNull
    @Column(name = "entity_type", nullable = false)
    private EntityFeedback entityType;

(...)
import org.springframework.data.jpa.repository.*;
import org.springframework.stereotype.Repository;
import uc.dei.mse.supportivecare.domain.Feedback;
import uc.dei.mse.supportivecare.domain.enumeration.EntityFeedback;

/**
 * Spring Data SQL repository for the Feedback entity.
 */
@SuppressWarnings("unused")
@Repository
public interface FeedbackRepository extends JpaRepository<Feedback, Long>, JpaSpecificationExecutor<Feedback> {
    void deleteByEntityTypeAndEntityId(EntityFeedback entityFeedback, Long entityId);

    void deleteAllBySolvedIsTrue();
}
export enum EntityFeedback {
  ACTIVE_SUBSTANCE = 'active-substance',

  THERAPEUTIC_REGIME = 'therapeutic-regime',

  OUTCOME = 'outcome',

  SYMPTOM = 'symptom',
}

DanielFran avatar May 06 '22 17:05 DanielFran

This issue is stale because it has been open for too long without any activity. Due to the moving nature of jhipster generated application, bugs can become invalid. If this issue still applies please comment otherwise it will be closed in 7 days

github-actions[bot] avatar Mar 04 '24 00:03 github-actions[bot]