I just realized that people found the notes I wrote about Spring Data REST about 3 years ago useful. For SEO’s sake, I’ll make a copy of it here:
How to change baseUri:
in application.properties
add:
spring.data.rest.basePath=/api
- or -
in application.yml
add:
spring:
data:
rest:
basePath: /api
WARNING:
-
Some SOs answer will suggest the deprecated
baseUri
. But, as of spring boot 1.5.1.RELEASE, it seems thatbaseUri
is removed. -
in YAML, make sure you nest the properties otherwise it’ll complain duplicate key:
Exception in thread "main" while parsing MappingNode
in 'reader', line 2, column 1:
server:
^
Duplicate key: spring
in 'reader', line 29, column 23:
# security: DEBUG
nesting example:
# JACKSON
spring:
jackson:
serialization:
INDENT_OUTPUT: true
# TOMY: below added by me
data:
rest:
basePath: /api
IMPORTANT PSA ABOUT YAML: Beware that YAML doesn’t allow tabs for indentation. It only accepts 2 or 4 spaces.
How to prevent spring data repository from being exported as REST?
import java.util.List;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
@RepositoryRestResource(exported=false)
public interface PersonRepository extends MongoRepository<Person, String> {
}
How to change path instead of the default pluralized name
import java.util.List;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
@RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends MongoRepository<Person, String> {
}
How to Make REST Resource Repository read-only (i.e. only support GET, HEAD, OPTIONS method)
Create and extend the below class. It uses annotation to suppress save
and delete
from being exposed as REST APIs (effectively removing POST and DELETE methods support).
import java.io.Serializable;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.rest.core.annotation.RestResource;
@NoRepositoryBean
public interface GetOnlyMongoRestRepository<T, ID extends Serializable> extends MongoRepository<T, ID> {
@Override
@RestResource(exported = false)
void delete(ID id);
@Override
@RestResource(exported = false)
void delete(T entity);
@Override
@RestResource(exported = false)
<S extends T> S save(S entity);
}
How to project nested object (e.g. DBRef)
For instance, you have a Order
object with a nested orderer reference which is of type Person
.
public class Order {
@Id
private String id;
private int quantity;
private String productName;
@DBRef
private Person orderer;
// usual getter and setter
}
You can create a projection such as the below:
import org.springframework.data.rest.core.config.Projection;
@Projection(name = "inlineOrderer", types = { Order.class })
interface InlineOrderer {
String getProductName();
int getQuantity();
Person getOrderer();
}
Now you can query your REST API using: http://localhost:8080/api/orders/some-order-id?projection=inlineOrderer
Additionally, if you want this projection to be default when listing the items in the collection, use the below:
@RepositoryRestResource(excerptProjection = InlineOrderer.class)
public interface OrderRepository extends GetOnlyMongoRestRepository<Order, String> {
}
How to query by a certain attributes (search/ filter function)
In the CrudRepository
interface, add the query method:
@RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends MongoRepository<Person, String> {
List<Person> findByLastNameContainingIgnoreCase(@Param("name") String name);
}
then, you can access it using:
http://localhost:8080/api/people/search/findByLastNameContainingIgnoreCase?name=ang
You can alias it:
@RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends MongoRepository<Person, String> {
@RestResource(path="searchByLastName", rel="searchByLastName")
List<Person> findByLastNameContainingIgnoreCase(@Param("name") String name);
}
then, you can access it using alias:
http://localhost:8080/api/people/search/searchByLastName?name=ang
Note:
- Containing does *SEARCH_STRING*
- IgnoreCase does case-insensitive search
Refer to the below link for all query methods: http://docs.spring.io/spring-data/jpa/docs/1.5.1.RELEASE/reference/html/jpa.repositories.html#jpa.query-methods.query-creation
How to query multiple fields
@RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends MongoRepository<Person, String> {
// Find multiple fields
List<Person> findByLastNameOrFirstNameContainingAllIgnoreCase(@Param("name") String firstName,
@Param("name") String lastName);
}
http://localhost:8080/api/people/search/findByLastNameOrFirstNameContainingAllIgnoreCase?name=test
Note:
- All makes both FirstName and LastName ignore case *
\* - Or joins the 2 criteria
How to query multiple TextIndexed fields
Set up the index in the POJO:
import org.springframework.data.mongodb.core.index.TextIndexed;
import org.springframework.data.mongodb.core.mapping.Document;
@Document
public class Person {
@TextIndexed
private String aboutMe;
@TextIndexed
private String address;
// ...
}
NOTES:
- Don’t forget to annotate
@Document
. Without it, somehow the text index is not created. - There can only be one text index per collection in MongoDB. So all the fields annotated with
@TextIndexed
will be combined into one. - You can assign weight to the text index: http://docs.spring.io/spring-data/mongodb/docs/current/api/org/springframework/data/mongodb/core/index/TextIndexed.html
Modify the MongoRepository
to add the method to findAllBy
:
import org.springframework.data.mongodb.core.query.TextCriteria;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.repository.query.Param;
public interface PersonRepository extends MongoRepository<Person, String> {
// Find in indexed text
List<Person> findAllBy(@Param("criteria") TextCriteria criteria);
}
Since Spring data rest doesn’t have the default converter from String
to TextCriteria
, create a RestController
wrapper to invoke this:
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.query.TextCriteria;
import org.springframework.data.repository.query.Param;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/people")
public class PersonController {
private static final Logger LOG = LoggerFactory.getLogger(PersonController.class);
@Autowired
private PersonRepository personRepository;
@RequestMapping(value = "search", method = RequestMethod.GET)
public List<Person> search(@Param("criteria") String criteria) {
LOG.debug("Search invoked for criteria {}", criteria);
return personRepository.findAllBy(TextCriteria.forDefaultLanguage().matchingAny(criteria));
}
}
You can now access it here:
http://localhost:8080/people/search?criteria=happy
How to page the results
E.g. page every 2 results. Access it here:
http://localhost:8080/api/orders?size=2
In addition to the normal results, there will be _links
property to navigate to the next pages. HETEOAS FTW!
How to sort
E.g. sort by quantity descending. Access it here:
http://localhost:8080/api/orders?sort=quantity,desc
NOTE:
- Use comma to separate field name is sort direction
How to serialize java 8 LocalDate nicely in spring data rest APIs
Add the below maven dependency:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
Add the below line in application.properties
:
spring.jackson.serialization.write_dates_as_timestamps=false
LocalDate will be serialized as yyyy-MM-dd
.
Resource: http://stackoverflow.com/questions/29956175/json-java-8-localdatetime-format-in-spring-boot
Two-way @DBRef
reference
Basically, if you have a User
which has an Address
and you want the Address
to have a back pointer to the User
, Do NOT use two-way @DBRef
! When spring-data-rest serializes the object, you’ll run into a infinite recursion and possibly get the below exception:
java.lang.IllegalStateException: getOutputStream() has already been called for this response
How to generate sequence ID for Spring Data Mongodb
Follow the instructions here: http://stackoverflow.com/questions/36448921/how-we-create-autogenerated-field-for-mongodb-using-springboot
How to index a field
Just annotate it with @Indexed
. But remember to annotate the POJO with @Document
for MongoDB to scan the metadata mapping. E.g.
@Document
public class Person {
@Id
private ObjectId id;
@Indexed
private Integer ssn;
How to handle LocalDate in Controller
Annotate the request parameter with @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
DateTimeFormat.ISO.DATE
is yyyy-MM-dd
format.
@RequestMapping(value = "/protected/getPastUserOrders", method = RequestMethod.GET)
public List<Order> getPastUserOrders(@RequestParam("userId") String userId, @RequestParam("date") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date) throws IOException {
return orderRepository.findByUser_FacebookIdAndDateBefore(userId, date);
}
How to pass deserialize JSON object as a java Map
?
Just declare the type as Map<K,V>
:
private Map<String, Boolean> daysOpen;
How to read file in ClassPath to String (One-liner)
- First, don’t use
ClassPathResource::getFile
. This doesn’t work when your file is bundled in a jar. (i.e. only works in an IDE). - Hence, use
ClassPathResource::getInputStream
- How to easily convert
InputStream
toString
? UseIOUtils
Sample:
String templateString = IOUtils.toString(new ClassPathResource("templates/order-receipt.html").getInputStream(), "UTF-8");
References:
- http://stackoverflow.com/questions/25869428/classpath-resource-not-found-when-running-as-jar
- http://stackoverflow.com/questions/309424/read-convert-an-inputstream-to-a-string
Epilogue
Original Gist hosted on Github can be found here: https://gist.github.com/TomyJaya/31e48d81af0ca236496582d00192ef80
Note that any future updates will be made in this blog post instead. However, since I don’t work with this technology actively anymore, you shouldn’t expect much of such.