Home > OS >  Store ENUM value into database
Store ENUM value into database

Time:10-18

I wan to use ENUM to map values into database table rows:

BusinessCustomersSearchParams:

@Getter
@Setter
public class BusinessCustomersSearchParams {

    private String title;

    private List<String> status;

    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;
}

Specification:

@Override
public Page<BusinessCustomersFullDTO> findBusinessCustomers(BusinessCustomersSearchParams params, Pageable pageable)
{
    Specification<BusinessCustomers> spec = (root, query, cb) -> {
        List<Predicate> predicates = new ArrayList<>();
        if (params.getTitle() != null) {
            predicates.add(cb.like(cb.lower(root.get("description")), "%"   params.getTitle().toLowerCase()   "%"));
        }

        final List<String> statuses = Optional.ofNullable(params.getStatus()).orElse(Collections.emptyList());
        if (statuses != null && !statuses.isEmpty()){
            List<BusinessCustomersStatus> statusesAsEnum = statuses.stream()
                .map(status -> BusinessCustomersStatus.fromStatus(status))
                .collect(Collectors.toList())
                ;

            predicates.add(root.get("status").in(statusesAsEnum));
        }

        return cb.and(predicates.toArray(new Predicate[predicates.size()]));
    };
    return businessCustomersService.findAll(spec, pageable).map(businessCustomersMapper::toFullDTO);
}

AttributeConverter:

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

@Converter
public class BusinessCustomersStatusAttributeConverter
    implements AttributeConverter<BusinessCustomersStatus, String> {

  public String convertToDatabaseColumn( BusinessCustomersStatus value ) {
    if ( value == null ) {
      return null;
    }

    return value.getStatus();
  }

  public BusinessCustomersStatus convertToEntityAttribute( String value ) {
    if ( value == null ) {
      return null;
    }

    return BusinessCustomersStatus.fromStatus( value );
  }

}

Enum:

package org.merchant.database.service.businesscustomers;

public enum BusinessCustomersStatus {
    A("active"),
    O("onboarding"),
    N("not_verified"),
    V("verified"),
    S("suspended"),
    I("inactive");

    private String status;

    BusinessCustomersStatus(String status)
    {
        this.status = status;
    }

    public String getStatus() {
        return status;
    }

    public static BusinessCustomersStatus fromStatus(String status) {
        switch (status) {
            case "active": {
                return A;
            }

            case "onboarding": {
                return O;
            }

            case "not_verified": {
                return NV;
            }

            case "verified": {
                return V;
            }

            case "suspended": {
                return S;
            }

            case "inactive": {
                return I;
            }

            default: {
                throw new UnsupportedOperationException(
                    String.format("Unkhown status: '%s'", status)
                );
            }
        }
    }
}

Entity:

@Entity
@Table(name = "business_customers")
public class BusinessCustomers implements Serializable {
   
    ..........
    @Convert( converter = BusinessCustomersStatusAttributeConverter.class )
    private BusinessCustomersStatus status;
    ......
}

Full code example: https://github.com/rcbandit111/Search_specification_POC

I send http query with params list?size=5&page=0&status=active,suspended and I get result with capital letters "status": "ACTIVE".

I wan to search and get status from FE for status using status=active but store into database row field only symbol A.

How I can store into database the ENUM key A?

CodePudding user response:

Notice your convertToDatabaseColumn() method in BusinessCustomersStatusAttributeConverter.

It should return value.name() instead of value.getStatus().

CodePudding user response:

In order to store the actual enumeration value in the database, you can do two things.

One, as suggested by @PetarBivolarski, modify the method convertToDatabaseColumn in AttributeConverter and return value.name() instead of value.getStatus(). But please, be aware that in addition you will need to update the convertToEntityAttribute as well to take into account that change:

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

@Converter
public class BusinessCustomersStatusAttributeConverter
    implements AttributeConverter<BusinessCustomersStatus, String> {

  public String convertToDatabaseColumn( BusinessCustomersStatus value ) {
    if ( value == null ) {
      return null;
    }

    return value.name();
  }

  public BusinessCustomersStatus convertToEntityAttribute( String value ) {
    if ( value == null ) {
      return null;
    }

    return BusinessCustomersStatus.valueOf( value );
  }

}

If you think about it, a more straightforward solution will be just keep the status field as @Enumerated:

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder(toBuilder = true)
@Entity
@Table(name = "business_customers")
public class BusinessCustomers {

    //...

    @Enumerated(EnumType.STRING)
    @Column(name = "status", length = 20)
    private BusinessCustomersStatus status;

    //...
}

It is in addition more according to the rest of your code.

Regarding your second problem, the application is returning "status":"ACTIVE" because in BusinessCustomersFullDTO you are defining the status field as String and this field receives the result of the mapping process performed by @Mapstruct and BusinessCustomersMapper.

To solve that issue, as I suggested you previously, you can modify your Mapper to handle the desired custom conversion:

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
import org.merchant.config.BaseMapperConfig;
import org.merchant.database.entity.BusinessCustomers;
import org.merchant.database.service.businesscustomers.BusinessCustomersStatus;
import org.merchant.dto.businesscustomers.BusinessCustomersFullDTO;

@Mapper(config = BaseMapperConfig.class)
public interface BusinessCustomersMapper {

    @Mapping(source = "status", target = "status", qualifiedByName = "businessCustomersToDTOStatus")
    BusinessCustomersFullDTO toFullDTO(BusinessCustomers businessCustomers);


    @Named("busineessCustomersToDTOStatus")
    public static String businessCustomersToDTOStatus(final BusinessCustomersStatus status) {
        if (status == null) {
            return null;
        }

        return status.getStatus();
    }
}

If you do not prefer this solution, perhaps you can take a different approach: it will consist in the following. The idea is modifying the Jackson serialization and deserialization behavior of BusinessCustomersFullDTO. In fact, in your use case only is necessary to modify the serialization logic.

First, define the status field in BusinessCustomersFullDTO in terms of BusinessCustomersStatus as well:

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder(toBuilder = true)
public class BusinessCustomersFullDTO {

    private long id;

    private String name;

    private String businessType;

    private BusinessCustomersStatus status;

    private String description;

    private String country;

    private String address1;
}

To complete the solution, please, perform the following changes in the BusinessCustomersStatus enum:

public enum BusinessCustomersStatus {
    A("active"),
    O("onboarding"),
    NV("not_verified"),
    V("verified"),
    S("suspended"),
    I("inactive");

    private String status;

    BusinessCustomersStatus(String status)
    {
        this.status = status;
    }

    // Define the status field as the enum representation by using @JsonValue
    @JsonValue
    public String getStatus() {
        return status;
    }

    // Use the fromStatus method as @JsonCreator
    @JsonCreator
    public static BusinessCustomersStatus fromStatus(String status) {
        if (StringUtils.isEmpty(status)) {
            return null;
        }

        switch (status) {
            case "active": {
                return A;
            }

            case "onboarding": {
                return O;
            }

            case "not_verified": {
                return NV;
            }

            case "verified": {
                return V;
            }

            case "suspended": {
                return S;
            }

            case "inactive": {
                return I;
            }

            default: {
                throw new UnsupportedOperationException(
                        String.format("Unkhown status: '%s'", status)
                );
            }
        }
    }
}

Note the inclusion of the @JsonValue and @JsonCreator annotations: the later is using for deserialization which seems unnecessary to me in your application, but just in case.

Please, see the relevant documentation of the provided Jackson annotations.

  • Related