Updating a hibernate entity with CrudRepository - spring

I'm currently writing my first spring boot Kotlin application and am trying to create a rest API with JPA persistence. The basics are going fine but I'm struggling with updating a model on a patch endpoint (#patchMapping).
I want to adhere to proper rest standards and for that reason I'm hitting the patch endpoint with #PatchMapping("/company/{id}").
I would like to be able to call the CrudRepository in a way like this.
#PatchMapping("/company/{id}")
fun update(#PathVariable id: Long, #RequestBody updateRequest: Company) : Company {
return repository.update(updateRequest, id)
}
but it appears as if the spring way to do it is to pass the id of the object you're going to update within the requestBody? e.g.
repository.save(updateRequest)
which then auto merges the object. But this conflicts with any sane rest convention...
is there an integrated solution available for what I want to achieve? I'd like to refrain from writing my own logic as I'd hoped spring to have this functionality.

Do you need something like this?
#RestController
class Controller(private val service: CompanyService) {
#PatchMapping("/company/{id}")
fun update(#PathVariable id: Long, #RequestBody company: Company): Company {
return service.updateCompany(company, id)
}
}
#Service
class CompanyService (private val repository: CompanyRepository) {
#Transactional
fun updateCompany(company: Company, id: Long): Company {
val companyToUpdate = repository.findOne(id)
companyToUpdate.setSomething(company.getSomething)
raturn companyToUpdate;
}
}
interface CompanyRepository : CrudRepository<Company, Long>

Related

Testing Service layer in Kotlin Spring Boot when back end is using Spring Security

I'm looking to try and test my back end Service layer (ideally along with my Controller layer too). However, I'm not sure how I would be able to go about doing this.
I saw that I can use Mockito to do this, however, I was not able to figure out how to apply this with what I currently have.
I have the entity, then repository, service and then finally controller.
My services have the main logic for the application within. For example, saving an entity, fetching and updating.
My controller (which is a restcontroller) is what my front end client interfaces with in order to call the service methods.
My service has an interface like below:
interface CompanyService {
fun saveCompany(company: Company): Company
fun saveCompany(form: CompanyController.SaveCompanyForm): Company
fun getCompany(name: String): Company
fun getCompany(id: Long): Company
fun getCompanies(): MutableList<Company>
fun addContactToCompany(firstName: String, lastName: String, companyName: String)
fun saveContact(contact: Contact): Contact
fun saveContact(form: CompanyController.SaveContactForm): Contact
fun saveContact(contact: Contact, companyName: String): Contact
fun getContact(firstName: String, lastName: String): Contact
fun getContact(id: Long): Contact
fun getContacts(): MutableList<Contact>
}
and then a CompanyServiceImpl:
#Service
#Transactional
class CompanyServiceImpl(
private val companyRepository: CompanyRepository,
private val contactRepository: ContactRepository,
) : CompanyService {
private val log = logger<CompanyServiceImpl>()
override fun saveCompany(company: Company): Company {
log.info("Saving Company ${company.name} to database")
return companyRepository.save(company)
}
...
}
My question as the subject says is how I would go about unit testing this so I don't have to manually do it which is both time consuming as well as it being "not the right way of doing it".
Ideally I'd like to have my controllers tested the same way, however I have Spring Security enabled and am unsure if this would get in the way in any way.

How to avoid Spring Repository<T, ID> to leak persistence information into service tier

I'm using spring-data-mongodb at the moment so this question is primarily in context of MongoDB but I suspect my question applies to repository code in general.
Out of the box when using a MongoRepository<T, ID> interface (or any other Repository<T, ID> descendent) the entity type T is expected to be the document type (the type that defines the document schema).
As a result injecting such a repository into service component means this repository is leaking database schema information into the service tier (highly pseudo) :
class MyModel {
UUID id;
}
#Document
class MyDocument {
#Id
String id;
}
interface MyRepository extends MongoRepository<MyDocument, String> {
}
class MyService {
MyRepository repository;
MyModel getById(UUID id) {
var documentId = convert(id, ...);
var matchingDocument = repository.findById(documentId).orElse(...);
var model = convert(matchignDocument, ...);
return model;
}
}
Whilst ideally I'd want to do this :
class MyModel {
UUID id;
}
#Document
class MyDocument {
#Id
String id;
}
#Configuration
class MyMagicConversionConfig {
...
}
class MyDocumentToModelConverter implements Converter<MyModel, MyDocument> {
...
}
class MyModelToDocumentConverter implements Converter<MyDocument, MyModel> {
...
}
// Note that the model and the model's ID type are used in the repository declaration
interface MyRepository extends MongoRepository<MyModel, UUID> {
}
class MyService {
MyRepository repository;
MyModel getById(UUID id) {
// Repository now returns the model because it was converted upstream
// by the mongo persistence layer.
var matchingModel = repository.findById(documentId).orElse(...);
return matchingModel ;
}
}
Defining this conversion once seems significantly more practical than having to consistently do it throughout your service code so I suspect I'm just missing something.
But of course this requires some way to inform the mongo mapping layer to be aware of what conversion has to be applied to move between MyModel and MyDocument and to use the latter for it's actual source of mapping metadata (e.g. #Document, #Id, etc.).
I've been fiddling with custom converters but I just can't seem to make the MongoDB mapping component do the above.
My two questions are :
Is it currently possible to define custom converters or implement callbacks that allow me to define and implement this model <-> document conversion once and abstract it away from my service tier.
If not, what is the idiomatic way to approach cleaning this up such that the service layer can stay blissfully unaware of how or with what schema an entity is persisted? A lot of Spring Boot codebases appear to be fine with using the type that defines the database schema as their model but that seems supoptimal. Suggestions welcome!
Thanks!
I think you're blowing things a bit out of proportion. The service layer is not aware of the schema. It is aware of the types returned by the repository. How the properties of those are mapped onto the schema, depends on the object-document mapping. This, by default, uses the property name, as that's the most straightforward thing to do. That translation can either be customized using annotations on the document type or by registering a FieldNamingStrategy with Spring Data MongoDB.
Spring Data MongoDB's object-document mapping subsystem provides a lot of customization hooks that allows transforming arbitrary MongoDB documents into entities. The types which the repositories return are your domain objects that - again, only by default - are mapped onto a MongoDB document 1:1, simply because that's the most reasonable thing to do in the first place.
If really in doubt, you can manually implement repository methods individually that allow you to use the MongoTemplate API that allows you to explicitly define the type, the data should be projected into.
You can use something like MapStruct or write your own Singleton Mapper.
Then create default methods in your repository:
interface DogRepository extends MongoRepository<DogDocument, String> {
DogDocument findById(String id);
default DogModel dogById(String id) {
return DogMapper.INSTANCE.toModel(
findById(id)
);
}
}

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

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
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.

Return custom-typed object from JpaRepository

I have the following repository:
public interface UserRepository extends BaseDAO<User> {
Collection<User> findByEmail(#Param("email") String email);
#Query("select new com.data.CustomUser(upper(substring(u.lastName, 1, 1)) as initial, count(*)) from User u join u.chats c where c.business=:business group by upper(substring(u.lastName, 1, 1)) order by initial")
List<CustomUser> getContactsIndex(#Param("email") String email);
}
which is exposed with Spring Data REST. The User object is a managed entity, while CustomUser not and as you can see, it's build on-fly by using custom query.
Once I want to call that function, it fails with Persistent entity must not be a null! exception. Is there any way to implement this behavior?
P.S. Expose CustomUser with separate repository is impossible because it is not a managed entity.
One challenge with using Spring Data Rest is when you hit an edge case and you don't know whether you've hit a bug or whether you're just outside the scope of what the library is intended for. In this case I think you are at the edge of what SDR will easily do for you, and it's time to implement your own controller.
Spring Data Rest is looking for an Entity - in your case a User - as the return type for ALL methods in the repository to expose under /entities/search, and breaks when it doesn't find that entity type. The User it wants to serialize isn't there, hence the "Persistent entity must not be null".
The way around this is to write a simple #Controller that has a #RequestMapping for the exact same url exposed by the repository method. This will override the SDR generated implementation for that url, and from that you can return whatever you want.
Your implementation might look something like this:
#Controller
public class CustomUserController {
private final UserRepository repository;
#Inject
public CustomUserController(UserRepository repo) {
repository = repo;
}
#RequestMapping(value = "/users/search/getContactsIndex", method = GET, produces = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody List<CustomUser> getContactsIndex(#RequestParam String email) {
return repository.getContactsIndex(email);
}
}
Be aware that there is a "recommended" way to override functionality this way. There is an open issue to document the best way to do this.

How does delete operation work with Rest in Spring Data

Currently we have exposed our methods like this
#RestController
#RequestMapping("/app/person")
public class PersonResource {
#Timed
public void delete(#PathVariable Long id) {
log.debug("REST request to delete Person: {}", id);
personRepository.delete(id);
}
}
The operations of this method, in terms of input and output, are very clear to the user developer.
This article http://spring.io/guides/gs/accessing-data-rest/ shows how to expose JPARepositories directly obviating the need of a service layer.
#RepositoryRestResource(collectionResourceRel="people", path="people")
public interface PersonRepository extends JpaRepository<PersonEntity, Long> {
}
It is not obvious to me how I can make a "delete operation" available with PathVariable Long id.
There is an excellent article on this topic. https://github.com/spring-projects/spring-data-rest/wiki/Configuring-the-REST-URL-path
But it actually shows how to supress export of a delete operation.
As documented here, Spring Data REST will expose item resources for the repository you declare. Thus, all you need to do is discover the URI of the resource to delete and issue a DELETE request to it.

Resources