#java #spring #spring-boot #enums #spring-data-jpa
Вопрос:
Я хочу использовать ПЕРЕЧИСЛЕНИЕ для отображения значений в строки таблицы базы данных:
Поисковые параметры бизнес-клиентов:
@Getter
@Setter
public class BusinessCustomersSearchParams {
private String title;
private List<String> status;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
Спецификация:
@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 amp;amp; !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);
}
Преобразователь атрибутов:
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
@Table(name = "business_customers")
public class BusinessCustomers implements Serializable {
..........
@Convert( converter = BusinessCustomersStatusAttributeConverter.class )
private BusinessCustomersStatus status;
......
}
Полный пример кода: https://github.com/rcbandit111/Search_specification_POC
Я отправляю http-запрос с параметрами list?size=5amp;page=0amp;status=active,suspended
и получаю результат с заглавными буквами «статус»: «АКТИВЕН».
Я хочу выполнить поиск и получить статус от FE для статуса, используя status=active
, но сохраняя в поле строки базы данных только символ A.
Как я могу сохранить в базе данных ключ перечисления A?
Комментарии:
1. Обычно для сохранения «имени» перечисления в базе
@Enumerated(EnumType.STRING)
данных над свойством перечисленияprivate BusinessCustomersStatus status;
в Сущности используется следующая аннотация.BusinessCustomersStatusAttributeConverter
По какой причине вы создали пользовательский конвертер?2. Просто измените
ACTIVE("active")
наACTIVE("ACTIVE")
, и аналогично для всех остальных элементов. Или вообще избавьтесь отstatus
элемента, так как вы, похоже, не хотите его использовать, иname()
вместо этого используйте атрибут. Вы приложили немало усилий, чтобы создать отображение, которое вам, похоже, на самом деле не нужно. Почему?3. Я обновил сообщение. Не могли бы вы дать мне совет, пожалуйста?
Ответ №1:
Чтобы сохранить фактическое значение перечисления в базе данных, вы можете сделать две вещи.
Один из них, как предложил @PetarBivolarski, изменяет метод convertToDatabaseColumn
AttributeConverter
и возвращает value.name()
вместо value.getStatus()
. Но, пожалуйста, имейте в виду, что кроме того, вам также потребуется обновить convertToEntityAttribute
, чтобы учесть это изменение:
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 );
}
}
Если вы подумаете об этом, более простым решением будет просто сохранить status
поле как @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;
//...
}
Это, кроме того, больше соответствует остальной части вашего кода.
Что касается вашей второй проблемы, приложение возвращается "status":"ACTIVE"
, потому BusinessCustomersFullDTO
что вы определяете поле состояния как String
, и это поле получает результат процесса сопоставления @Mapstruct
, выполненного BusinessCustomersMapper
и.
Чтобы решить эту проблему, как я предлагал вам ранее, вы можете изменить свой Mapper
, чтобы обработать желаемое пользовательское преобразование:
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();
}
}
Если вы не предпочитаете это решение, возможно, вы можете использовать другой подход: он будет заключаться в следующем. Идея заключается в изменении поведения сериализации и десериализации Джексона BusinessCustomersFullDTO
. На самом деле, в вашем случае необходимо только изменить логику сериализации.
Во-первых, определите status
поле BusinessCustomersFullDTO
в терминах BusinessCustomersStatus
также:
@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;
}
Чтобы завершить решение, пожалуйста, внесите следующие изменения в BusinessCustomersStatus
перечисление:
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)
);
}
}
}
}
Обратите внимание на включение @JsonValue
@JsonCreator
аннотаций и: последнее используется для десериализации, которая мне кажется ненужной в вашем приложении, но на всякий случай.
Пожалуйста, ознакомьтесь с соответствующей документацией по предоставленным аннотациям Джексона.
Комментарии:
1. Спасибо вам за ответ. Я думаю, что в
BusinessCustomersFullDTO
строкеprivate BusinessCustomersStatus status;
должно бытьprivate String status;
для того, чтобы выполнить поиск по нескольким значениям?2. Привет @PeterPenzov. Нет, пожалуйста, не смешивайте эти два понятия. С одной стороны, у вас есть
BusinessCustomersSearchParams
, и в этом dtostatus
поле представлено как aList
, чтобы вы могли фильтровать по разным значениям. Но здесь, уBusinessCustomersFullDTO
вас есть единственный результат. Поскольку, похоже, вы предпочитаете не менять свой картограф, просто сохраняйтеstatus
какBusinessCustomersStatus status
есть и используйте предложенные@JsonValue
и@JsonCreator
аннотации. Пожалуйста, не могли бы вы попробовать и посмотреть?
Ответ №2:
Обратите внимание на свой convertToDatabaseColumn()
метод BusinessCustomersStatusAttributeConverter
.
Он должен вернуться value.name()
вместо value.getStatus()
этого .
Комментарии:
1. Похоже, теперь мне нужно изменить что-то еще? Я
java.lang.UnsupportedOperationException: Unknown status: 'ACTIVE'
, наверное, доберусь сюда? github.com/rcbandit111/Search_specification_POC/blob/main/src/…2. Вам вообще не нужен статусный член, поэтому вы можете просто удалить его. Просто используйте метод .name() ПЕРЕЧИСЛЕНИЯ или, как правило, лучше удалите весь пользовательский конвертер и используйте аннотацию @Enumerated(EnumType. СТРОКА) как объясняет @pleft в своем комментарии
3. @PeterPenzov, так что это означает, что вы определили
enum
в базе данных строчный регистр. Итак, вы пошли на все эти хлопоты, чтобы попасть в нижний регистр. Почему именно вы думаете, что вам нужно изменить свое мнение обо всем этом?4. Потому что я хочу хранить только в БД
A
и возвращаться кactive
значению пользовательского интерфейса.5. @PeterPenzov Что вы имеете в виду под лучшим решением? Что не так с моим решением? Или ты меня не спрашиваешь?