How to debug "Found two representations of same collection"? - java

I have found several questions about this, but none with a complete explaintation of the problem, and how to debug it - the answers are all anecdotal.
The problem is that in a Play 1.2.4 JPA test, I'm getting this exception when I save() a model:
org.hibernate.HibernateException: Found two representations of same
collection: models.Position.projects
I would like to know:
Is there a documentation of this problem in general, unrelated to Play? The issue is in hibernate, yet a lot of the Google results on this are within Play apps.
What are some basic best practices to avoid this problem?
Is it caused by Play? Or something I'm doing wrong?
How to resolve in my specific case?
Here is a reproduction of the problem on github. I have four entities:
#Entity
public class Person extends Model {
public String name;
#OneToMany(cascade = CascadeType.ALL)
public List<Position> positions;
}
#Entity
public class Position extends Model {
public Position(){}
public Position(Company companies) {
this.companies = companies;
this.projects = new ArrayList<Project>();
}
#OneToOne
public Company companies;
#ManyToOne
public Person person;
#OneToMany
public List<Project> projects;
}
#Entity
public class Company extends Model {
public String name;
}
#Entity
public class Project extends Model {
public Project(){}
public Project(String field, String status){
this.theField = field;
this.status = status;
}
#ManyToOne
public Position position;
public String theField;
public String status;
}
And my persistence code:
Company facebook = new Company();
facebook.name = "Facebook";
facebook.save();
Company twitter = new Company();
twitter.name = "Twitter";
twitter.save();
Person joe = new Person();
joe.name = "Joe";
joe.save();
joe.positions = new ArrayList<Position>();
Position joeAtFacebook = new Position(facebook);
joeAtFacebook.projects.add(new Project("Stream", "Architect"));
joeAtFacebook.projects.add(new Project("Messages", "Lead QA"));
joe.positions.add(joeAtFacebook);
Position joeAtTwitter = new Position(twitter);
joeAtTwitter.projects.add(new Project("Steal stuff from Facebook", "CEO"));
joe.positions.add(joeAtTwitter);
joe.save();
BTW, I've tried adding the Play associations module as one person suggested, and it does't seem to help.
I see that indeed that tables that are created are duplicate in a sense:
I have both a person_position table and a position table, where both contain similar fields: person_position contains a Person_id and positions_id, while the position table contain id (meaning position id), person_id, and companies_id. So I understand some kind of unintended redundancy is created by my model definition, but I don't really understand how to solve it.
I thought this might be related to bi-directional mappings, but here is a branch where the model is uni-directional (I removed some back-references) - and the problem still occurs.

As far as I've been able to tell, the error is caused by any combination of:
Lacking / missing mappedBy parameter on #OneToMany annotations. This parameter should receive the name of the field in the target model that refers back to this model.
Old hibernate - Play 1.2.4 ships with hibernate 3.6.1 ... upgrading to 3.6.8 seems to resolve another such issue (just add the following to dependencies.yml, and play deps)
- org.hibernate -> hibernate-core 3.6.8.Final:
force: true
For me, the above steps solved the issue.
It is in fact a bug in hibernate, because it is thrown when persisting objects, while it actually implies a "design time" problem that should be detected when creating the schema.
Steps I used to debug:
Wrote a test that reproduced the problem
Added the associations module - I'm not sure if it resolved a part of the issue, or made it worse.
Debugged through hibernate code, and realized this probably indicates a hibernate problem, not a user / configuration error.
Noticed that hibernate has quite a few bugfix versions after 3.6.1, and decided to try my luck and upgrade.
Also important, cleaning the tmp folder can't hurt - Play caches compiled jars there, and after a major change like upgrading hibernate version, it might be worthwhile to clean it.

  Try
#OneToMany(mappedBy="position")
        public List<Project> projects;

First I think you miss a line one before the last:
joe.positions.add(joeAtTwitter);
Second:
I think that you should not do
joe.positions = new ArrayList<Position>();
instead change Person to:
#Entity
public class Person extends Model {
public String name;
#OneToMany(cascade = CascadeType.ALL)
public List<Position> positions = new ArrayList<Position>();
}
It will solve your problem, plus it's a best practice, using empty collection instead of null value (see Effective Java) at general and specifically for working with Hibernate managed objects. Read first paragraph here for explanation why you better initialize with empty collections.
Now what I think is happened is: when you call joe.save() you have made the object managed (by Hibernate) then you overwritten a property with a new collection, I can't understand why the error you got is about model.Position.projects, but I think that's the case.

Related

Persisting nested or related objects for testing created with builder pattern and JPA/Hibernate

Let's take the following classes which are a simplification of more complex classes and their relationships.
#Data
#Builder
public class UserAccount {
private String username;
private String password;
private Language contactLanguage;
public static UserAccount.UserAccountBuilder defaultUserAccount() {
return UserAccount.builder()
.username("default_username")
.password("default_password")
.contactLanguage(defaultLanguage().build());
}
}
#Data
#Builder
public class Language {
private String name;
private String iso2;
private String iso3;
public static Language.LanguageBuilder defaultLanguage() {
return Language.builder()
.name("default_language_name")
.iso2("default_iso2")
.iso3("default_iso3");
}
}
Using Lombok's #Builder annotation, I can easily construct an object like this, especially for testing:
UserAccount.builder()
.username("foo")
.password("bar")
.contactLanguage(Language.builder()
.name("English")
.iso2("EN")
.iso3("ENG")
.build())
.build();
// Or even like this...
defaultUserAccount().build();
This works fine for unit tests or any tests where such generated objects are only required to exist in memory.
However I'd also like to use this approach for integration tests with an underlying database (using Spring Boot 2.4 + JPA + Hibernate). And this is where some issues come up I couldn't solve so far. Let's have a look:
Each UserAccount needs to have a contactLanguage, but Language lives on its own. Other entities might use it as well. When constructing a user account with defaultUserAccount().build(), then persisting this entity fails because the Language object has not been persisted yet. There is no persist cascade on contactLanguage because I don't want "any" Language being created upon creating a UserAccount.
My only idea would be to use defaultLanguage().build() and persist this before defaultUserAccount().build(). But I feel that this will become complex and flaky as soon as there are more levels of nested builders or relationship to other entites.
Another thing is: Even if I managed to persist the defaultLanguge, I would run into a collision as soon as another test calls defaultUserAccount().build() because then the langauge already exists and cannot be inserted again.
Are there any patterns or approaches for persisting such test data objects?
Update #1
After more searching, I found this question on SO which looks almost identical.

Multiple levels of cascading persists in Ebean

I have a model class which defines a list of children that are models of the same class. Persisting a new object with some initial children works fine, but when I have two or more levels of children Ebean does not seem to be able to handle it well. This seemed unexpected so I'm worried I made a mistake. At the same time I couldn't find any examples or mentions about multiple level persist cascades so my questions are:
Is there an error in my code, Is this even a supported feature or did I find a bug?
My model class:
#Entity
public class TestEntity extends Model {
#Id
private int id;
private String text;
#ManyToOne
private TestEntity parentEntity;
#OneToMany(cascade = CascadeType.ALL)
private List<TestEntity> childEntities;
...
}
My program:
TestEntity grandparent = new TestEntity();
grandparent.setText("grandparent");
TestEntity parent = new TestEntity();
parent.setText("parent");
TestEntity child = new TestEntity();
child.setText("child");
grandparent.setChildEntities(Collections.singletonList(parent));
parent.setChildEntities(Collections.singletonList(child));
grandparent.save();
I added logging for the sql statements and it is evident that the third insert didn't get the correct value for parent_entity_id. That row fails due to 0 not being a valid foreign key and the batch is reverted.
insert into test_entity (text, parent_entity_id) values ('grandparent',null);
insert into test_entity (text, parent_entity_id) values ('parent',1);
insert into test_entity (text, parent_entity_id) values ('child',0);
I'm using Play framework 2.7.3 with the ebean plugin version 5.0.2 and Ebean version 11.39
This is indeed a supported feature and the code snippet above is expected to persist all three entities.
There was a unit test added to verify that this is working correctly in the latest version of ebean.
In ebean 11.39 which is currently the latest supported by play framework the test fails. An easy workaround when using that version is to use Long instead of primitive int as ID for the models.
While not an answer to this specific question, it is good to be aware that these same symptoms also appear if the collections are set without using setters enhanced by ebean. I had some trouble using public fields and play enhancer .

Hibernate good practice, lazy/eager loading and saving/deleting children (help me Hibernate sensei)

So, I have found myself in quite a pickle regarding Hibernate. When I started developing my web application, I used "eager" loading everywhere so I could easily access children, parents etc.
After a while, I ran into my first problem - re-saving of deleted objects. Multiple stackoverflow threads suggested that I should remove the object from all the collections that it's in. Reading those suggestions made my "spidey sense" tickle as my relations weren't really simple and I had to iterate multiple objects which made my code look kind of ugly and made me wonder if this was the best approach.
For example, when deleting Employee (that belongs to User in a sense that User can act as multiple different Employees). Let's say Employee can leave Feedback to Party, so Employee can have multiple Feedback and Party can have multiple Feedback. Additionally, both Employee and Party belong to some kind of a parent object, let's say an Organization. Basically, we have:
class User {
// Has many
Set<Employee> employees;
// Has many
Set<Organization> organizations;
// Has many through employees
Set<Organization> associatedOrganizations;
}
class Employee {
// Belongs to
User user;
// Belongs to
Organization organization;
// Has many
Set<Feedback> feedbacks;
}
class Organization {
// Belongs to
User user;
// Has many
Set<Employee> employees;
// Has many
Set<Party> parties;
}
class Party {
// Belongs to
Organization organization;
// Has many
Set<Feedback> feedbacks;
}
class Feedback {
// Belongs to
Party party;
// Belongs to
Employee employee;
}
Here's what I ended up with when deleting an employee:
// First remove feedbacks related to employee
Iterator<Feedback> iter = employee.getFeedbacks().iterator();
while (iter.hasNext()) {
Feedback feedback = iter.next();
iter.remove();
feedback.getParty().getFeedbacks().remove(feedback);
session.delete(feedback);
}
session.update(employee);
// Now remove employee from organization
Organization organization = employee.getOrganization();
organization.getEmployees().remove(employee);
session.update(organization);
This is, by my definition, ugly. I would've assumed that by using
#Cascade({CascadeType.ALL})
then Hibernate would magically remove Employee from all associations by simply doing:
session.delete(employee);
instead I get:
Error during managed flush [deleted object would be re-saved by cascade (remove deleted object from associations)
So, in order to try to get my code a bit cleaner and maybe even optimized (sometimes lazy fetch is enough, sometimes I need eager), I tried lazy fetching almost everything and hoping that if I do, for example:
employee.getFeedbacks()
then the feedbacks are nicely fetched without any problem but nope, everything breaks:
failed to lazily initialize a collection of role: ..., could not initialize proxy - no Session
The next thing I thought about was removing the possibility for objects to insert/delete their related children objects but that would probably be a bad idea performance-wise - inserting every object separately with
child.parent=parent
instead of in a bulk with
parent.children().add(children).
Finally, I saw that multiple people recommended creating my own custom queries and stuff but at that point, why should I even bother with Hibernate? Is there really no good way to handle my problem relatively clean or am I missing something or am I an idiot?
If I understood the question correctly it's all about cascading through simple 1:N relations. In that case Hibernate can do the job rather well:
#Entity
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(cascade = CascadeType.ALL,
mappedBy = "post", orphanRemoval = true)
private List<Comment> comments = new ArrayList<>();
}
#Entity
public class Comment {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
private Post post;
}
Code:
Post post = newPost();
doInTransaction(session -> {
session.delete(post);
});
Generates:
delete from Comment where id = 1
delete from Comment where id = 2
delete from Post where id = 1
But if you have some other (synthetic) collections, Hibernate has no chance to know which ones, so you have to handle them yourself.
As for Hibernate and custom queries, Hibernate provides HQL which is more compact then traditional SQL, but still is less transparent then annotations.

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.

Saving "master" element in many to one relationship with ebean

I am having a little issue with Ebean (in the context of Play Framework, Java).
I have elements sharing a one-to-many relationship (BankAccount <- BankingOperation).
I have defined the BankAccount class with, among others, the following fields:
#JsonIgnore
#OneToMany(cascade = CascadeType.ALL)
public List<BankingOperation> operations = new ArrayList<BankingOperation>();
For the Banking operation, the corresponding field:
#ManyToOne
#JsonIgnore
public BankAccount bankAccount;
My issue is that when I try to update the bank account, it deletes the related operations. Here's the code I am using:
public static Result saveAccount(Long id)
{
Form<BankAccount> form = Form.form(BankAccount.class).bindFromRequest();
if (form.hasErrors() || form.get().id != id) {
return badRequest();
}
form.get().update(id);
return ok();
}
I have the feeling that operations are deleted because they aren't loaded when I do the form().get(), and thus, when synchronizing with the DB, Ebean does what seems to be the best solution to it.
Would anyone have any clue on this issue? Is there another solution that I haven't discovered yet?
Thanks in advance for your help!
For now, I have found a (ugly) solution which is adding the following line before doing the update:
Ebean.refreshMany(form.get(), "operations");
Another solution could be not to build the form the model class but on an another class, forcing me to map each field one by one.

Categories

Resources