Using a custom ID in a Spring Data REST repository backed by MongoDB

Viewed 352

I'm working on a new project using Spring and MongoDB. I'm a novice with both of these so bear with me, I couldn't find a definitive answer to this question.

I'm using spring-boot-starter-data-rest and have a repository like this:

@RepositoryRestResource(collectionResourceRel = "widget", path = "widget")
interface WidgetRepository : MongoRepository<Widget, ObjectId> {
    fun findByType(@Param("type") type: String): List<Widget>
}

For an entity like this:

data class Widget @JsonCreator constructor(@JsonProperty val type: String) {
    @Id
    lateinit var id: ObjectId
}

This automatically gives you a CRUD API using the Mongo document ID:

GET /widget/{mongo doc id}
GET /widget/search/findByType?type=
POST /widget
PUT /widget
PATCH /widget

But I don't want to use the Mongo document ID. I want to introduce a secondary identifier and use that everywhere in the API. This is because the "widgets" are items in the physical world that are barcoded, and we don't want to print the Mongo document ID in the barcode.

Obviously we can implement this using Spring REST API tools, eg

    @GetMapping("/widget/{barcode}/")
    fun getByBarcode(@PathVariable barcode: String): Widget {
        return widgetRepository.findByBarcode(barcode)
    }

etc.. but is there any clever way to get @RepositoryRestResource to build its automagic API for us with a custom ID? Maybe by implementing CrudRepository<Widget, Barcode> in such a way that we have to wrap a MongoRepository<Widget, ObjectId> ? I'm not familiar enough with how Spring works under the hood to know if anything like this is even possible.

Thanks in advance

1 Answers

I think you are looking for an EntityLookup:

SPI to customize which property of an entity is used as unique identifier and how the entity instance is looked up from the backend.

First - sorry if I make any mistake, I do not use to program in Kotlin - you need to include the barcode property in your entity:

data class Widget @JsonCreator constructor(@JsonProperty val type: String, @JsonProperty val barcode: String) {
    @Id
    lateinit var id: ObjectId
}

Then, modify your repository and define a new method that will provide a Widget given its barcode:

@RepositoryRestResource(collectionResourceRel = "widget", path = "widget")
interface WidgetRepository : MongoRepository<Widget, ObjectId> {
    fun findByBarcode(@Param("barcode") barcode: String): Optional<Widget>
    fun findByType(@Param("type") type: String): List<Widget>
}

Finally, configure a RestRepositoryConfigurer and register the EntityLookup through an EntityLookupRegistrar:

@Component
class RestRepositoryConfigurator : RepositoryRestConfigurer {
   override fun configureRepositoryRestConfiguration(config: RepositoryRestConfiguration) {
       config.withEntityLookup()
          .forRepository(WidgetRepository::class.java)
          .withIdMapping(Widget::barcode)
          .withLookup(WidgetRepository::findByBarcode)
   }
}

Please, review the section 15.1 of the Spring Data Rest documentation if you need more information.

Related