CrudRepository: find "Field" By "OtherField" - spring-data-jpa

To avoid writing query like this:
#Query("SELECT p.name FROM Person p WHERE p.email =:email")
String findNameByEmail(#Param("email") String email);
I would like to know if there is a way to write it without #Query annotation:
String findNameByEmail(#Param("email") String email);
I can't find anything like this and I am not sure if it is possible or if I am missing something.

You can use projection with one property:
public interface OnlyName {
String getName();
}
OnlyName findByEmail(String email);

Related

Spring Data - MongoRepository - #Query to find item within nested list of objects

I'm trying to query MongoDB to return a single Answer object contained within a QuestionDocument object.
I am using Spring, MongoRepository, and JDK 11.
My QuestionDocument POJO:
#Data
#Document(collection = "Questions")
#AllArgsConstructor(onConstructor = #__(#Autowired))
#NoArgsConstructor
public class QuestionDocument {
#Id
private String questionId;
(...)
private List<Answer> answers;
(...)
}
My Answer POJO:
#Data
public class Answer implements Serializable {
private String answerId;
(...)
My QuestionRepository:
#Repository
public interface QuestionRepository extends MongoRepository<QuestionDocument, String> {
#Query(value = "{ { 'questionId' : ?0 }, { 'answers.$answerId' : ?1 } }")
Answer findByQuestionIdAndAnswerId(String questionId, String answerId);
My QuestionServiceImpl:
public getAnswer(String questionId, String answerId){
Answer answer = findByQuestionIdAndAnswerId(questionId, answerId);
return answer;
}
protected Answer findByQuestionIdAndAnswerId(String questionId, String answerId){
Answer answer;
try {
answer = questionRepository.findByQuestionIdAndAnswerId(questionId, answerId);
} catch (Exception e) {
throw new IllegalArgumentException("There is no answer with this ID.");
}
return answer;
}
When I hit my endpoint in Postman, the correct response body appears, but all of its values are null. I have verified that the correct questionId and answerId are passed in my parameters.
I have also consulted several additional SO posts and Spring and MongoDB documentation, but so far, implementing what I've read regarding traversing nested objects by property hasn't helped.
How does my #Query value need to change to properly return a specific Answer object from this nested list of answers?
I have attempted to create findBy methods like:
findByQuestion_Answers_AnswerId(String answerId);
I have attempted to add #DBRef above my List<Answer> answers, and adding #Document(collection = "Answers") and #Id above private String answerId; in my Answer POJO. I then cleared my database, created a new question and answer, and queried for the specific answerId, and still returned null data.
What I expect, is that given the questionId and answerId, the query will return one Answer object and its associated information (answerBody, answerAuthor, etc.).
My postman response states SUCCESS, but the data is null.
You can change the Query to this.
#Query(value = "{{'questionId' : ?0, 'answers.answerId' : ?1}}")
or, just define this method.
findByQuestionIdAndAnswerId(String questionId, String answerId);
The return type will be of QuestionDocument, not Answer.
More details here.

How to delete a field in all documents of mongodb collection using MongoRepositoty #Query

I have a collection:
public class Person {
private String name;
private Integer age;
}
I want to delete field age in all the documents. So the schema gonna look like that:
public class Person {
private String name;
}
I'm using MongoRepositoty and I've been trying to write this method:
#Repository
public interface PersonRepository extends MongoRepository<Person, String> {
#Query("{$updateMany: [ {}, { $unset: {'age': ''} }]}")
void deleteAgeField();
}
I tried different brackets and quotes, but it all ends up with errors. What's wrong with my syntax? I see it differs from how we write queries in mongo console. For instance, round brackets and double quotes are not allowed here.
You could use simply
#Query(value = "{}", delete = true)
void deleteAgeField();
A solution I've found is simply to set the field to null:
repository.findAll().forEach(
person -> {
person.setAge(null);
repository.save(person);
});
As Mongo is not relational DB, it contains documents not tables. It has json presentation of objects, and when a field=null, it disappears. Maybe my explanation is a bit twisted, please correct me if I'm wrong.

Searching by query for attributes of complex objects in Java EE

I have created the object Person, I can deleted and modify it and I can also search for Person by his name or phonenumber... but I don't know for exemple how to search for a person by his ** home address**. Here is my code:
My entity Person.java:
public class Person{
private Long id;
private String name;
#ManyToOne(cascade = CascadeType.ALL)
private Address address;
....
}
My entity Address.java
public class Address{
...
private String streetName;
...
}
And here is the most interesting function that I am trying to modify to get what I want, I would like to search for Persons who live in xxx (streetName = xxx). Here is my function getByQuery:
public List<Person> getByQuery(PersonSearchQuery searchQuery) {
Map<String, String> criteriaQuery = new HashMap<String, String>();
if (searchQuery.getName() != null)
criteriaQuery.put("name",searchQuery.getName());
TypedQuery<Person> query = this.findByQuery(criteriaQuery);
return query.getResultList();
}
The object PersonSearchQuery contains just to attributes name (String) and streetName (String) and their getters.
Function findByQuery:
public TypedQuery<T> findByQuery(Map<String, String> criteriaQuery) {
CriteriaBuilder builder = this.em.getCriteriaBuilder();
CriteriaQuery<T> criteria = builder.createQuery(this.entityClass);
Root<T> root = criteria.from(this.entityClass);
criteria.select(root);
Predicate predicate = builder.conjunction();
if (criteriaQuery.size() != 0) {
for (String key : criteriaQuery.keySet()) {
try{
predicate = builder.and(predicate, builder.equal(root.<String>get(key), criteriaQuery.get(key)));
}catch(IllegalArgumentException e){
continue;
}
}
}
criteria.where(predicate);
return this.em.createQuery(criteria);
}
So I can search for Persons by their names by I cannot search for them by streetName the problem is my function getByQuery I would like to do something like this:
if (searchQuery.getStreetName() != null)
criteriaQuery.put("Address.streetName",searchQuery.getStreetName());
The problem is I don't know how to define the key in this case. Thanks for your help
I only use CriteriaBuilder if I have several similar Entities which needs to be used/rendered in the same way, so if person is the only Entity with an Address reference I would just use JPQL, like this:
entityManager.createQuery(
"select p from Person p where p.address.streetName like :streetName", Person.class)
.setParameter("streetName", "xyz" + "%").getResultList()
The main reason I tend to avoid CriteriaBuilder, is because it has a rather steep learning curve, and you need to write a lot of code to express very simple concepts. In contrast any developer familiar with SQL can read and maintain JPQL code.
These days I always use frameworks, like DeltaSpike Data (for EE) and Spring Data, they both implements most of the basic DAO/Repository features, so If you don't mind an extra dependency (and some magic) it can save you a lot of boilerplate JPA code.

Is there a way to transform objects that spring data repositories return?

Right now I have an entity object and a DTO. The repository returns a list of objects arrays when I do a simple example like: findById(). Is there a way to easily map the return type to be a custom DTO object rather than always return entity objects?
Example is below:
#Query("Select f.id, f.name from Food f where f.id = :id")
public List<Object[]> findById(#Param("id") String id);
My DTO object looks like:
FoodDto{
private String id;
private String name;
}
Right now I've only ever been able to get repositories to return a List< Object[] > type.
Try this.
#Query("Select new package.FoodDto(f.id, f.name) from Food f where f.id = :id")
public List<FoodDto> findById(#Param("id") String id);
Assuming class FoodDto is in package, if not you need to set the full package.
Also I assume the FoodDto have a constructor that match
public FoodDto(int id, String name){
//Variable assignation
}
I never tried in spring-jpa but that works in JPQL so I assume it will work XD

Best practices using JPA to load object from query with a list member

I'm getting started with JPA and I want to know the best way to achieve something like this:
I need to implement a service that returns a list of scripts, and each script has a list of parameters. I simplified the query, but it is something like this:
(SELECT
p.DESC
FROM
INPUT_PARAMETERS p
INNER JOIN SCRIPT_PARAMS sp ON p.PARAM_ID = sc.PARAM_I
INNER JOIN SCRIPT s ON s.SCRIPT_ID = sc.SCRIPT_ID
WHERE
s.NAME = 'name')
UNION
(SELECT
p.DESC
FROM
OUPUT_PARAMETERS p
INNER JOIN SCRIPT_PARAMS sp ON p.PARAM_ID = sc.PARAM_I
INNER JOIN SCRIPT s ON s.SCRIPT_ID = sc.SCRIPT_ID
WHERE
s.NAME = 'name')
And I want to return a list of POJO objects that are something like:
public class Script {
private String name;
private List<String> params;
public Script(){}
public String getName()
{
return name;
}
public void setName(String pName)
{
name = pName;
}
public List<String> getParams()
{
return params;
}
public void setParams(List<String> pParams)
{
params = pParams;
}
}
I want to know what is the best way to load the POJO object from a query. Is it best to build a JPQL query, or can I use a Native Named Query, and do I need to get a object[] and construct my POJOs manually, or can I use JPA to load the objects from the query?
You can only construct instances of Script manually. When querying with JPQL, reason is that constructor expression cannot take List as argument. Additionally JPQL does not have unions.
Also with SQL query you cannot construct Script directly, because result can be only scalars or entity.