spring jpa projection nested bean - spring

is it possible to have a projection with nested collection with Spring JPA?
I have the following 2 simple entity (to explain the problem)
#Entity
#Table(name = "person")
public class Person implements Serializable {
private Integer id;
private String name;
#OneToMany
private List<Address> addressList = new ArrayList<>();
}
#Entity
#Table(name = "address")
public class Address implements Serializable {
private Integer id;
private String city;
private String street;
}
Is it possible to have a projection of Person with following attributes filled in ? {person.name, address.city}
I might be wrong in semantics of word Projection. but the problem is what i need to achieve. Maybe it is not possible with Projection, but is there another way to achieve the end goal? Named Entity graph perhaps ?
P.S. please suggest a solution for Spring JPA not Spring Jpa REST
thanks in advance

You're right, Entity Graphs serve this exact purpose - control field loading.
Create entity graphs dynamically from the code or annotate target entities with Named Entity Graphs and then just use their name.
Here is how to modify your Person class to use Named Entity Graphs:
#Entity
#Table(name = "person")
#NamedEntityGraph(name = "persion.name.with.city",
attributeNodes = #NamedAttributeNode(value = "addressList", subgraph = "addresses.city"),
subgraphs = #NamedSubgraph(name = "addresses.city", attributeNodes = #NamedAttributeNode("city")))
public class Person implements Serializable {
private Integer id;
private String name;
#OneToMany
private List<Address> addressList;
}
And then when loading your person:
EntityGraph graph = em.getEntityGraph("person.name.with.city");
Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);
return em.find(Person.class, personId, hints);
The same applies for queries, not only em.find method.
Look this tutorial for more details.

I think that that's not usual scenario of Data JPA usage. But you can achieve your goal with pure JPQL:
SELECT a.street, a.person.name FROM Address a WHERE …
This solution has 2 drawbacks:
It forces you to have bidirectional relationship Address ←→ Person
It returns List
Another solution (and that's preferred JPA way) is to create DTO like this:
class MyPersonDTO {
private String personName;
private List<String> cities;
public MyPersonDTO(String personName, List<Address> adresses) {
this.personName = personName;
cities = adresses
.stream()
.map(Address::getCity)
.collect(Collectors.toList());
}
}
And the execute JPQL query like this:
SELECT NEW package.MyPersonDTO(p.name, p.addressList) FROM Person p WHERE …
Return type will be List<MyPersonDTO> in that case.
Of course you can use any of this solutions inside #Query annotation and it should work.

Related

Spring hibernate orderBy on list element

#Entity
class Person{
private int id;
#OneToMany(mappedBy=owner)
private List<Pet> pets;
}
#Entity
class Pet{
private name;
private ZonedDateTime birthDate;
#ManyToOne
#JoinColumn(name="owner_id")
private Person owner;
}
I want to find all the persons and order them by their oldest pet birthday
The only way I can solve this is through #Formula , something like
#Entity
class Person{
private int id;
private List<Pet> pets;
#Formula("(SELECT p.birth_date FROM pet p WHERE p.owner_id = id order by p.birth_date ASC LIMIT 1)")
private ZonedDateTime oldestPetBirthday;
}
then
public List<Person> findPersonByOrderByOldestPetBirthdayAsc
But I don't want to touch raw sql, I am looking for something like
public List<Person> findPersonByOrderByPetsTop1OrderByBirthDateAsc
OR by using pageable something like:
PageRequest.of(page,pageSize,Sort.by(ASC, "pets.sort(BirthDateComparator).get(0)"))
is that possible?
Try to use #OrderBy annotation from #javax.persistence.OrderBy package on your one to many collection object.
#OrderBy("birthDate")
private List<Pet> pets;
Your solution with the formula is ok but suffers from some issues. Anyway, since you don't want to write SQL, you will have to use something like Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(Person.class)
public interface PersonDto {
#IdMapping
Long getId();
#Limit(limit = "1", order = "birthDate desc)
#Mapping("pets")
OldestPetDto getOldestPet();
#EntityView(Pet.class)
interface OldestPetDto {
#IdMapping
Long getId();
ZonedDateTime getBirthDate();
}
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
PersonDto a = entityViewManager.find(entityManager, PersonDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<PersonDto> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!
Also, you can add a Sort for oldestPet.birthDate and it will work just like you would like it to!

Cyclic recursion call in neo4j

I am trying to input few graph nodes in neo4j db using spring-data-neo4j.
Nodes are having the following relationship.
Project -> Cluster -> Entity -> Methods and Entity node has relationship with its ownn, making it bidirectinoal relationship.
Entity class is defined as follows.
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
#NodeEntity
public class Entity {
public int id;
public String type;
public String name;
public String entityId;
Public String projectId;
#Relationship(type = "CONNECTS_TO", direction = Relationship.INCOMING)
private Set<Entity> entityIdr;
}
Below error is thrown while trying to insert Cluster and Entity nodes, what could be possible resolution to avoid following ?
com.fasterxml.jackson.databind.JsonMappingException: Infinite
recursion (StackOverflowError) (through reference chain:
I assume that you try to serialize the data and expose it on the web layer or similar, right?
The jackson serialization needs to get more informations on how to break the cycles the Java data model describes.
So either you ignore a property with #JsonIgnore like
#JsonIgnore
#Relationship(type = "CONNECTS_TO", direction = Relationship.INCOMING)
private Set<Entity> entityIdr;
, but that seems at least for the first level like a loss of information,
or
#JsonIgnoreProperties("entityIdr")
#Relationship(type = "CONNECTS_TO", direction = Relationship.INCOMING)
private Set<Entity> entityIdr;
We wrote this up in the documentation for Neo4j-OGM: https://neo4j.com/docs/ogm-manual/current/reference/#_a_note_on_json_serialization

Selecting from Multiple Tables in Spring JPA with Pageable and Sorting

I saw the Selecting from Multiple Tables in Spring Data already had the solution for multiple tables.
I would like to know if it is possible to write custom query that has tables with pageable and sorting feature at the same time in Spring JPA/DATA.
SELECT s.service_id, s.name, us.rating_id
FROM services s,
ratings r,
user_services us
where
us.service_id = s.service_id and
us.rating_id = r.rating_id and
us.user_id= ?
;
Thanks for you help in advance.
Sorting feature is under question, but pagination is possible to use.
Assume that we have:
#Entity
public class Service {
#Id
private Long id;
private String name;
//...
}
#Entity
public class UserService {
#Id
private Long id;
#ManyToOne
User user;
#ManyToOne
Service service;
#ManyToOne
Rating rating;
//...
}
Then we create a projection:
public interface ServiceRating {
Long getServiceId();
String getServiceName();
Long getRatingId();
}
And then create a query method supported pagination:
public interface UserServiceRepo extends CrudRepository<UserService, Long> {
#Query("select s.id as serviceId, s.name as serviceName, us.rating.id as ratingId from UserService us join us.service s where us.user.id = ?1")
Page<ServiceRating> getServiceRating(Long userId, Pageable pageable);
}
(Since this query does not contain grouping it's not necessary to use an additional countQuery (see the parameter of #Query)).
Test:
Page<ServiceRating> pages = userServiceRepo.getServiceRating(1L, new PageRequest(0, 10));
assertThat(pages.getContent()).hasSize(10));
UPDATE
Sorting also working perfectly.
Just create a Sort object, specify direction and filed name (from the projection):
Sort sort = new Sort(Sort.Direction.ASC, "serviceName");
userServiceRepo.getServiceRating(1L, new PageRequest(0, 10, sort));

Cannot Query Neo4j Repositories

Hey everyone I am fairly new to Neo4j and am having an issue querying my repositories.
Repository is the follow:
public interface NodeOneRepository extends GraphRepository<NodeOne> {
List<NodeOne> findByNodeTwoNodeThreeAndActiveTrue(NodeThree nodeThree);
}
My entities are the following:
#NodeEntity(label = "NodeOne")
public class NodeOne {
#GraphId
private Long id;
private Boolean active = TRUE;
#Relationship(type = "IS_ON")
private NodeTwo nodeTwo;
}
#NodeEntity(label = "NodeTwo")
public class NodeTwo {
#GraphId
private Long id;
#Relationship(type = "CONTAINS", direction = "INCOMING")
private NodeThree nodeThree;
#Relationship(type = "IS_ON", direction = "INCOMING")
private List<NodeOne> nodeOnes = new ArrayList<>();
}
#NodeEntity(label = "NodeThree")
public class NodeThree {
#GraphId
private Long id;
#Relationship(type = "CONTAINS")
private List<NodeTwo> nodeTwos = new ArrayList<>();
}
Getters & Setters omitted. When I call the method I get an empty list. Is there something I am doing incorrectly?
You didn't describe exactly what you wanted to achieve, but I can see two problems:
Problem 1:
The current version of Spring Data Neo4j and OGM only allow nested finders, that is, finders that specify a relationship property, to one depth.
Supported
findByNodeTwoSomePropertyAndActiveTrue(String relatedNodePropertyValue)
Not Supported
findByNodeTwoNodeThree //Nesting relationships in finders is not supported
Problem 2:
Derived Finders Allow Matching Properties and Nested Properties. Not a whole instance of that class.
You can probably achieve what you would like using a custom query.
#Query("custom query here")
List<NodeOne> findByNodeTwoNodeThreeAndActiveTrue(NodeThree nodeThree);
If you need help to write a custom query, you can post another question or join the neo4j-users public slack channel.

Proper Way to layer Spring JPA based DAO using Spring Boot Framework

Am new to Spring Boot & JPA...
Let's say I have two entities mapped to two tables which are joined in a database.
Student-1------<-Course
Also, lets presume that the database is already created and populated.
This depicts that one student has many courses...
My Student Entity:
#Entity
public class Student {
#OneToMany(mappedBy="student")
private List<Courses> courses;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "Student_Id")
private long studentId;
#Column(name = "Student_Name")
private String studentName;
protected Student() { }
// Getters & Setters
}
My Course Entity:
#Entity
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "Course_Id")
private long courseId;
#Id
#Column(name = "Student_Id")
private long studentId;
#ManyToOne
#PrimaryKeyJoinColumn(name="Student_Id", referencedColumnName="Student_Id")
private Student student;
#Column(name = "Course_Name")
private String courseName;
// Getters & Setters
}
In Spring Boot's Tutorial Guides, it illustrates how to extend a CrudRepository interface, but
it doesn't specify how to setup a Spring based DAO which contains custom finder methods which use HQL and EntityManager inside it.
Is the following DAO and DaoImpl correct?
public interface CourseDao {
List<Course> findCoursesByStudentName(String studentName);
}
#Repository
public class CourseDaoImpl implements CourseDao {
#PersistenceContext
EntityManager em;
public List<Course> findCoursesByStudentName(String studentName) {
String sql = "select c.courseName" +
"from Course c, Student s " +
"where c.course_id = s.student_id " +
"and s.studentName = :studentName ";
Query query = em.createQuery(sql);
query.setParameter("studentName", studentName);
return query.getResultList();
}
}
And then in the client code, for example, in the main class:
public class Application {
#Autowired
CustomerDao dao;
public static void main (String args []) {
List<Course> courses = dao.findCoursesByStudentName("John");
}
}
Is this the standard way to use HQL inside Spring DAOs ? I've seend examples of the #Transactional annotation being prepended to the DAO class's impl (e.g. CustomerDAOImpl) ?
Please let me know if this is the write way to structure my Spring Boot app or am I supposed to extend / add to the CrudRepository only?
If someone could correct my example and point me to a URL which talks about HQL using Entities that are joined, I would be very grateful.
The Spring Boot guides didn't depict joins or DAOs - I just need to learn how to properly create finder methods which emulate select statement which return lists or data structures.
Thanks for taking the time to read this...
If I understood your question correct you do have two questions:
How to create a DAO and DAOImpl?
Where to put your Transaction annotations?
In regards to the first question I want to point out that this is a question in regards to spring-data-jpa using Hibernate as a JPA provider, not spring-boot.
Using Spring Data I usually skip completely to create a DAO but directly use a Custom Repository extending a standard one like CrudRepository. So in your case you don't even have to write more code than:
#Repository
public interface StudentRepository extends CrudRepository<Student, Long> {
List<Student> findByStudentName(String studentName);
}
Which will be sufficient and Spring Data will take care of filling it with the correct implementation if you use
#Autowired
StudentRepository studentRepo;
in your service class. This is where I also usually annotate my methods with #Transactional to make sure that everything is working as expected.
In regards to your question about HQL please look into the spring data jpa documentation, which points out that for most of the cases it should be sufficient to stick to proper named methods in the interface or go for named queries (section 3.3.3) or use the #Query annotation (section 3.3.4) to manually define the query, e.g. should work (didn't tried):
#Repository
public interface #CourseRepository extends CrudRepository<Course, Long> {
#Query("select c.courseName from Course c, Student s where c.course_id = s.student_id and s.studentName = :studentName")
public List<Course> findCoursesByStudentName(String studentName);
}
If you annotate your CourseDaoImpl with #Transactional (Assuming your have defined JpaTransactionManager correctly) You can just retrieve the Student with the matching name and call the getCourses() method to lazy load the Courses attached to that student. Since findCoursesByStudentName will run within a Transaction it will load the courses just fine.
#Repository
#Transactional(readOnly=true)
public class CourseDaoImpl implements CourseDao {
#PersistenceContext
EntityManager em;
public List<Course> findCoursesByStudentName(String studentName) {
String sql = "select s " +
"from Student s " +
"where s.studentName = :studentName ";
Query query = em.createQuery(sql);
query.setParameter("studentName", studentName);
User user = query.getSingleResult();
if(user != null) {
return user.getCourses();
}
return new ArrayList<Course>();
}
}

Resources