JPA - How to query using Specification and #EmbeddedId? - java

I'm using a JPA query that uses a specification to retrieve entities. When I execute the query, I'm getting the error:
org.springframework.data.mapping.PropertyReferenceException: No property name found for type Task!
I've looked at the answers to similar questions that have been asked on this site previously & tried to model my code to follow the patterns that were recommended but the code is still failing.
When I step through the code with a debugger, the expanded path in the criteria builder is returning the embedded ID class, but when the specification is actually used in the query it looks like the attribute is being applied to the base entity class.
Am I missing something obvious?
Here is the entity class:
#Entity
#Table(name = "TASKS")
public class Task implements Serializable {
#EmbeddedId
private TaskId id;
...more attributes, getters and setters
}
Here is the embedded ID entity class:
#Embeddable
public class TaskId implements Serializable {
#Column(name = "NAME", length = 100)
private String name;
...more attributes, getters and setters
}
Here is the specification builder that matches on the embedded id 'name' attribute:
public class HasTaskNameSpec {
private HasTaskNameSpec() {
}
public static Specification<Task> equals(String name) {
return (root, query, criteriaBuilder) -> {
return criteriaBuilder.equal(root.get("id").get("name"), taskName);
};
}
}
The query is executed on the repository as follows:
List<Task> results = taskRepository.findAll(HasTaskNameSpec.equals("foo"));
The repository itself is very simple:
public interface TaskRepository extends JpaRepository<Task, TaskId>, JpaSpecificationExecutor<Task> {
List<Task> findByIdName(String name);
Page<Task> findByIdName(String name, Pageable page);
}
** EDIT added methods to repository as was suggested below **

Ahh, the root cause was totally in our codebase. There was a sort order being specified on the page that didn't include the embedded "id" attribute. The above code works.

'root.get({embeddedIdName}).get({subPropertyName})' is used to query on embeddedId using specification.
#Embeddable
public class ProjectId implements Serializable{
private static final long serialVersionUID = 1L;
#Column(name = "PROJECT_NAME")
private String projectName;
#Column(name = "ORGANIZATION")
private String organization;
......
......
}
#Entity
#Table(name = "projects")
public class Project {
#EmbeddedId
private ProjectId projectId;
#Column(name = "STARTED_TIME")
private Timestamp startedTime;
#Column(name = "ACTIVE")
private String active;
#Column(name = "DESCRIPTION")
private String description;
......
......
}
In the above snippet, ProjectId is an embedded id. To query on projectName, we should use below snippet.
expression = root.get("projectId").get("projectName");
Demo application link.

Take a look at this link which has a similar query.
EmbbededId Lookup
The final answer suggests that you can add a method to your TaskRepository thus.
public interface TaskRepository extends JpaRepository<Task, TaskId>, JpaSpecificationExecutor<Task> {
public List<Task> findByIdName(String name);
}

Related

Can't generate mapping method with no input arguments with Mapstruct

I’m starting my very first steps with Mapstruct mapper. I want to map a JPA data entity class to a DTO class. This is my source class:
#Entity
#Data
#Table(name = "projects")
public class Project {
#Id
private Long Id;
private String projectName;
private String description;
#OneToMany(mappedBy = "project")
List<Sprint> sprints;
#OneToMany(mappedBy = "project")
List<Epic> epics;
#OneToMany(mappedBy = "project")
List<Story> stories;
public Project(Long id, String projectName, String description) {
Id = id;
this.projectName = projectName;
this.description = description;
}
}
This is my target class:
#Data
#AllArgsConstructor
public class ProjectDTO {
private Long Id;
private String projectName;
private String description;
}
The #Data annotation is from Lombok.
I want to make a mapper to map the Project to ProjectDTO, the attributes like sprints, epics, stories SHOULD NOT be included in ProjectDTO. This is my mapper interface:
#Mapper
public interface ProjectMapper extends Mapper {
ProjectMapper INSTANCE = Mappers.getMapper(ProjectMapper.class)
ProjectDTO projectToProjectDTO(Project project);
}
When I try to build it, this is the error message I got:
[ERROR] Can't generate mapping method with no input arguments.
I guess it’s related to the missing properties in ProjectDTO, but don’t know to solve it. With the #Mapping, I cannot do it like:
#Mapping(source=“sprints”, target= null)
Any help would be appreciated!
Add the '#NoArgConstructor' as well. MapStruct cannot (yet) deal with constructing objects via constructor. Another option would be using '#Builder' in stead if your objects are truly immutable
You should not extend the annotation Mapper. It is enough when you just use it at the type declaration level of your interface

How to depict joins with #Query annotation in Spring JPA Repository method

I am using Spring-Boot with JPA and a MySQL backend. Now I got quite confused about the repositories Spring-Boot provides. I know these are quite powerful (and seem to be quite useful since they can shorten your code a lot). Still, I do not understand how to represent Joins within them, since the result-set should be a combination of specified attributes in the select of a few Entities.
Now let's assume we have three tables Book, Author, AuthorOfBook, where the last one is simply connecting Book and Author by a combined Primary key. I guess we had the following Java-Classes:
Entity Book:
#Entity
#Table(name="BOOK")
public class Book {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "TITLE")
private String title;
}
Entity Author
#Entity
#Table(name="AUTHOR")
public class Author {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "LASTNAME")
private String lastname;
#Column(name = "FIRSTNAME")
private String firstname;
//Let's assume some getters and setters and a constructor
}
Entity AuthorOfBook:
#Entity
#Table(name="BOOK")
public class Book {
#EmbeddedId
private AuthorOfBookId pk;
}
An Embedded ID
#Embeddable
public class AuthorOfBookId implements Serializable {
private int authorId;
private int bookId;
}
Repository
#Repository
public interface AuthorOfBookRepository extends JpaRepository<,AuthorOfBookId> {
}
Now how would I represent that query:
SELECT b.name, a.firstname, a.lastname from AuthorOfBook ab inner join Book b on b.id = ab.book_id inner join Author a on a.id = ab.author_id where a.lastname = :lastname;
in my repository? I know the signature would need to be like
#Query([the query string from above])
public (...) findAuthorAndBookByAuthorLastname(#Param("lastname") String lastname);
but I cannot make out what Type the return would be like. What is that method returning? (simply AuthorOfBook would not work I guess)
You don't want AuthorOfBook as a separate Entity. Book should have a field of type Author as a #ManyToOne relationship. That way, given any Book, you can find the author's details.
If you want to handle audits fields you can do something like this:
Audit class
#Embeddable
public class Audit {
#Column(name = "created_on")
private Timestamp createdOn;
#Column(name = "updated_on")
private Timestamp updatedOn;
#Column(name = "is_deleted")
private Boolean isDeleted;
//getters and setters
}
AuditListener to update automatically audits fields
public class AuditListener {
private Long loggedUser = 1001L;
/**
* Method to set the fields createdOn, and isDeleted when an entity is persisted
* #param auditable
*/
#PrePersist
public void setCreatedOn(Auditable auditable) {
Audit audit = auditable.getAudit();
if (audit == null) {
audit = new Audit();
auditable.setAudit(audit);
}
audit.setIsDeleted(Boolean.FALSE);
audit.setCreatedOn(Timestamp.from(Instant.now()));
}
/**
* Method to set the fields updatedOn and updatedBy when an entity is updated
* #param auditable
*/
#PreUpdate
public void setUpdatedOn(Auditable auditable) {
Audit audit = auditable.getAudit();
audit.setUpdatedOn(Timestamp.from(Instant.now()));
}
}
And add this to the entities
#EntityListeners(AuditListener.class)
public class Book implements Auditable {
#Embedded
private Audit audit;

Spring Data Rest post fails to save nested object

I have these Objects:
#Data
#Entity
#Table
#EqualsAndHashCode(callSuper = true)
public class User extends AbstractEntity implements Serializable {
private static final long serialVersionUID = -55089179131569489L;
private String username;
private String email;
private boolean admin;
private String name;
private String surname;
#OneToMany(mappedBy = "owner")
private List<Ad> ads;
}
and
#Entity
#Table
#Data
#EqualsAndHashCode(callSuper = true)
public class Ad extends AbstractEntity implements Serializable {
private static final long serialVersionUID = -4590938091334150254L;
private String name;
private String description;
private double price;
#Enumerated(EnumType.STRING)
private Category category;
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "OWNER_ID")
private User owner;
}
When I try to execute a POST with an object of type Ad.class with inside an existing object of type User.class (already in the Database) the service saves only the Ad object and the join column "OWNER_ID" remains empty.
I think that the mapping is correct. Could you help me to figure out the problem?
This is my Repository:
#Repository
#Transactional(readOnly = true)
public interface AdRepository extends PagingAndSortingRepository<Ad, String>
{}
and this is my RestRepository
#RepositoryRestResource(collectionResourceRel = "ad", path = "ad")
public interface AdRestRepository extends PagingAndSortingRepository<Ad, String> {}
If I step back a little and generalize your problem,
You are trying to POST a sub resource and expect both actions of
making a new resource (Ad)
making association with the owner (User)
to be happened with a single call.
But unfortunately spring-data-rest does not support such a behavior. You need 2 calls to do this.
One to make the resource (Ad) => POST to /ads with actual payload
Second to make the association => POST to users/{ownerId} with the hateoas link of the resource created by the first call.
Take a look at this section of official documentation.

How can I get List of nested objects using JpaRepository?

I am unable to to get a list of nested objects using a JpaRepository. I'll try to explain what I want using the following code:
AutoService entity:
#Entity
public class AutoService {
#Id
private long id;
#Column(name = "serviceName", nullable = false)
private String serviceName;
}
Service entity:
#Entity
public class Service {
#Id
private long serviceId;
#Column(name = "serviceName", nullable = false)
private String serviceName;
#Column(name = "category", nullable = false)
private String category;
#ManyToOne
#JoinColumn(name = "autoServiceId", nullable = false)
private AutoService autoService;
}
ServiceRepository interface:
public interface ServiceRepository extends JpaRepository<Service, Long> {
List<Service> findByServiceNameAndCategory(String autoServiceName, String categoryName);
}
Business logic:
#org.springframework.stereotype.Service
public class ServiceServiceImpl implements ServiceService {
#Autowired
private ServiceRepository serviceRepository;
#Override
public List<Service> findByAutoServiceAndCategory(String autoServiceName, String serviceCategory) {
return serviceRepository.findByServiceNameAndCategory(autoServiceName, serviceCategory);
}
}
As I am expecting, the code above is unable to provide the desired list of Services matching the provided category and AutoService names.
Can someone provide advice on how should I use my repository to get list of nester services by: autoServiceName and serviceCategory please?
EDIT:
Right now I am using the custom query.
I am using autoServiceId instead of service name right now.
But for some reason I am getting empty list of objects.
Here is my JPA Repo.
public interface ServiceRepository extends JpaRepository<Service, Long> {
#Query("SELECT s from Service s where s.autoService.id = :autoServiceId and s.category = :categoryName")
List<Service> findByServiceNameAndCategory(#Param("autoServiceId") Long autoServiceId, #Param("categoryName") String categoryName);
}
Any suggestions please ?
I think i know the answer. Problem in my category, sended to the server. I wrote it on Russian language. And encoding broken value of category on server side.
1- Use #Embedded and #Embeddable annotation accordingly on your entity object then your method will fetch nested object.
OR
2- #Query annotation is used for writing custom query please refer this link custom query reference
You may have to write a query like this in your ServiceRepository.
public interface ServiceRepository extends JpaRepository<Service, Long> {
#Query("SELECT s from Service s where s.autoService.serviceName = :autoServiceName and s.category = :categoryName")
Set<Round> getRoundsBySessionQuestionId(#Param("autoServiceName") String autoServiceName, #Param("categoryName") String categoryName);
}
Hope this helps. Happy coding !
Since you have a serviceName attribute in both AutoService and Service entities, ServiceRepository.findByServiceNameAndCategory is equivalent to the following SQL query:
SELECT
*
FROM
Service
WHERE
serviceName = ?
AND category = ?
As seen, this query does not hit the AutoService entity at all, which is why the results are not as expected.
The correct repository method is:
public interface ServiceRepository extends JpaRepository<Service, Long> {
List<Service> findByCategoryAndAutoServiceServiceName(String category, String autoServiceName);
}
This method will search the nested AutoService object by its serviceName, as expected.
A sample project is available on Github to show this in action.

Spring Data Mongo - How to query by #DBRef field's id

I am new to Spring Data Mongo so I must be doing something wrong because I can't manage to execute such a simple query. This is my model:
#Document(collection="brands")
public class Brand{
#Id
private int id;
private String name;
...
//getters-setters
}
#Document(collection="models")
public class Model{
#Id
private int id;
private String name;
#DBRef
private Brand brand;
...
//getters-setters
}
I would like to get all models from a brand, so I implement the DAO as follows:
#Repository
public interface IModelDAO extends MongoRepository<Model, Integer>{
#Query(value="{ 'brand.$id' : ?0 }")
public List<Model> findByBrandId(Integer id);
}
If I execute this mongodb query in the shell it works: db.modelss.find({ 'brand.$id' : 1 })
But, the Java application throws the following exception:
Caused by: java.lang.IllegalAccessError
at org.springframework.data.mapping.PropertyReferenceException.detectPotentialMatches(PropertyReferenceException.java:134)
Apparently it is looking for a field $id in Brand class, and since it doesn't exist it fails. So I change the query to the following, so that it navigates to the id field:
#Query(value="{ 'brand.id' : ?0 }")
Now, it doesn't throw an exception but it doesn't find anything in the DB.
Debugging the MongoTemplate.executeFindMultiInternal() method in can see that in
DBCursor cursor = null;
try {
cursor = collectionCallback.doInCollection(getAndPrepareCollection(getDb(), collectionName));
cursor's query is query={ "brand" : 134}. So it makes sense it doesn't find anything. Changing the query value during debugging to query={ "brand.$id" : 134} it works.
So, why isn't the query correctly translated?
The problem was caused by the #Id int type. Changing it to Integer solved it:
#Document(collection="brands")
public class Brand{
#Id
private Integer id;
private String name;
...
//getters-setters
}
#Document(collection="models")
public class Model{
#Id
private Integer id;
private String name;
#DBRef
private Brand brand;
...
//getters-setters
}
try this;
Query que = new Query();
que.addCriteria(Criteria.where("user.$id").is(new ObjectId("123456789012345678901234")));

Categories

Resources