12

I have a model:

public class MyModel {
    @Id private Long id;
    private Long externalId;
    // Getters, setters
}

I'd like to use externalId as my resource identifier:

@Configuration
static class RepositoryEntityLookupConfig extends RepositoryRestConfigurerAdapter {
    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration configuration) {
        configuration
                .withEntityLookup()
                    .forRepository(MyRepository.class, MyModel::getExternalId, MyRepository::findByExternalId);
    }
}

If externalId is a String, this works fine. But since it's a number (Long)

public interface MyRepository extends JpaRepository<MyModel, Long> {
    Optional<MyModel> findByExternalId(@Param("externalId") Long externalId);
}

when invoking: /myModels/1 I get:

java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Long
    at org.springframework.data.rest.core.config.EntityLookupConfiguration$RepositoriesEntityLookup.lookupEntity(EntityLookupConfiguration.java:213) ~[spring-data-rest-core-2.6.4.RELEASE.jar:na]
    at org.springframework.data.rest.core.support.UnwrappingRepositoryInvokerFactory$UnwrappingRepositoryInvoker.invokeFindOne(UnwrappingRepositoryInvokerFactory.java:130) ~[spring-data-rest-core-2.6.4.RELEASE.jar:na]
    at org.springframework.data.rest.webmvc.RepositoryEntityController.getItemResource(RepositoryEntityController.java:524) ~[spring-data-rest-webmvc-2.6.4.RELEASE.jar:na]
    at org.springframework.data.rest.webmvc.RepositoryEntityController.getItemResource(RepositoryEntityController.java:335) ~[spring-data-rest-webmvc-2.6.4.RELEASE.jar:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_111]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_111]
    ...

A separate custom EntityLookupSupport<MyModel> component class works.

Am I missing something to get it working for Long using method references in my RepositoryRestConfigurerAdapter?

Jens Schauder
  • 77,657
  • 34
  • 181
  • 348
rizjoj
  • 197
  • 2
  • 9
  • What is the type of the underlying database column? Is the column a String? – Ben M Jul 27 '17 at 01:31
  • @Ben Database column type: int, Database: MySQL. If the type were String, it works (no converter needed, model field would be String instead of Long). I need it to work with type int (foreign key constraints). – rizjoj Jul 27 '17 at 09:44
  • What if you call `/myModels/1L` instead of `/myModels/1` ? It may be a serialization issue – Anthony Raymond Jul 31 '17 at 16:48

4 Answers4

0

Try to add this to your RepositoryEntityLookupConfig class:

@Override
public void configureConversionService(ConfigurableConversionService conversionService) {
    conversionService.addConverter(String.class, Long.class, Long::parseLong);
    super.configureConversionService(conversionService);
}
Cepr0
  • 28,144
  • 8
  • 75
  • 101
  • Added it. I still get "java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Long". – rizjoj Jul 22 '17 at 23:26
0

Do you really need to set configuration by yourself ? You could try to use spring-boot auto-configuration by adding @RepositoryRestResource annotation

@RepositoryRestResource(collectionResourceRel = "myModels", path = "myModels")
public interface MyRepository extends JpaRepository<MyModel, Long> {
        Optional<MyModel> findByExternalId(@Param("externalId") Long externalId);
}

Also add @Entity on your model class

@Entity
public class MyModel {
    @Id 
    private Long id;
    @Column(name = "EXTERNAL_ID")
    // Column annotation is not required if you respect case-sensitive
    private Long externalId;
    // Getters, setters
}
Martin Choraine
  • 2,296
  • 3
  • 20
  • 37
  • Didn't work for me. Adding the `@RepositoryRestResource` on MyRepository (while removing the configuration) results in the same effect as the default `findById`. Model is already annotated with @Entity and column doesn't require annotation (findByExternalId otherwise works). – rizjoj Aug 06 '17 at 00:16
0

The signature of the method you are trying to call seems to be:

forRepository(Class<R> type, Converter<T,ID> identifierMapping, 
         EntityLookupRegistrar.LookupRegistrar.Lookup<R,ID> lookup)

I don't see how MyModel::getExternalId can be doing the necessary conversion.

I would try something like the following:

@Configuration
static class RepositoryEntityLookupConfig extends RepositoryRestConfigurerAdapter {
    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration configuration) {
    configuration
                .withEntityLookup()
                    .forRepository(MyRepository.class, Long::parseLong, MyRepository::findByExternalId);
    }
}
aboger
  • 2,214
  • 6
  • 33
  • 47
Alan Hay
  • 22,665
  • 4
  • 56
  • 110
0

Apparently, the default BackendIdConverter (see DefaultIdConverter) does nothing with ID conversion and on the other hand Spring Data Rest cannot use the repository ID type. So, you have to either convert it yourself or configure your custom ID converter bean, for example:

@Bean
public BackendIdConverter myModelBackendIdConverter() {
  return new BackendIdConverter() {

    @Override
    public Serializable fromRequestId(final String id, final Class<?> entityType) {
      return Optional.ofNullable(id).map(Long::parseLong).orElse(null);
    }

    @Override
    public boolean supports(final Class<?> delimiter) {
      return MyModel.class.isAssignableFrom(delimiter);
    }

    @Override
    public String toRequestId(final Serializable id, final Class<?> entityType) {
      return Optional.ofNullable(id).map(Object::toString).orElse(null);
    }
  };
}

See also:

  • BackendIdHandlerMethodArgumentResolver
  • @BackendId
aux
  • 1,589
  • 12
  • 20