Retrieve data from three entities where two of them are in many-to-one relationships with the last one - json

I am using Spring Boot as REST backend app. Let's say that Person has Cats and Dogs and both Cat and Dog are in many-to-one relathionships with Person. By this, Cat and Dog have Person id as a foreign key. Since I'm using Spring JPA Repository and many-to-one relationship, it is straightforward to get list of cats with their persons and list of dogs with their persons. Those lists are transformed to json and I can access the person's data with frontend app. Here is my problem:
I want to return the list of all persons with all the cats and dogs for each person.
I guess that JPA Repository does not have a default query for my request, so I have to use custom queries. However, I do not know how to make it. I have tried the following one:
#Query("select p, c, d from Person p, Cat c, Dog d where c.person.id = :id and d.person.id = :id and person.id = :id")
List<Object[]> findAllPersonsWithCatsAndDogs(Integer id);
The idea was to run through for loop for each person and to use person's id to retrieve his cats and dogs. The result is the list of objects where each object has the same person, one of his cats, and one of his dogs. I do not like that, because then for all persons I have a list of lists of persons with their cats and dogs.
How to get one list of all persons with all the cats and dogs for each person.
Thanks
Here are the mappings to make it more clear:
#Entity
public class Person {
//there is no mappings because of unidirectional many to one
}
...
#Entity
public class Cat{
#ManyToOne
#JoinColumn(name = "person_id")
private Person person;
}
...
#Entity
public class Dog{
#ManyToOne
#JoinColumn(name = "person_id")
private Person person;
}
So, I have many to one unidirectional, which means that Person does not see cats and dogs.

You should add the reference between person, cat and dog.
#Entity
public class Person {
#OneToMany(cascade = CascadeType.PERSIST, mappedBy="person")
private List<Cat> catList;
#OneToMany(cascade = CascadeType.PERSIST, mappedBy="person")
private List<Dog> dogList;
}
Then if you want to get all the people with their cats and dogs you can do something like
#Query("SELECT p FROM Person P JOIN FETCH p.catList JOIN FETCH p.dogList")
List<Person> findAllPersonsWithCatsAndDogs()
(catList and dogList are just assumed to be the list in person due to not seeing your mapping).
This query will eager fetch your cats and dog list for every person. Then you can do
for (Person p : personRepo.findAllPersonsWithCatsAndDogs()) {
for (Cat c : p.getCastList()) {
}
for (Dog d : p.getDogList()) {
}
}

In general, the DTO Pattern is used to wrap up the necessary data. Therefore, you can use several object mapping frameworks such as ModelMapper, Modelstruct, Dozer.
If you create two repositories for the cat and dog Entity, you can do the following approach:
Method in CatRepository:
List<Cat> findByPersonId(int id);
Method in DogRepository:
List<Cat> findByPersonId(int id);
Querying the persons:
List<Person> persons = personRepository.findAll();
List<PersonDto> personsDto = new ArrayList<>();
foreach(Person p:persons) {
PersonDto dto = modelmapper.map(p, PersonDto.class);
p.setCats(catRepo.findByPersonId(p.getId()));
p.setCats(catRepo.findByPersonId(p.getId()));
personsDto.add(dto);
}

Related

Find rows in a unidirectional many-to-many mapping from the non-owning side in JPA/Nativequery/jpql

I have 2 entities that have a unidirectional many-to-many relationship, with a junction table between to keep track of the relationship. These entities are: Cat and Owner. Owner is the owning side of the relationship, so there is no information about owners in Cat. How do I write a nativequery to get all owners when providing a list of cats, the result of owners should be distinct. I tried solving this with Specifications, but OracleDB does not allow query.distrinct(true) on CLOB(Owner has a multiple CLOB fields). The class looks like this:
Public class Owner {
Long id;
#JoinTable(
name= "owner_cat",
JoinColumns = #JoinColumn(name = "Owner_id"),
inverseJoinColumns = #JoinColumn(name = "Cat_id")
)
Set<Cat> cats;
}
Public class Cat {
Long id;
}
Each row in the junction table have Owner id and Cat id.
I would appreciate if anyone can show me how I can solve this with either nativequery/jpql or jpa Specifications.
I tried getting all Owners by providing a list of Cats

JPA: How to represent JoinTable and composite key of a JoinTable?

Let's say I have a webapp where users can follow other users.
In my database, I have a User table, and a Following table.
The Following table just has two values: a followingUserId and a followedUserId.
In Java, I have a User class. Most tutorials I see involve one object containing a set of objects it's related to. So many tutorials can describe how to have Users have a set of users following that user, and a set of users followed by a user. But that would require a lot of memory.
I'm thinking of an alternate structure where a User object has no info about following. Instead, there is a Following object that looks like this
#Entity
#Table(name = "Following")
public class Following {
RegisteredUser follower;
RegisteredUser followed;
}
and corresponds to the join table. When I want to get all the followers of a user, I can do a query for all Following objects with that user as the follower.
My issues are:
The Followers Table has a composite key of each of the two userids. How can I use annotations to represent that composite key? The #Id annotation denotes a single variable as the key
How can I do such a query?
If it's relevant, I am using MySQL as the db
If using JPA > 2.0, you can mark relationships as your ID:
#Entity
#Table(name = "Following")
#IdClass(FollowingId.class)
public class Following {
#Id
RegisteredUser follower;
#Id
RegisteredUser followed;
}
public class FollowingId implements Serializable {
private int follower;
private int followed;
}
the types within the followingId class must match the type of the RegisteredUser Id. There are plenty of more complex examples if you search on JPA derived ID.
For queries, it depends on what you are looking for. A collection of followers for a particular user could be obtained using JPQL:
"Select f.follower from Following f where f.followed = :user"

Yii2 hasMany relation with same table

Here is the scenario:
I have two tables:
family: id, name
person: id, name, familyId
The foreign key is on person (familyId -> family.id)
In my Person model i want to have a relationship that can include all the person entries that have the same familyId as the current person.
Essentially I want to do $person = Person::find([...])->with('family')->all() to get the current Person model, including an array of family members.
So far I have this on PersonModel:
public function getFamilyMembers()
{
return $this->hasMany(Person::className(), ['familyId' => 'familyId']);
}
...
$person = Person::find()
->with('familyMembers')
->where(['id'=>1]);
foreach($person->family as $m) {
var_dump($m);
}
I know I could do this with a junction table. But since it is a 1:n relationship I would like to avoid the extra Table.
Thanks.
The fast decision is something like this query in your person model :
public function getRelatedPersons()
{
return self::find()->jeftJoin(Family::tableName(), 'person.familyId =
family.id')->where(['person.familyId' => $this->familyId])->all();
}
...
foreach($personModel->relatedPersons as $person) {
var_dunp($preson);
}

Using seperate OneToManys to create a ManyToMany Join table

I've got 2 models User and Exercise. Now any User can have any Exercise. It's a ManyToMany situation. I modeled it with #ManyToMany, but you can't have a duplicate entry in a ManyToMany. A User is likely to do multiple sets of one exercise so I duplicate entries are required. To get round this I created the join table separately called UserExerciseJoin. User and Exercise had ManyToOne relationships with the UserExerciseJoin model. Though this solved the multiple keys issue I now can't delete from the new table. I get an OptimisticLockException from some of the models associated to the Exercise.
My question is: Am I on the right path with the seperate table or is there something I can do to a standard #ManyToMany to make it accept duplicate entries?
If I understand it right in your model, then yes, it is probably not the case for #ManyToMany. It seems to me that you can be better off with a meaningful entity like UserExerciseOccurrence that reference both a User and an Exercise and means a concrete exercise session.
You can also benefit from this approach if you need to save more info about a particular exercise session (like duration, etc).
#Entity
class UserExerciseOccurrence {
#ManyToOne
User user;
#ManyToOne
Exercise exercise;
}
#Entity
class User {
#OneToMany(mappedBy="user", cascade=DELETE)
Set<UserExerciseOccurrence> exerciseOccurrences;
}
#Entity
class Exercise {
#OneToMany(mappedBy="exercise", cascade=DELETE)
Set<UserExerciseOccurrence> exerciseOccurrences;
}
You are on the right path. You should have #OneToMany relation from User class and from Excercise class to this new entity. And in UserExerciseJoin you should have #ManyToOne relations.
So this code should look like this:
#Entity
User {
#OneToMany(mappedBy="user")
private List<UserExercise> userExercises;
....
}
#Entity
Excercise {
#OneToMany(mappedBy="excercise")
private List<UserExercise> userExercises;
....
}
#Entity
UserExercise
{
#ManyToOne
private User user;
#ManyToOne
private Excercise excercise;
...
}
You had an error when deleting this new entity. You had in on some entity related to excercise. It seems that this is because of cascades. You probably set cascades on fields of UserExerciseJoin class. If it was CascadeType.DELETE or CascadeType.ALL cascade then it caused deletion of related entities. So you shouldn't set cascades in UserExercise class. Then deleting of such entity will not cause a problem.

PetaPoco and Many-to-One, One-to-Many and Many-to-Many relations

PetaPoco has introduced Multi-POCO queries in experimental form (for now). As their blog post suggests and the code it provides this looks nice and all in One-to-One relations when we load multi POCOs per row as long as they don't repeat over the records.
What happens when at least one side is many relation? Actually example code is Many-to-One relational data.
Example code is clearly a Many-to-One relation. I haven't tested any PetaPoco code but what does the provided code on the blog post do? Does every Article have their own User object instance even though some may be the same user or do they share the same user object instance?
And what about other Many relation types? How do they work of they work at all?
Usually I map these one-to-many queries myself like the following example.
[TableName("Blogs"), PrimaryKey("BlogId")]
public class Blog {
public int BlogId {get;set;}
public string Title {get;set;}
[Ignore]
public IList<Post> Posts {get;set;}
}
[TableName("Posts"), PrimaryKey("PostId")]
public class Post {
public int PostId {get;set;}
public int BlogId {get;set;}
public string Subject {get;set;}
public string Content {get;set;}
}
public class FlatBlogPost {
public int BlogId {get;set;}
public string Title {get;set;}
public int PostId {get;set;}
public string Subject {get;set;}
public string Content {get;set;}
}
There are two ways I could display a list of posts for one blog or without too much work, all blogs.
1.Two queries -
var Blog = Db.Query<Blog>(1);
var Posts = Db.Query<Post>("where BlogId = #0", 1);
2.One query =
var flat = Db.Query<FlatBlogPost>("select b.blogid, b.title, p.postid, p.subject,
p.content from blogs b inner join posts p on b.blogid = p.blogid where
b.blogid = #0", 1);
var blog = flat
.GroupBy(x=> new { x.BlogId, x.Title })
.Select(x=> new Blog {
BlogId = x.Key.BlogId,
Title = x.Key.Title,
Posts = x.Select(y=> new Post{
PostId = y.PostId,
BlogId = x.Key.BlogId,
Subject = y.Subject,
Content = y.Content
}).ToList()
});
However usually in number 2 I would map directly from the FlatBlogPost object to my viewmodel for which I need to display the data.
Update
Check out these helpers which extend PetaPoco to support basic One-to-Many and Many-to-One queries. schotime.net/blog/index.php/2011/08/21/petapoco-one-to-many-and-many-to-one/ https://schotime.wordpress.com/2011/08/21/petapoco-one-to-many-and-many-to-one/
My 'One to Many' recipe for Petapoco is below. The docs are not clear enough for me. Create a db connection in Linqpad, it will show you all Navigation properties you can add to generated Petapoco poco classes. Execute the same SQL in Linqpad, to make sure it gets the data you expect.
// subclass the generated Parent table pocos, add navigation prop for children
[ResultColumn] public List<DecoratedChild> Child { get; set; }
// subclass the generated Child table pocos, add navigation prop for parent
[ResultColumn] public DecoratedParent Parent { get; set; }
// to get children with parent info
List<DecoratedChild> children = db.Fetch<DecoratedChild, DecoratedParent>(SELECT child.*, parent.* from ...)
// to get children with parent info, using PetapocoRelationExtensions
List<Child> children = db.FetchManyToOne<Child, Parent>(child => child.ID, "select child.*, parent.* from ...
// to get parents with children info, using PetapocoRelationExtensions
List<Parent> parents = db.FetchOneToMany<Parent, Child>(par => par.ID, child => child.ID != int.MinValue, "select parent.*, child.* from ...
SQL select order important, same as in Fetch types list !!!
navigation props will have parent or children data ...
with 3 levels the call will be like:
List<DecoratedGrandChild> grandChildColl = db.Fetch<DecoratedGrandChild, DecoratedChild, DecoratedParent>(SELECT grandch.* , child.*, parent.* from ...)
Personally I don't think you can avoid another database call to get the comments. You could get a list of all comments for the 10 articles (in the same order the articles are stored) by using an IN clause, and loop through them adding them to each article.comments as you go along and the comment.articleid changes. The only way I can see getting this information in a single sql call would be to use a join but then you'd get duplicate article details for each comment, so maybe this isn't a problem with petapoco, just one of those things that'll never be perfect