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
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> {
}
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> {
}
Create and extend the below class. It uses annotation to surpresse save
and delete
from being exposed as REST APIs (remove 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);
}
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> {
}
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
@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
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
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.
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
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
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
Follow the instructions here: http://stackoverflow.com/questions/36448921/how-we-create-autogenerated-field-for-mongodb-using-springboot
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;
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);
}
Just declare the type as Map<K,V>:
private Map<String, Boolean> daysOpen;
- 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:
Nice collection of Spring Data REST wisdom. It helped me to win many challenges in my project.