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.