generator-jhipster
generator-jhipster copied to clipboard
Add Enum JPA AttributeConverter for key value enumeration declared in jdl model
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)
If we want, I can develop this feature.
Can you provide an example? I don't get what's the issue here. Thanks
@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
}
I thought this was already the case, WDYT @mshima?
This is probably because the changes for enums are only available on master, so not yet available for the blueprint
@DanielFran, @MathieuAA you talk about this template committed on the main branch and not on the last 6.10.3 release?
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)
Just my opinion, I never liked this approach. If use Converter then might need to use JsonSerializer/etc, you just bring yourself more trouble.
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
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.
@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.
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 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.
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
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',
}
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