Spring Data REST custom finder for JpaRepository - spring-data-jpa

I am looking to build a REST interface with a generic finder. The idea is to provide a search form where users can get all records by not providing any parameter or refine their search results by typing any combination of the fields.
The simple example I have annotates the JpaRepository with #RestResource which provides a nice out of the box way to add finders either by using #Query or by method name conventions
#RestResource(path = "users", rel = "users")
public interface UserRepository extends JpaRepository<User, Long>{
public Page<User> findByFirstNameStartingWithIgnoreCase(#Param("first") String fName, Pageable page);
}
I am looking to add a custom finder that would map my parameters and would leverage the paging, sorting and REST support where the actual implementation query will be composed dynamically (probably using QueryDSL) the method will have n parameters (p 1 ... p n) and will look like:
public Page<User> findCustom(#Param("p1") String p1, #Param("p2") String p2, ... #Param("pn") String pn, Pageable page);
I have tried the approach described in:
http://docs.spring.io/spring-data/data-jpa/docs/current/reference/html/repositories.html#repositories.custom-implementations
but my custom method is not available from the repository's REST interface (/users/search)
I hope someone already figured this out and would be kind to give me some direction.

Try something like this but of course adopted to your scenario:
public interface LocationRepository extends CrudRepository,
PagingAndSortingRepository,
LocationRepositoryExt {
}
public interface LocationRepositoryExt {
#Query
public List findByStateCodeAndLocationNumber(#Param("stateCode") StateCode stateCode, #Param("locationNumber") String locationNumber);
}
class LocationRepositoryImpl extends QueryDslRepositorySupport implements LocationRepositoryExt {
private static final QLocation location = QLocation.location;
public LocationRepositoryImpl() {
super(Location.class);
}
#Override
public Page findByStateAndLocationNumber(#Param("state") State state, #Param("locationNumber") String locationNumber, Pageable pageable) {
List locations = from(location)
.where(location.state.eq(state)
.and(location.locationNumber.eq(locationNumber)))
.list(location);
return new PageImpl(locations, pageable, locations.size());
}
}

Related

findAll clashes with findAll with CrudRepository in Projections

Login
#ApiModel
#Entity
public class Login {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private LocalDateTime loginDateTime;
/** Other fields ***/
}
LoginDateOnly
interface LoginDateOnly {
#Value("#{target.loginDateTime.toLocalDate()}")
LocalDate getDateFromLoginDateTime();
}
LoginRepository
#RepositoryRestResource(collectionResourceRel = "login", path = "login")
public interface LoginRepository extends PagingAndSortingRepository<Login, Long> {
Collection<LoginDateOnly> findAll();
/** Other query methods **/
}
I simply want to get all my Login record, with LocalDate part of my loginDateTime selected/projected using a http://host/api/login. But currently I'm encountering a clash with CrudRepository's findAll(). How to solve this as much as possible using projection. I'm making #Query and #NamedQuery my last resort.
A findAll method signature is:
List<T> findAll();
If you want to override it you cannot use another signature.
All you need to get a list of your projections is define another method for this, for example:
Collection<LoginDateOnly> findAllBy();
But as I can see you are using the Spring Data REST, so in this case you don't need to define a new method. You should firstly add annotation #Projection to your projection:
#Projection(name = "loginDateOnly", types = Login.class)
interface LoginDateOnly {
//...
}
Then use its name in the request url:
GET http://host/api/login?projection=loginDateOnly
See more info in the doc: Projections and Excerpts

How to NOT expose query methods in REST?

by default JPA DATA REST will expose query methods to /search/. I just wonder how to hide some of them.
For example, in my repository class:
public interface LeaseRepository extends CrudRepository<Lease, Long> {
List<Lease> findByName(#Param("name") String name);
List<Lease> findByIsActive(#Param("isActive") boolean isActive);
}
I would use findByName only internally, so how to disable it from being accessed in /search/findByName to others?
Just using annotation #RestResource:
#RestResource(exported = false)
List<Lease> findByName(#Param("name") String name);
link

Spring boot getting the value from the repository and use that value in another method as return type

I am working on Spring Boot Where i am facing issue while fetching values from a particular column and use it as return value in another class. I had followed the steps as
1) fetching the value using repository in service class as below
public MyEntity fetchDate(){
return MyRepository.findByName(date)
}
2) How can I write a method in controller class which returns fromDate value which I passed in findByName() method as
public Date getDate(Long Id){
myService.fetchDate();
return date;
}
MyEntity.getFromDate () can return the required value.
I believe you should be able to override the query behavior with the #Query annotation on your interface as follows and it should provide the functionality you're interested in:
public interface MyEntityRepository extends JpaRepository<MyEntity, Long> {
#Query("SELECT e.myDateField FROM MyEntity e WHERE e.name = :name")
Date findByName(String name)
}

Spring Data JPA And NamedEntityGraphs

currently I am wrestling with being able to fetch only the data I need. The findAll() method needs to fetch data dependant on where its getting called.
I do not want to end up writing different methods for each entity graph.
Also, I would avoid calling entitymanagers and forming the (repetitive) queries myself.
Basicly I want to use the build in findAll method, but with the entity graph of my liking. Any chance?
#Entity
#Table(name="complaints")
#NamedEntityGraphs({
#NamedEntityGraph(name="allJoinsButMessages", attributeNodes = {
#NamedAttributeNode("customer"),
#NamedAttributeNode("handling_employee"),
#NamedAttributeNode("genre")
}),
#NamedEntityGraph(name="allJoins", attributeNodes = {
#NamedAttributeNode("customer"),
#NamedAttributeNode("handling_employee"),
#NamedAttributeNode("genre"),
#NamedAttributeNode("complaintMessages")
}),
#NamedEntityGraph(name="noJoins", attributeNodes = {
})
})
public class Complaint implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private long id;
private Timestamp date;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "customer")
private User customer;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "handling_employee")
private User handling_employee;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="genre")
private Genre genre;
private boolean closed;
#OneToMany(mappedBy = "complaint", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<ComplaintMessage> complaintMessages = new ArrayList<ComplaintMessage>();
//getters and setters
}
And my JPARepository
#Repository
public interface ComplaintRepository extends JpaRepository<Complaint, Long>{
List<Complaint> findByClosed(boolean closed);
#EntityGraph(value = "allJoinsButMessages" , type=EntityGraphType.FETCH)
#Override
List<Complaint> findAll(Sort sort);
}
We ran into a similar problem and devised several prospective solutions but there doesn't seem to be an elegant solution for what seems to be a common problem.
1) Prefixes. Data jpa affords several prefixes (find, get, ...) for a method name. One possibility is to use different prefixes with different named graphs. This is the least work but hides the meaning of the method from the developer and has a great deal of potential to cause some non-obvious problems with the wrong entities loading.
#Repository
#Transactional
public interface UserRepository extends CrudRepository<User, Integer>, UserRepositoryCustom {
#EntityGraph(value = "User.membershipYearsAndPreferences", type = EntityGraphType.LOAD)
User findByUserID(int id);
#EntityGraph(value = "User.membershipYears", type = EntityGraphType.LOAD)
User readByUserId(int id);
}
2) CustomRepository. Another possible solutions is to create custom query methods and inject the EntityManager. This solution gives you the cleanest interface to your repository because you can name your methods something meaningful, but it is a significant amount of complexity to add to your code to provide the solution AND you are manually grabbing the entity manager instead of using Spring magic.
interface UserRepositoryCustom {
public User findUserWithMembershipYearsById(int id);
}
class UserRepositoryImpl implements UserRepositoryCustom {
#PersistenceContext
private EntityManager em;
#Override
public User findUserWithMembershipYearsById(int id) {
User result = null;
List<User> users = em.createQuery("SELECT u FROM users AS u WHERE u.id = :id", User.class)
.setParameter("id", id)
.setHint("javax.persistence.fetchgraph", em.getEntityGraph("User.membershipYears"))
.getResultList();
if(users.size() >= 0) {
result = users.get(0);
}
return result;
}
}
#Repository
#Transactional
public interface UserRepository extends CrudRepository<User, Integer>, UserRepositoryCustom {
#EntityGraph(value = "User.membershipYearsAndPreferences", type = EntityGraphType.LOAD)
User findByUserID(int id);
}
3) JPQL. Essentially this is just giving up on named entity graphs and using JPQL to handle your joins for you. Non-ideal in my opinion.
#Repository
#Transactional
public interface UserRepository extends CrudRepository<User, Integer>, UserRepositoryCustom {
#EntityGraph(value = "User.membershipYearsAndPreferences", type = EntityGraphType.LOAD)
User findByUserID(int id);
#Query("SELECT u FROM users WHERE u.id=:id JOIN??????????????????????????")
User findUserWithTags(#Param("id") final int id);
}
We went with option 1 because it is the simplest in implementation but this does mean when we use our repositories we have have to look at the fetch methods to make sure we are using the one with the correct entity graph. Good luck.
Sources:
JPA EntityGraph with different views using Spring
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods
I don't have enough reputation to post all of my sources. Sorry :(
We had the same issue and built a Spring Data JPA extension to solve it :
https://github.com/Cosium/spring-data-jpa-entity-graph
This extension allows to pass named or dynamically built EntityGraph as an argument of any repository method.
With this extension, you would have this method immediatly available:
List<Complaint> findAll(Sort sort, EntityGraph entityGraph);
And be able to call it with an EntityGraph selected at runtime.
Use #EntityGraph together with #Query
#Repository
public interface ComplaintRepository extends JpaRepository<Complaint, Long>{
#EntityGraph(value = "allJoinsButMessages" , type=EntityGraphType.FETCH)
#Query("SELECT c FROM Complaint ORDER BY ..")
#Override
List<Complaint> findAllJoinsButMessages();
#EntityGraph(value = "allJoins" , type=EntityGraphType.FETCH)
#Query("SELECT c FROM Complaint ORDER BY ..")
#Override
List<Complaint> findAllJoin();
...
}
Using the #EntityGraph annotation on a derived query is possible, as I found out from This article. The article has the example:
#Repository
public interface ArticleRepository extends JpaRepository<Article,Long> {
#EntityGraph(attributePaths = "topics")
Article findOneWithTopicsById(Long id);
}
But I don't think there's anything special about "with" and you can actually have anything between find and By. I tried these and they work (this code is Kotlin, but the idea is the same):
interface UserRepository : PagingAndSortingRepository<UserModel, Long> {
#EntityGraph(attributePaths = arrayOf("address"))
fun findAnythingGoesHereById(id: Long): Optional<UserModel>
#EntityGraph(attributePaths = arrayOf("address"))
fun findAllAnythingGoesHereBy(pageable: Pageable): Page<UserModel>
}
The article had mentioned the caveat that you can't create a method similar to findAll which will query all records without having a By condition and uses findAllWithTopicsByIdNotNull() as an example. I found that just including By by itself at the end of the name was sufficient: findAllWithTopicsBy(). A little more terse but maybe a little more confusing to read. Using method names which end with just By without any condition may be in danger of breaking in future versions in Spring since it doesn't seem like an intended use of derived queries name.
It looks like the code for parsing derived query names in Spring is here on github. You can look there in case you're curious about what's possible for derived queries repository method names.
These are the spring docs for derived queries.
This was tested with spring-data-commons-2.2.3.RELEASE
EDIT: this doesn't actually work. Ended up having to go with https://github.com/Cosium/spring-data-jpa-entity-graph. The default method LOOKS correct, but doesn't successfully override the annotations.
Using JPA, what I found works is to use a default method, with a different EntityGraph annotation:
#Repository
public interface ComplaintRepository extends JpaRepository<Complaint, Long>{
List<Complaint> findByClosed(boolean closed);
#EntityGraph(attributePaths = {"customer", "genre", "handling_employee" }, type=EntityGraphType.FETCH)
#Override
List<Complaint> findAll(Sort sort);
#EntityGraph(attributePaths = {"customer", "genre", "handling_employee", "messages" }, type=EntityGraphType.FETCH)
default List<Complaint> queryAll(Sort sort){
return findAll(sort);
}
}
You don't have to do any of the re-implementation, and can customize the entity graph using the existing interface.
Can you try create EntiyGraph name with child that you will request and give same name to the find all method.
Ex:
#EntityGraph(value = "fetch.Profile.Address.record", type = EntityGraphType.LOAD)
Employee getProfileAddressRecordById(long id);
For your case:
#NamedEntityGraph(name="all.Customer.handling_employee.genre", attributeNodes = {
#NamedAttributeNode("customer"),
#NamedAttributeNode("handling_employee"),
#NamedAttributeNode("genre")
})
method name in repository
#EntityGraph(value = "all.Customer.handling_employee.genre" , type=EntityGraphType.FETCH)
findAllCustomerHandlingEmployeeGenre
This way you can keep track of different findAll methods.

Generic Entity interface on gwt client side while using GwtQuery

I want to have a simple generic Entity interface pretty much like a map such as on client side in a Gwt + GwtQuery project .
public interface Entity extends JsonBuilder {
public String JsonObject getProperty(String property) ;
public Entity setProperty(String name , JsonObject obj ) ;
public String getPropertyType(String property) /* returns the actual
}
I want to be able to convert any pojo on server side to a Map form along with some type info and retrieve it on client as an Entity. Entities can be nested.
Is this doable ?
If yes. Please give some detailed guidelines.
To clarify further my goal is to have a single generic Entity interface that is capable of representing varied/diverse types of pojos from server. The type information of such a dynamic entity is expected to be available on the client side as a separate entity.
Do you think the code below will work and serve my purpose ? If yes - how will the json text underneath look like ?
public interface Tuple extends JsonBuilder {
public JsonValue get(String name);
public void set(String name, JsonValue ser);
}
public interface Entity extends Tuple {
public String getType();
public Tuple[] getTuples();
}