Inject parameter into spring-data dynamic query-build methods - java

I want to know if it's possible to inject some functionality into the dynamically generated CrudRepository query methods to provide an additional filterBy criteria for all methods in a repository.
The short version, I want
Page<Collection> findByName(#Param("name") String name);
to work as if it were named
Page<Collection> findByNameAndGroup(#Param("name") String name);
where I add the AndGroup criteria into the generated code for the repo's methods at impl creation time.
I don't want to have to add this everywhere, as it will be on all methods, and the parameter is known already.
My initial thought was to extend the repo class, and add some new methods that called the generated ones with the added parameter, but then I realized that the repo is defined only as an interface, so one cannot extend it, and adding any impl would impact the spring-data impl generation. At least, I think that's how it works..
Detail:
I have a MongoRepository that has a lot of API methods generated using the spring-data query-builder syntax. (http://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#repositories.query-methods.query-creation)
Main repo class:
#RepositoryRestResource
public interface CollectionRepository extends MongoRepository<Collection, String> {
Page<Collection> findByName(#Param("name") String name);
Page<Collection> findByNameRegex(#Param("name") String name);
// lots more findBy methods here...
}
The repo objects, collections, are just an object with a name and a group Id:
#Document
public class Collection {
private String name;
private String group;
}
My authenticated user objects are:
#Document
public class Collection {
private String username;
private String password;
private String group;
}
The user's group needs to filter what collections a user can see, in all cases.
Every one of the above findBy...() needs to become a findBy...AndGroup(), which is possible because the group is an element in the collection.
The easy way is to simply add the new parameter to every one of the methods, ie:
#RepositoryRestResource
public interface CollectionRepository extends MongoRepository<Collection, String> {
Page<Collection> findByNameAndGroup(#Param("name") String name);
Page<Collection> findByNameRegexAndGroup(#Param("name") String name);
// lots more methods here...
}
This also makes the client needlessly pass in a parameter that is already known from the user's context.
If I wasn't using spring-data and I had to actually write code for the controllers I'd call a method from all API handlers that read the user's group:
User user = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String group = user.getGroup();
and added it to calling method's filterBy criteria.
Is it possible to wire this functionality into the spring-data infrastructure such that the generated find...() methods for this interface add the "...ByGoup" criteria to all methods, and pass in a method to use to extract this group parameter?
To say it another way, can I alter the repository instance to affect all the generated methods to extract the user's group using a provided method, add this ByGroup filter to the initial criteria that spring generates based on the method name?
Could one use aspectJ to wire in a a pre/post method handler to do this?

Currently there's no direct support for that. Spring Data JPA already has support to use SpEL expressions in manual query definitions so that e.g. Spring Security specific functions can be used to in JPQL queries. This is shown in this example project but requires manual definition of the queries.
We're currently working on a mechanism to provide means to augment all queries executed by Spring Data. Be sure to follow DATACMNS-293 for the fundamental infrastructure as well as DATAJPA-307 for an example implementation of soft-deletes on top of that.
For DATACMNS-293 we already provide feature branch builds so that you could try to build your own QueryAugmentor. AnnotationBasedQueryAugmentor is probably type you want to look into.

Related

Spring Boot Repository - load DTO's direct from the Database

In my application I use DTOs. My current solution in pseudocode is this - works well:
ResponseEntity<EntityDTO> RestController.get(String uuid){
EntityDTO dto = Service.get(uuid) {
Entity entity = Repository.loadEntity(id);
return EntityDTO.from(entity);
}
return ResponseEntity<EntityDTO>( dto , HttpStatus.OK);
}
Recently I saw an other solution without the transformation step in the service layer.
E.g. your Entity looks like this
:
#Entity
public class Book {
Long id;
String title;
String text;
.....
}
And the text is too 'heavy' to send it with the hole book you usually would create a DTO like this:
public class SlimBookDTO {,
static SlimBookDTO from(Book book) {
return new SlimBookDTO(book.id, book.title);
}
Long id;
String title;
.....
}
The "new" (for me) Solution is to create only an interface like this:
public interface SlimBookDTO {
Long getId();
String getTitle();
}
And your BookRepository gets a new method:
#Repository
public interface BookRepository extends JpaRepository<Book , Long> {
List<SlimBookDTO> findAllByTitle(String title);
}
With this method I don't need the service layer any more for direct requests. Is this common? Does somebody has experience with this? Has it some downsides that I can't see in a small application but will face in larger scale?
Those are couple of ways of returning data from the database.
You create DTO and map necessary fields and return
Other is create an interface which is directly a kind of return type from Repository. this is what we call as JPA interface projection.
For second one, you know in detail by referring below link
https://www.baeldung.com/spring-data-jpa-projections
JPA interface projections are very useful when we query two or more entities in the Repository class
This is totally fine for simple GETs if the objects are straightforward enough, although of course you can't add additional logic, formatting or constraints. But as long as you don't need to do that, this will work well.
I don't think Hibernate analyzes the dto to only select a few fields though, so if you want to improve the performance too you can define the queries yourself, i.e. #Query("select new com.bla.SlimbookDTO(book.id, book.title) from Book book"), at the cost of not being able to just use automagically generated queries anymore based on the method name.

How to use getById method to get the a corresponding name in Spring MVC?

I'm learning Spring MVC and I want find a car via an id but get in return the name.
In my service class I call a generic method getXXXById. This is something JPA gives me by nature.
I know that I get the whole entity but how can I just receive the corresponding name to the id.
Example: I call getCarById(2) and it gives me back Tesla.
My Table:
id | Name
----------
1 | Ford
2 | Tesla
My Service:
class CarService {
// code ...
public Optional<CarEntity> getCarById(int id) {
return carRepository.findById(id);
}
There are two options to do that.
Making your own query
You could write your own query in JQPL to retrive only names.
For example you could create method like that in your repository.
#Query("select t.name from CarEntity where id = ?1")
public String findNameById(Integer id);
more information on this feature of Spring Data Jpa HERE
Projections
Second option is to make projection. As it is written in documentation
Spring Data query methods usually return one or multiple instances of the aggregate root managed by the repository. However, it might sometimes be desirable to rather project on certain attributes of those types. Spring Data allows to model dedicated return types to more selectively retrieve partial views onto the managed aggregates.
In simple words, it allows you to aggregate your results form queries in some limited set of attributes rather then whole entity.
Specifically for your needs I'd suggest to use first approch, but it is worth to know both.

How to make dynamic queries at run-time in Spring Boot and Data?

I am new to Java and started with Spring Boot and Spring Data JPA, so I know 2 ways on how to fetch data:
by Repository layer, with Literal method naming: FindOneByCity(String city);
by custom repo, with #Query annotation: #Query('select * from table where city like ?');
Both ways are statical designed.
How should I do to get data of a query that I have to build at run time?
What I am trying to achieve is the possibility to create dynamic reports without touching the code. A table would have records of reports with names and SQl queries with default parameters like begin_date, end_date etc, but with a variety of bodies. Example:
"Sales report by payment method" | select * from sales where met_pay = %pay_method% and date is between %begin_date% and %end_date%;
The Criteria API is mainly designed for that.
It provides an alternative way to define JPA queries.
With it you could build dynamic queries according to data provided at runtime.
To use it, you will need to create a custom repository implementation ant not only an interface.
You will indeed need to inject an EntityManager to create needed objects to create and execute the CriteriaQuery.
You will of course have to write boiler plate code to build the query and execute it.
This section explains how to create a custom repository with Spring Boot.
About your edit :
What I am trying to achieve is the possibility to create dynamic
reports without touching the code. A table would have records of
reports with names and SQl queries with default parameters like
begin_date, end_date etc, but with a variety of bodies.
If the queries are written at the hand in a plain text file, Criteria will not be the best choice as JPQL/SQL query and Criteria query are really not written in the same way.
In the Java code, mapping the JPQL/SQL queries defined in a plain text file to a Map<String, String> structure would be more adapted.
But I have some doubts on the feasibility of what you want to do.
Queries may have specific parameters, for some cases, you would not other choice than modifying the code. Specificities in parameters will do query maintainability very hard and error prone. Personally, I would implement the need by allowing the client to select for each field if a condition should be applied.
Then from the implementation side, I would use this user information to build my CriteriaQuery.
And there Criteria will do an excellent job : less code duplication, more adaptability for the query building and in addition more type-checks at compile type.
Spring-data repositories use EntityManager beneath. Repository classes are just another layer for the user not to worry about the details. But if a user wants to get his hands dirty, then of course spring wouldn't mind.
That is when you can use EntityManager directly.
Let us assume you have a Repository Class like AbcRepository
interface AbcRepository extends JpaRepository<Abc, String> {
}
You can create a custom repository like
interface CustomizedAbcRepository {
void someCustomMethod(User user);
}
The implementation class looks like
class CustomizedAbcRepositoryImpl implements CustomizedAbcRepository {
#Autowired
EntityManager entityManager;
public void someCustomMethod(User user) {
// You can build your custom query using Criteria or Criteria Builder
// and then use that in entityManager methods
}
}
Just a word of caution, the naming of the Customized interface and Customized implementating class is very important
In last versions of Spring Data was added ability to use JPA Criteria API. For more information see blog post https://jverhoelen.github.io/spring-data-queries-jpa-criteria-api/ .

Specify MongoDb collection name at runtime in Spring boot

I am trying to reuse my existing EmployeeRepository code (see below) in two different microservices to store data in two different collections (in the same database).
#Document(collection = "employee")
public interface EmployeeRepository extends MongoRepository<Employee, String>
Is it possible to modify #Document(collection = "employee") to accept runtime parameters? For e.g. something like #Document(collection = ${COLLECTION_NAME}).
Would you recommend this approach or should I create a new Repository?
This is a really old thread, but I will add some better information here in case someone else finds this discussion, because things are a bit more flexible than what the accepted answer claims.
You can use an expression for the collection name because spel is an acceptable way to resolve the collection name. For example, if you have a property in your application.properties file like this:
mongo.collection.name = my_docs
And if you create a spring bean for this property in your configuration class like this:
#Bean("myDocumentCollection")
public String mongoCollectionName(#Value("${mongo.collection.name}") final String collectionName) {
return collectionName
}
Then you can use that as the collection name for a persistence document model like this:
#Document(collection = "#{#myDocumentCollection}")
public class SomeModel {
#Id
private String id;
// other members and accessors/mutators
// omitted for brevity
}
It shouldn't be possible, the documentation states that the collection field should be collection name, therefore not an expression:
http://docs.spring.io/spring-data/data-mongodb/docs/current/api/org/springframework/data/mongodb/core/mapping/Document.html
As far as your other question is concerned - even if passing an expression was possible, I would recommend creating a new repository class - code duplication would not be bad and also your microservices may need to perform different queries and the single repository class approach would force you to keep query methods for all microservices within the same interface, which isn't very clean.
Take a look at this video, they list some very interesting approaches: http://www.infoq.com/presentations/Micro-Services
I used #environment.getProperty() to read from my application.yml. Like so :
application.yml:
mongodb:
collections:
dwr-suffix: dwr
Model:
#Document("Log-#{#environment.getProperty('mongodb.collections.dwr-suffix')}")
public class Log {
#Id
String logId;
...

How do I query for dates in Spring Data MongoDB repository?

My domain object -
Person{
String name;
Date born;
}
and I have a PersonRepository
PersonRepository{
#Query(value="{'born': {$gt: new Date(?0)} }")
findPerson(Date bornAfter);
}
I'm trying to fetch all Persons born after a certain date. That doesn't work though. What am I missing? The date-format for 'born' in mongodb console looks like
ISODate("2011-11-16T09:46:33.750Z")
I tried to look for a unit/integration test for this in data-jpa source. Couldn't find any. Can someone point me to it?
So first you have to make sure you don't mix up Spring Data JPA with Spring Data MongoDB here. I don't think any part of the question is actually targetting some JPA stuff. Here's how your Repository might look like:
public interface PersonRepository extends Repository<Person, Long> {
// Query generated from the method name
List<Person> findByBornGreaterThan(Date born);
#Query("{'born' : { '$gt' : ?0 }}")
List<Person> findPersons(Date born);
}
The latter does not work for 1.0.1.RELEASE and I have created a ticket for that and already fixed it for the upcoming versions 1.0.2.RELEASE and 1.1.0.M1. So you might wanna grab a snapshot build to try it. I have also created a ticket to add Before and After to the supported keywords for more intuitive use than LessThan and GreaterThan currently allow.
Either use the #Query annotation or use the key words in the method name. I'd advise to just stick with the key words in the method name. Which means this annotation can be removed. Means,
List<Person> findByBornGreaterThan(Date born);
will only work. You can refer https://docs.spring.io/spring-data/mongodb/docs/1.2.0.RELEASE/reference/html/repositories.html for more clarification.

Categories