How to properly do user favorites? JPA - java

I'm trying to create a favorites mapping and I'm somewhat confused on how to do it,
Here is my user class, for the sake of clarity it has only 2 bindings(1st one(file) - #OneToMany, 2nd one(favorite file) - #ManyToMany.
#OneToMany(mappedBy = "app_user")
#JsonIgnoreProperties("app_user")
private List<File> file;
#ManyToMany(mappedBy = "app_user")
#JsonIgnoreProperties("app_user")
private Set<File> favorite_file;
And I've just realized that intellij complains about it for some reason.
it tries to connect only to 1 field on the other side:
#JsonIgnoreProperties({"file", "profile"})
#ManyToOne
private AppUser app_user;
what would be a better way to do this? because Intellij complains about it and it works but only with native queries(since JPA claims that there is no difference between files and favorite files, but in native query there is a ManyToMany table so it works there)
My goal is to make users have favorite files distinct from files.

It tries to connect only to 1 field on the other side
Well with #OneToMany(mappedBy = "app_user") you explicitly tell it to map it to the same field.
Which doesn't make sense, because
why would you have the same relation represented twice in one entity
The relationship types don't match. You can't have one relationship be ManyToOne from the one side and in the inverse direction be ManyToMany
It looks you have some clarification to do on what you want to represent by your model.
I see multiple possible variants.
The general mapping in AppUser is correct
So an AppUser has multiple File instances but each File is only owned by a single AppUser (if at all).
But an AppUser favorites many files and each File might be favoured by many users.
#Entity
class AppUser {
#OneToMany(mappedBy = "ownedBy")
private List<File> files;
#ManyToMany(mappedBy = "favoredBy")
private Set<File> favoriteFiles;
}
#Entity
class File {
#ManyToOne
private AppUser ownedBy;
#ManyToMany
private Set<AppUser> favoredBy;
}
The important part is that the defining relationships (the one in File) are two distinct relationships.
I removed the JSON annotations, because they are independent of the discussed problem.
Note that it is an interesting discussion if the relationship even should be bidirectional, but that is a different topic.
The favourite files are a subset of the owned files.
Then a more fitting model would be to model favouriting as an attribute of the relationship between AppUser and File
#Entity
class AppUser {
#OneToMany
private List<FileRelation> files;
eFiles;
}
#Entity
class FileRelation {
#ManyToOne
File file;
boolean isFavourite;
}
#Entity
class File {
}
I made the relationships unidirectional and the overall relation between user and file a many to many.
You might want to adapt that to your needs.
Side remark: Please use standard Java naming conventions when coding in Java (no snake case for attributes). Otherwise you are seriously hurting other Java developers.

Related

Best Way to SELECT subset of properties for associated #Entity

I am trying to choose the properties of associated entities that will be loaded.
For example:
#Entity
#Getter #Setter
public class Book {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "first")
private String first;
#Column(name = "second")
private String second;
#OneToMany(mappedBy = "book", cascade = CascadeType.ALL)
private List<Page> pages = new ArrayList();
}
#Entity
#Getter #Setter
public class Page {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "number")
private Integer number;
#Column(name = "content")
private String content;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "book_id")
private Book book;
}
I would like to have just a SELECT on the Book's first and the content of all associated Page entities
book
- first
- pages
- content
For example, in PostgreSQL this might look something like
SELECT book.first,
array_agg(page.content || ' ')
FROM book
LEFT JOIN page
ON page.book_id = book.id
GROUP BY book.first
I've done my research on how you could go about doing this, most mentioned solution is to use Spring DATA JPA Projections (ref)
I did the following:
public interface FindAllBookProjection {
String getFirst();
List<PageProjection> getPages();
interface PageProjection {
Integer getNumber();
}
}
The issue with projections is that they cause the famous N+1 select problem as the pages are loaded lazily.
I also couldn't find a way to use projections with #Query in the #Repository for the nested properties.
There are other mentions online to use #EntityGraph. From what I understand #EntityGraph will not work as it also selects all properties, specifying only what Entity associations should be loaded.
There are also suggestions on using other libraries like Blaze Persistence (ref) or Hibernate's ResultTransformer (ref) . I would prefer to use only Spring DATA JPA instead of introducing another library for this functionality and writing ResultTransformers seems like adding a lot of boilerplate code to the mix.
To summarize my question is what is the best way to choose what properties are selected for #Entity associations. The main goal is to avoid pulling unnecessary amount of data from the database. The given example above is for demonstration, the data that I am working on includes over 10 columns and spans across 3-4 entities. Having control over the data means better performance.
I am the creator of Blaze-Persistence and I can tell you that if there were an easy way to do this, I would not have created Entity-Views. You can read some of the articles I wrote about this and you will realize that doing this yourself, like you already figured, will require lots of boilerplate code. Doing this efficiently, will require even more code and in the end, you will probably end up with a solution that is inferior to Blaze-Persistence Entity-Views in one way or another and has probably no documentation.
Spring Data Projections is just limited and as I tried to outline many times before, Blaze-Persistence Entity-Views is like Spring Data Projections on steroids.
If you have "just" one or two simple mappings, you might be able to get this done by introducing special #Immutable #Entity, maybe even with #Subselect in this particular case to model what you need, but believe me, this only works good on a small scale. Apart from that, Blaze-Persistence which works on top of JPA/Hibernate enables the use of a lot of advanced SQL features, which you usually can't use from within plain JPA/Hibernate.
In my opinion, ORM libraries should use whole objects, that means loading all data into the program and then transforming/filtering according to logic.
For specific use-cases, where performance is really hindered, I'd use entities in
the database, such as Views/Procedures.
In your case, i'd create a View:
CREATE VIEW book_content as
SELECT book.first as [first],
array_agg(page.content || ' ') as [content]
FROM book
LEFT JOIN page
ON page.book_id = book.id
GROUP BY book.first
And then create a #Repository and #Entity for it in spring.

How to keep current and previous location field in database?

I am implementing solution to office relocation. One of the main task is to show where assets/furniture should be moved. I have an entity like this (shorted version):
#Entity
#Table
class Asset extends BaseEntity{
private String name;
#Embedded
private Localization localization;
Localization contains Floor (floor_id, floor_name), x_axis, y_axis;
Here is a graphic representation of my problem (on frontend side).
How I can keep current and next (destination) localization in database? I've tried some ways but they did not work. Like here:
#Entity
#Table
class Asset extends BaseEntity{
private String name;
#AttributeOverrides({
#AttributeOverride(name="floor.id", column=#Column(name="current_floor_id")),
})
#Embedded
private Localization localization;
#AttributeOverrides({
#AttributeOverride(name="floor.id", column=#Column(name="destination_floor_id")),
})
#Embedded
private Localization destinationLocalization;
But it yells at me that floor_id is not unique and should be inserted=false and updatable=false.
Another way that I've tried was #OneToOne relation with new entity Column, or even joining in query without relations.
I've heard about Hibernate Envers, maybe that is solution?
If you need just 2 locations, I would suggest you use the approach you already mentioned i.e. keep both old and current location infos in the entity.
If you need a history, you could create an entity e.g. AssetLocation with a surrogate id and refer to that in the Asset. Just don't delete the old AssetLocation objects, then you can create a one-to-many association to refer to the old positions.
You could use envers as well, but there are some limitations you have to be aware of which you can read about in the documentation.

How to maintain bi-directional relationships with Spring Data REST and JPA?

Working with Spring Data REST, if you have a OneToMany or ManyToOne relationship, the PUT operation returns 200 on the "non-owning" entity but does not actually persist the joined resource.
Example Entities:
#Entity(name = 'author')
#ToString
class AuthorEntity implements Author {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id
String fullName
#ManyToMany(mappedBy = 'authors')
Set<BookEntity> books
}
#Entity(name = 'book')
#EqualsAndHashCode
class BookEntity implements Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id
#Column(nullable = false)
String title
#Column(nullable = false)
String isbn
#Column(nullable = false)
String publisher
#ManyToMany(fetch = FetchType.LAZY, cascade = [CascadeType.ALL])
Set<AuthorEntity> authors
}
If you back them with a PagingAndSortingRepository, you can GET a Book, follow the authors link on the book and do a PUT with the URI of a author to associate with. You cannot go the other way.
If you do a GET on an Author and do a PUT on its books link, the response returns 200, but the relationship is never persisted.
Is this the expected behavior?
tl;dr
The key to that is not so much anything in Spring Data REST - as you can easily get it to work in your scenario - but making sure that your model keeps both ends of the association in sync.
The problem
The problem you see here arises from the fact that Spring Data REST basically modifies the books property of your AuthorEntity. That itself doesn't reflect this update in the authors property of the BookEntity. This has to be worked around manually, which is not a constraint that Spring Data REST makes up but the way that JPA works in general. You will be able to reproduce the erroneous behavior by simply invoking setters manually and trying to persist the result.
How to solve this?
If removing the bi-directional association is not an option (see below on why I'd recommend this) the only way to make this work is to make sure changes to the association are reflected on both sides. Usually people take care of this by manually adding the author to the BookEntity when a book is added:
class AuthorEntity {
void add(BookEntity book) {
this.books.add(book);
if (!book.getAuthors().contains(this)) {
book.add(this);
}
}
}
The additional if clause would've to be added on the BookEntity side as well if you want to make sure that changes from the other side are propagated, too. The if is basically required as otherwise the two methods would constantly call themselves.
Spring Data REST, by default uses field access so that theres actually no method that you can put this logic into. One option would be to switch to property access and put the logic into the setters. Another option is to use a method annotated with #PreUpdate/#PrePersist that iterates over the entities and makes sure the modifications are reflected on both sides.
Removing the root cause of the issue
As you can see, this adds quite a lot of complexity to the domain model. As I joked on Twitter yesterday:
#1 rule of bi-directional associations: don't use them… :)
It usually simplifies the matter if you try not to use bi-directional relationship whenever possible and rather fall back to a repository to obtain all the entities that make up the backside of the association.
A good heuristics to determine which side to cut is to think about which side of the association is really core and crucial to the domain you're modeling. In your case I'd argue that it's perfectly fine for an author to exist with no books written by her. On the flip side, a book without an author doesn't make too much sense at all. So I'd keep the authors property in BookEntity but introduce the following method on the BookRepository:
interface BookRepository extends Repository<Book, Long> {
List<Book> findByAuthor(Author author);
}
Yes, that requires all clients that previously could just have invoked author.getBooks() to now work with a repository. But on the positive side you've removed all the cruft from your domain objects and created a clear dependency direction from book to author along the way. Books depend on authors but not the other way round.
I faced a similar problem, while sending my POJO(containing bi-directional mapping #OneToMany and #ManyToOne) as JSON via REST api, the data was persisted in both the parent and child entities but the foreign key relation was not established. This happens because bidirectional associations need to be manually maintained.
JPA provides an annotation #PrePersist which can be used to make sure that the method annotated with it is executed before the entity is persisted. Since, JPA first inserts the parent entity to the database followed by the child entity, I included a method annotated with #PrePersist which would iterate through the list of child entities and manually set the parent entity to it.
In your case it would be something like this:
class AuthorEntitiy {
#PrePersist
public void populateBooks {
for(BookEntity book : books)
book.addToAuthorList(this);
}
}
class BookEntity {
#PrePersist
public void populateAuthors {
for(AuthorEntity author : authors)
author.addToBookList(this);
}
}
After this you might get an infinite recursion error, to avoid that annotate your parent class with #JsonManagedReference and your child class with #JsonBackReference. This solution worked for me, hopefully it will work for you too.
This link has a very good tutorial on how you can navigate the recursion problem:Bidirectional Relationships
I was able to use #JsonManagedReference and #JsonBackReference and it worked like a charm
I believe one can also utilize #RepositoryEventHandler by adding a #BeforeLinkSave handler to cross link the bidirectional relation between entities. This seems to be working for me.
#Component
#RepositoryEventHandler
public class BiDirectionalLinkHandler {
#HandleBeforeLinkSave
public void crossLink(Author author, Collection<Books> books) {
for (Book b : books) {
b.setAuthor(author);
}
}
}
Note: #HandlBeforeLinkSave is called based on the first parameter, if you have multiple relations in your equivalent of an Author class, the second param should be Object and you will need to test within the method for the different relation types.

Many to many bidirectional mapping in JPA

I have the following JPA entities.
A profile have many users and a user have many profiles:
#Entity
public class Profile implements Serializable {
#Id
private Long id;
#ManyToMany(cascade = CascadeType.ALL)
private List<User> users;
...
}
#Entity
public class User implements Serializable {
#Id
private Long id;
#ManyToMany(mappedBy = "users")
private List<Profile> profiles;
...
}
On my application, when a user is merged, the profiles are updated on database.
However, when a profile is merged, the users are not updated.
Is possible to map my entities in order to make both sides merge their lists?
I am using JPA 2.1 and Hibernate.
Your Profile entity is ownind side or relationship. It's up to it, to manage relationship, so in order to update User you'll have to update Profile too or make manual SQL calls.
Java Specification for JPA 2.1 says that:
• For many-to-many bidirectional relationships either side may be the owning side
So if you'd like to make both entities editable from both side, remove mappedBy element and assigne necessacy cascade. But I'm not sure it works in Hibernate (didn't try actually), see this docs on mapping, there's no information about m:m without owning side: http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html/ch07.html#collections-bidirectional
Otherwise, you may need to iterate through collection in Profile entity and then change them. For example:
for( User user : profile.getUsers() ) {
user.setSomething(.....);
}
session.merge(profile);
Changing List to Set might be needed in order to avoid Hibernate's delete and reinsert, described here: http://assarconsulting.blogspot.fr/2009/08/why-hibernate-does-delete-all-then-re.html
Also, don't forget about equals() and hashCode() methods override

Hibernate Inheritance.JOINED generated FK name

I am currently trying to use inheritance within Hibernate and came across InheritanceType.JOINED. I like the idea of concentrating all data in one table and sharing IDs rather than having duplicate columns in all the sub type tables (#MappedSuperClass). But Hibernate automatically generates indexes on my sub class tables on the id column like FK_idx3wiwdm8yp2qkkddi726n8o everytime I initialize my Hibernate singleton. I noticed that by hitting the 64 key limit on my MySQL Table as the names are generated differently on every startup.
What is the proper way to handle this? Can this be fixed by annotations? What else could I try?
I know that there are countless similar Questions on SO but haven't been able to identify one solving my specific problem.
I am not going to disable hbm2ddl.auto during dev mode.
I am using MyISAM. There are no actual Foreign Keys. This is why Hibernate generates default indexes, I think. Anyway, the problem would be identical with InnoDB and real Foreign Keys as the names would still be quite random. Or maybe Hibernate would actually check for existence in this case. I don't really see, why it does not do this on MyISAM tables.
As I hit similar problems before, the solution could also be to specify a name for that single-column index. But how?
Super Class: FolderItem
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class FolderItem implements Comparable<FolderItem>
{
#Id
#GeneratedValue
protected int id;
protected String name;
#OneToOne
#ForeignKey(name = "fkParent")
protected Folder parent;
...
}
Sub Class: Folder
#Entity
public class Folder extends FolderItem
{
#OneToMany(mappedBy = "parent")
#OrderBy(value = "sortOrder")
private List<FolderItem> children;
...
}
What I tried
add #Index to FolderItem.id - this created an index on the FolderItem table as one would expect, but didn't affect the Folder table
copy protected int id; to Folder and tried to add an #Index to it, which resulted in an Exception similar to "duplicate definition of ID"
add #Table(appliesTo = "Folder", indexes = { #Index(name = "fkId", columnNames = { "id" }) }) to Folder class, which actually created my specified index as expected, but still created it's own FK_9xcia6idnwqdi9xx8ytea40h3 which is identical to mine, except for the name
Try #PrimaryKeyJoinColumn(name = "foler_item_id") annotation for Folder class.

Categories

Resources