#OneToMany stackoverflow in Spring Data Rest - spring

I have a Questions entity which has list of options as follows:
#OneToMany(mappedBy = "question")
List<Option> options;
And in Options entity I have specified the relation as :
#ManyToOne
#JoinColumn(name="question_id")
Question question;
When I hit /api/questions , it works fine but when I hit /api/questions/1 , it gives java.lang.StackOverflowError: null
What am I doing wrong?

It's because Option refers to Question and Question to Option. You should add
#JsonIgnore to one of your class to prevent infinite linking to each other. The same thing can be with toString() method. If you use Lombok or generate default toString method, it could cause statckoverflow also. Because classs linked to class. To prevent this try to exclude link on class in one of toString method.
In Lombok in #ToString annotation add exclude statement and exclude either Option or Question. Maybe you call toString method that cases loop. #ToString(exclude = {"option"})

Related

JPA - Auto-generated field null after save

I have an Account entity and I'm trying to persist it using save function. My code:
#Override
public Account createAccount(String pin) {
Account account = new Account();
account.setBalance(0L);
account.setPin(pin);
return accountRepository.save(account);
}
Now my entity class has an autogenerated field called accountNumber. My entity class:
#Entity
#Table(name = "accounts")
#Data
public class Account {
#Column(name = "account_number", length = 32, insertable = false)
private String accountNumber;
private Long balance;
}
Now after calling save, the entity returned has accountNumber as null but i can see in the intellij database view that it is actually not null. All the other auto-generated fields like id etc are there in the returned entity just the accountNumber is null. Default value for accountNumber is set in the sql file :
ALTER TABLE accounts
ALTER COLUMN account_number SET DEFAULT DefaultValueSerializer(TRUE, TRUE, 12);
Here, DefaultValueSerializer is the function which is generating the account number.
I've tried other solutions available here like using saveAndFlush() etc, nothing worked in my case. What can be an issue?
As mentioned in comment Hibernate is not aware about what happens in database engine level so it does not see the value generated.
It would be wise to move generation of account number to JPA level instead of using db defaults.
I suggest you to study annotations #GeneratedValue and related stuff like #SequenceGenerator. That way the control of generating account number is in JPA level and there is no need for stuff like refreshing entity after save.
One starting point: Java - JPA - Generators - #SequenceGenerator
For non-id fields it is possible to generate value in method annotated with #PrePersist as other answer suggests but you could do the initialization already in the Accounts constructor .
Also see this answer for options.
You can create an annotated #PrePersist method inside the entity in which you set its fields to their default value.
That way jpa is going to be aware of the default.
There are other such annotation avaiable for different entity lifecycle event https://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/listeners.html
P.s. if you decide to go this way remember to remove the insertable = false
Use
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
for your IDs. And also leave your saving to saveAndFlush so you can immediately see the changes, If any. I'd also recommend separating IDs and account numbers. They should not be the same. Try debugging your program and see where the value stops passing around.

Default qualifier/filter for Spring Data Rest with JPA

I'm trying to create a default filter in Spring Data Rest with JPA. I have a Reward class, and the Reward can have an exclusive relationship.
Here is a shortened version of the Reward class
#Data
#Entity
public class Reward implements Identifiable<UUID> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private UUID id;
#ManyToOne(fetch = FetchType.LAZY)
private Owner exclusiveOwner;
}
The Owner class has a #ManyToOne relationship to Reward.
I'd like to have it setup so that when /api/rewards is called it only returns Rewards who's exclusive relationship is null (i.e., generally available rewards), and when /api/rewards?exclusiveOnwer=<UUID> it only returns the rewards exclusive to that Owner.
I've gotten the second part to work with QueryDSL by having the RewardRepository extend QueryDslPredicateExecutor<Reward>, but I can't figure out how to get the default qualifier to work. Is there any way to do that?
Update
I have tried a workaround by creating a handler method with QueryDSL Predicates based on the Spring Blog. It is in my RewardsController class, which is a #RepositoryRestController. The method signature is
#GetMapping(path = "/rewards")
public ResponseEntity<?> findAll(#QuerydslPredicate(root = Reward.class) Predicate predicate, final FindParams findParams, final Pageable p, final PersistentEntityResourceAssembler entityAssembler) {
but that gives me the error
org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.querydsl.core.types.Predicate]: Specified class is an interface whenever I hit the endpoint. (FindParams is a simple POJO for capturing parameters. The same error occurs if I use a Map.)
There is a bug with a workaround, but that requires changing the Controller from a #RepositoryRestController to a #RestController, which I don't want to do as 1) my API has a base path of /api, and 2) I already have a method in there that I don't want to break. If I change the Controller to #BasePathAwareController the problem persists.

Spring Hibernate - Does it support nested objects?

I recently asked this question : Spring Mongodb - Insert Nested document?
And found out that Spring-Data-MongoDB does not support such behavior - so now I need a working alternative.
Now - to avoid having you look at the code on another page, I am going to paste it here from the other question... Here are my two POJOs :
#Document
public class PersonWrapper {
#Id
private ObjectId _Id;
#DBRef
private Person leader;
#DBRef
List<Person> delegates;
// Getters and setters removed for brevity.
}
public class Person
{
#Id
private ObjectId _Id;
private String name;
// Getters and setters removed for brevity.
}
Now, what I want to be able to do here - is send up a JSON object in my POST request as follows :
{
"personWrapper":
{
"_Id":"<ID HERE (MIGHT WANT SQL TO GENERATE THIS DURING CREATE>",
"leader":{
"_Id":"<ID HERE (MIGHT WANT SQL TO GENERATE THIS DURING CREATE>",
"name":"Leader McLeaderFace"
},
delegates:[{...},{...},{...}]
}
}
At this point - I would like the SQL side of this to create the individual records needed - and then insert the PersonWrapper record, with all of the right foreign keys to the desired records, in the most efficient way possible.
To be honest, if one of you thinks I am wrong about the Spring-Data-MongoDB approach to this, I would still be interested in the answer - because it would save me the hassle of migrating my database setup. So I will still tag the spring-data-mongodb community here, too.
If I understand well you want to cascade the save of your objects ?
ex : you save a PersonWrapper with some Person in the delegates property and spring data will save PersonneWrapper in a collection and save also the list of Person in another Collection.
It is possible to do that with Spring DATA JPA if you annotate your POJO with the JPA annotation #OneToMany and setup cascade property of this annotation. See this post
However the cascade feature is not available for Spring DATA mongoDB. See documentation .First you have to save the list of Person and then you save PersonWrapper.

Spring JPA repository how to write a query

I have a User class, that is identified by id, and Skills class, that has its own id field, and also references User.
public class User {
#Id
#GeneratedValue
private int id;
#JsonIgnore
#OneToOne(mappedBy = "user")
private SoftSkills softSkills;
}
the other one has
#Entity
public class SoftSkills {
#Id
#GeneratedValue
private int id;
#OneToOne
#JoinColumn
private User user;
}
Is there a simple way to write a query, implementing the JPARepository, that would search the SoftSkills class by using user.id field as a parameter and return a SoftSkills object as a result?
You can, from the documentation:
Property expressions can refer only to a direct property of the managed entity, as shown in the preceding example. At query creation time you already make sure that the parsed property is a property of the managed domain class. However, you can also define constraints by traversing nested properties. Assume a Person has an Address with a ZipCode. In that case a method name of
List<Person> findByAddressZipCode(ZipCode zipCode);
creates the property traversal x.address.zipCode. The resolution algorithm starts with interpreting the entire part (AddressZipCode) as the property and checks the domain class for a property with that name (uncapitalized). If the algorithm succeeds it uses that property. If not, the algorithm splits up the source at the camel case parts from the right side into a head and a tail and tries to find the corresponding property, in our example, AddressZip and Code. If the algorithm finds a property with that head it takes the tail and continue building the tree down from there, splitting the tail up in the way just described. If the first split does not match, the algorithm move the split point to the left (Address, ZipCode) and continues.
So this will do the trick:
SoftSkills findByUserId(int id);
Reference; Spring Data JPA Documentation

org.hibernate.LazyInitializationException during JSF validation [duplicate]

Despite of FetchType.EAGER and JOIN FETCH, I get a LazyInitalizationException while adding some objects to a #ManyToMany collection via a JSF UISelectMany component such as in my case the <p:selectManyMenu>.
The #Entity IdentUser, with FetchType.EAGER:
#Column(name = "EMPLOYERS")
#ManyToMany(fetch = FetchType.EAGER, cascade= CascadeType.ALL)
#JoinTable(name = "USER_COMPANY", joinColumns = { #JoinColumn(name = "USER_ID") }, inverseJoinColumns = { #JoinColumn(name = "COMPANY_ID") })
private Set<Company> employers = new HashSet<Company>();
The #Entity Company, with FetchType.EAGER:
#ManyToMany(mappedBy="employers", fetch=FetchType.EAGER)
private List<IdentUser> employee;
The JPQL, with JOIN FETCH:
public List<IdentUser> getAllUsers() {
return this.em.createQuery("from IdentUser u LEFT JOIN FETCH u.employers WHERE u.enabled = 1 AND u.accountNonLocked=0 ").getResultList();
}
The JSF UISelectMany component causing the exception while submitting:
<p:selectManyMenu value="#{bean.user.employers}" converter="#{entityConverter}">
<f:selectItems value="#{bean.companies}" var="company" itemValue="#{company}" itemLabel="#{company.name}"/>
</p:selectManyMenu>
The relevant part of the stack trace:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection, could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:566)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:186)
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:545)
at org.hibernate.collection.internal.PersistentSet.add(PersistentSet.java:206)
at com.sun.faces.renderkit.html_basic.MenuRenderer.convertSelectManyValuesForModel(MenuRenderer.java:382)
at com.sun.faces.renderkit.html_basic.MenuRenderer.convertSelectManyValue(MenuRenderer.java:129)
at com.sun.faces.renderkit.html_basic.MenuRenderer.getConvertedValue(MenuRenderer.java:315)
at org.primefaces.component.selectmanymenu.SelectManyMenuRenderer.getConvertedValue(SelectManyMenuRenderer.java:37)
...
How is this caused and how can I solve it?
While submitting, the JSF UISelectMany components need to create a brand new instance of the collection with the submitted and converted values prefilled. It won't clear out and reuse the existing collection in the model as that may either get reflected in other references to the same collection, or may fail with an UnsupportedOperationException because the collection is unmodifiable, such as the ones obtained by Arrays#asList() or Collections#unmodifiableList().
The MenuRenderer, the renderer behind UISelectMany (and UISelectOne) components who's responsible for this all, will by default create a brand new instance of the collection based on collection's getClass().newInstance(). This would in turn fail with LazyInitializationException if the getClass() returns an implementation of Hibernate's PersistentCollection which is internally used by Hibernate to fill the collection property of an entity. The add() method namely needs to initialize the underlying proxy via the current session, but there's none because the job isn't performed within a transactional service method.
To override this default behavior of MenuRenderer, you need to explicitly specify the FQN of the desired collection type via the collectionType attribute of the UISelectMany component. For a List property, you'd like to specify java.util.ArrayList and for a Set property, you'd like to specify java.util.LinkedHashSet (or java.util.HashSet if ordering isn't important):
<p:selectManyMenu ... collectionType="java.util.LinkedHashSet">
The same applies to all other UISelectMany components as well which are directly tied to a Hibernate-managed JPA entity. E.g:
<p:selectManyCheckbox ... collectionType="java.util.LinkedHashSet">
<h:selectManyCheckbox ... collectionType="java.util.LinkedHashSet">
<h:selectManyListbox ... collectionType="java.util.LinkedHashSet">
<h:selectManyMenu ... collectionType="java.util.LinkedHashSet">
See also the VDL documentation of among others <h:selectManyMenu>. This is unfortunately not specified in VDL documentation of <p:selectManyMenu>, but as they use the same renderer for converting, it must work. If the IDE is jerking about an unknown collectionType attribute and annoyingly underlines it even though it works when you ignore'n'run it, then use <f:attribute> instead.
<p:selectManyMenu ... >
<f:attribute name="collectionType" value="java.util.LinkedHashSet" />
...
</p:selectManyMenu>
Solution: Replace the editUserBehavior.currentUser.employers with collection that is not managed by Hibernate.
Why? When the Entity becomes managed, the Hibernate replaces your HashSet with its own implementation of Set (be it PersistentSet). By analysing the implementation of JSF MenuRenderer, it turns out that at one point it creates new Set reflectively. See the comment in MenuRenderer.convertSelectManyValuesForModel()
// try to reflect a no-argument constructor and invoke if available
During construction of PersistentSet initialize() is invoked and - as this class is only meant to be invoked from Hibernate - LazyInitializationException is thrown.
Note: This is my suspicion only. I don't know your versions of JSF and Hibernate but this is more likely the case.

Resources