Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Data JPA method + REST: Enum to Integer conversion

I've got an endpoint:

/api/offers/search/findByType?type=X

where X should be an Integer value (an ordinal value of my OfferType instance), whereas Spring considers X a String and will be applying its StringToEnumConverterFactory with the StringToEnum convertor.

public interface OfferRepository extends PagingAndSortingRepository<Offer, Long> {

    List<Offer> findByType(@Param("type") OfferType type);

}

So I wrote a custom Converter<Integer, OfferType> which simply get a instance by the given ordinal number:

public class IntegerToOfferTypeConverter implements Converter<Integer, OfferType> {

    @Override
    public OfferType convert(Integer source) {
        return OfferType.class.getEnumConstants()[source];
    }

}

Then I registered it properly with a Configuration:

@EnableWebMvc
@Configuration
@RequiredArgsConstructor
public class GlobalMVCConfiguration extends WebMvcConfigurerAdapter {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new IntegerToOfferTypeConverter());
    }

}

And I was expected that all requests to findByType?type=X will pass through my converter, but they do not.

Is any way to say that all enums defined as a request parameters have to be provided as an Integer? Furthermore, is any way to say it globally, not just for a specific enum?

EDIT: I've found IntegerToEnumConverterFactory in my classpath that does all I need. And it is registered with DefaultConversionService which is a default service for conversion. How can that be applied?

EDIT2: It's such a trivial thing, I was wondering if there is a property to turn enum conversion on.

EDIT3: I tried to write a Converter<String, OfferType> after I had got String from TypeDescriptor.forObject(value), it didn't help.

EDIT4: My problem was that I had placed custom converter registration into a MVC configuration (WebMvcConfigurerAdapter with addFormatters) instead of a REST Repositories one (RepositoryRestConfigurerAdapter with configureConversionService).

like image 391
Andrew Tobilko Avatar asked Mar 14 '17 16:03

Andrew Tobilko


People also ask

What is the difference between findAllById and findById?

Actually, the difference between findallBy and findby, is that : findAllBy returns a Collection but findBy returns Optional. so it's preferable to write List findAllBy instead of writing List findBy (but it will work also :p). and to write Optional findBy instead of Optional findAllBy.

What does findById return in JPA?

Its findById method retrieves an entity by its id. The return value is Optional<T> . Optional<T> is a container object which may or may not contain a non-null value. If a value is present, isPresent returns true and get returns the value.

What is the difference between save and saveAndFlush in JPA?

On saveAndFlush , changes will be flushed to DB immediately in this command. With save , this is not necessarily true, and might stay just in memory, until flush or commit commands are issued.

What is the default enum value type in JPA?

JPA & Hibernate Standard Enum Mappings By default, Hibernate maps an enum to a number. It uses the ordinal value, which is the zero-based position of a value within the definition of the enum. So, the enum value that's defined first gets mapped to 0, the second one to 1 and so on.


1 Answers

Spring parses the query parameters as Strings. I believe it always uses Converter<String, ?> converters to convert from the query parameters to your repository methods parameters. It uses an enhanced converter service, since it registers its own converters such as Converter<Entity, Resource>.

Therefore you have to create a Converter<String, OfferType>, e.g.:

@Component
public class StringToOfferTypeConverter implements Converter<String, OfferType> {

    @Override
    public OfferType convert(String source) {
        return OfferType.class.getEnumConstants()[Integer.valueOf(source)];
    }
}

And then configure this converter to be used by the Spring Data REST, in a class extending RepositoryRestConfigurerAdapter:

@Configuration
public class ConverterConfiguration extends RepositoryRestConfigurerAdapter {

    @Autowired
    StringToOfferTypeConverter converter;

    @Override
    public void configureConversionService(ConfigurableConversionService conversionService) {
        conversionService.addConverter(converter);
        super.configureConversionService(conversionService);
    }
}

I tried to add this to the basic tutorial, added a simple enum to the Person class:

public enum OfferType {
    ONE, TWO;
}


@Entity
public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    private OfferType type;


    public OfferType getType() {
        return type;
    }

    public void setType(OfferType type) {
        this.type = type;
    }
}

And when I call:

http://localhost:8080/people/search/findByType?type=1

I get the result without errors:

{
  "_embedded" : {
    "people" : [ ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/people/search/findByType?type=1"
    }
  }
}

To implement a global Enum converter, you have to create a factory and register it in the configuration using the method: conversionService.addConverterFactory(). The code below is a modified example from the documentation:

public class StringToEnumFactory implements ConverterFactory<String, Enum> {

    public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
        return new StringToEnum(targetType);
    }

    private final class StringToEnum<T extends Enum> implements Converter<String, T> {

        private Class<T> enumType;

        public StringToEnum(Class<T> enumType) {
            this.enumType = enumType;
        }

        public T convert(String source) {
            Integer index = Integer.valueOf(source);
            return enumType.getEnumConstants()[index];
        }
    }
}
like image 91
MartinTeeVarga Avatar answered Oct 31 '22 23:10

MartinTeeVarga