These are two entities, each with more fields but these two are causing StackOverflow. When I'm using only #JsonMannagedReference and #JsonBackReference and not using mappedBy, infinite recursion doesn't exist. Still, in that case, I'm unable to create bidirectional relation, when I add a mappedBy parameter it causes a StackOverflow error.
#Entity
#Getter
#Setter
#ToString
#NoArgsConstructor
#AllArgsConstructor
public class Business {
#OneToMany(targetEntity = ProductCategory.class
, cascade = CascadeType.ALL
, fetch = FetchType.LAZY
, mappedBy = "business"
)
#JsonManagedReference
private List<ProductCategory> productCategories;
}
#Entity
#Getter
#Setter
#ToString
#NoArgsConstructor
#AllArgsConstructor
public class ProductCategory {
#ManyToOne(targetEntity = Business.class
, cascade = CascadeType.ALL
, fetch = FetchType.LAZY)
#JoinColumn(name = "business_id")
#JsonBackReference
private Business business;
}
We have a very similar setup and it works. I see two differences.
First, we do not set targetEntity, JPA should be able to figure that out based on field types.
Second, we excluded "business" fields from toString and equalsAndHashCode generated for ProductCategory class.
Try adding annotations
#ToString(exclude = {"business"})
#EqualsAndHashCode(exclude = {"business"})
to your ProductCategory class.
That should exclude cyclic calls in toString and equals/hashcode methods and take away your unwanted infinite recursion.
Related
I have the following class:
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
public class Yard extends ModelObject {
// Relations
#ManyToOne(optional=false)
#JoinColumn(name = "house_id", foreignKey=#ForeignKey(name="fk1_yard"))
#Getter #Setter
#JsonView({Views.AdminPortal.class})
private House house = null;
}
And I get the following error
Could not write JSON: Unable to find com.db.model.main.House with id 7
My understanding was that targetAuditMode = RelationTargetAuditMode.NOT_AUDITED would prevent this error by only auditing the id number. What am I doing wrong here?
Your usage is incorrect. I believe what you're looking for is:
#Audited
#Entity
public class Yard extends ModelObject {
#ManyToOne(optional=false)
#JoinColumn(name = "house_id", foreignKey=#ForeignKey(name="fk1_yard"))
#Getter
#Setter
#JsonView({Views.AdminPortal.class})
#Audited(targetAuditMode = RElationTargetAuditMode.NOT_AUDITED)
private House house = null;
}
You'll notice how the #Audited annotation with targetAuditMode is being used on the association in the entity mapping rather than on the class-mapping. That attribute has no impact at the class-level.
I'm working with JPA, Spring boot.
Using #OneToMany annotation, when I fetch orders containing cart items.
My domain codes are below.
Order:
#Data
#Entity
#Table(name="\"order\"")
#ToString
public class Order {
#Id
#GeneratedValue
#Getter #Setter
private Long id;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name="order_id")
#Getter #Setter
private List<Cart> carts;
public void addCart(Cart cart) {
if (this.carts == null) {
carts = new ArrayList<Cart>();
}
carts.add(cart);
}
}
Cart:
#Data
#Entity
#Table(name="cart")
#ToString
public class Cart {
#Id
#GeneratedValue
#Getter #Setter
#Column(name="id")
private Long id;
#Column(name = "order_id")
#Getter #Setter
private Long orderId;
}
This works very well when I fetch only one order, but doesn't work when I fetch more than two orders. I mean when I fetch only one order, carts field's size is 1 or more, but when I fetch two or more orders, the carts field's size is 0.
How can I solve this problem? Thank you in advance!
It's nearly impossible to find out what the fetching problem is without seeing the code which is loading / querying your entities. So could you please add it to your question?
Meanwhile there are at least some things you could improve to have cleaner entities and faster code, maybe this also can help a little to hunt down the problem.
At first, you are using redundant configurations, respective annotations you are recreating the default values with.
I assume that is lombok what you are using, right? You could remove the #Getter and #Setter annotations on your fields in both entities and add it once on class level to avoid the declaration on every single field, but since you are using #Data you don't need it at all. Same with #toString, #Data is a convenience annotations for all of it (and a little more).
The JavaDoc of #Data says:
Equivalent to {#code #Getter #Setter #RequiredArgsConstructor #ToString #EqualsAndHashCode}.
Then, although the #Table(name="\"order\"") on the order entity is needed because order is a reserved word in some DBMS, the #Table(name="cart") on the cart entity is the default.
Up next, I would not recommend lazy initialization of collections, because in general there is no benefit to do that compared to the penalty it causes while checking for null before every access. Just initialize it within declaration and you will never have to care about handling null again. Beside of that you should think about using a Set instead a List for the carts (so you will have no duplicates).
Also think about the FetchType because EAGER is only useful if you work with detached entities and want to initialize the relation in every case. LAZY is the default for #OneToMany and should be preferred.
A thing you already improved is the #JoinColumn, that will prevent Hibernate (I brazenly assume you are using Hibernate) creating a join table. But even more important is thinking about turning the #OneToMany into a #ManyToOne on the other side or making it bidirectional. That would gain some performance on reading (it also prevents the join table so less joins are needed, faster indexing is possible) and writing time (relation managed by parent side).
So what do you think about this:
#Data
#Entity
#Table(name="\"order\"")
public class Order {
#Id
#GeneratedValue
private Long id;
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Cart> carts = new HashSet<>();
}
and this:
#Data
#Entity
public class Cart {
#Id
#GeneratedValue
private Long id;
#ManyToOne
private Order order;
}
Order class:
public class Order {
#Id
#GeneratedValue
#Getter #Setter
private Long id;
#OneToMany(mappedBy="orderId" fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#Getter #Setter
private Set<Cart> carts;
}
Cart class:
public class Cart {
#Id
#GeneratedValue
#Getter #Setter
#Column(name="id")
private Long id;
#ManyToOne
#Getter #Setter
private Order orderId;
}
For example: JPA OneToMany and ManyToOne Relationships
I just wrote it to change.
I hope that it will work
I'm using springBootTest to test a service I've created. In the before each function i create a parententity using the repository directly.
parentEntity = parentEntityRepository.saveAndFlush(ObjectMother.getParentEntityBuilder().string3("s3").build());
Within my test i create a childentity
childEntity = childEntityRepository.saveAndFlush(ObjectMother.getChildEntityBuilder().parentEntity(parentEntity).build());
The children relation is defined as follows
#Getter #Setter
#OneToMany(orphanRemoval = true)
#JoinColumn(name="PARENTENTITY_ID", updatable = false)
private Set<ChildEntity> children;
This is called within the test
parentEntity = parentEntityService.read(requestContext, parentEntity.getId());
parentEntity.getChildren().forEach(child -> Assert.assertNotNull(child.getText()));
It causes the following error
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.sap.icn.pi.test.data.mcp.ParentEntity.children, could not initialize proxy - no Session
If i add #Transactional to my test method i receive the following
java.lang.NullPointerException // for parentEntity.getChildren()
** Edit: Code Snippets **
#Test
public void get_children_through_parent() {
parentEntity = parentEntityService.read(requestContext, 1);
parentEntity.getChildren().forEach(child -> Assert.assertNotNull(child));
parentEntity.getChildren().forEach(child -> Assert.assertNull(child.getTooltip()));
}
ParentEntity Class
#Entity
#Table(name = "T_PARENTENTITY")
#SequenceGenerator(initialValue = 1, name = "idgen", sequenceName = "SEQ_PARENTENTITY")
#Builder #NoArgsConstructor #AllArgsConstructor
#Localized
public class ParentEntity extends BaseEntity{
... //props
#Getter #Setter
#OneToMany(orphanRemoval = true)
#JoinColumn(name="PARENTENTITY_ID", updatable = false)
private Set<ChildEntity> children;
}
This is a common JPA/Hibernate problem. Object was read is different Hibernate session or hibernate session doesn't exist anymore, so lazy loading can't do SQL query to retrieve lazy dependency. Reason of this situation can vary and you didn't provide enough context.
To fix that, you have various options:
Make sure that object is read and lazy dependency is loaded in same Hibernate session. Spring Automatically creates hibernate session per controller request, so it would be good to make sure that you object is not retrieved in servlet filter and lazy dependency in your controller/service. Or common problem is also passing that object into separate thread.
Change dependency to be EAGER:
#Getter #Setter
#OneToMany(orphanRemoval = true, fetch = FetchType.EAGER)
#JoinColumn(name="PARENTENTITY_ID", updatable = false)
private Set<ChildEntity> children;
I have a two objects with simple #OneToMany relationship which looks as follows:
parent:
#Entity
public class ParentAccount {
#Id
#GeneratedValue
private long id;
private String name;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "parentAccount")
private Set<LinkedAccount> linkedAccounts;
}
child:
#Entity
public class LinkedAccount {
#Id
#GeneratedValue
private long id;
#ManyToOne(optional = false)
private ParentAccount parentAccount;
private String name;
// empty constructor for JPA
public LinkedAccount() {
}
}
I ma using Spring CrudRepository to operate with these entities. However, when calling ParentAccount parent = parentAccountRepository.findOne(id);, some kind of infinite loop starts happening and hibernate spams this all over the console:
Hibernate: select linkedacco0_.parent_account_id as parent_a6_1_0_, linkedacco0_.id as id1_0_0_, linkedacco0_.id as id1_0_1_, linkedacco0_.aws_id as aws_id2_0_1_, linkedacco0_.key_id as key_id3_0_1_, linkedacco0_.name as name4_0_1_, linkedacco0_.parent_account_id as parent_a6_0_1_, linkedacco0_.secret_key as secret_k5_0_1_ from linked_account linkedacco0_ where linkedacco0_.parent_account_id=?
I tried changed the fetch type to LAZY but then I get this error:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.berrycloud.scheduler.model.ParentAccount.linkedAccounts, could not initialize proxy - no Session
(It seems that it is trying to do the lazy load outside of the transactional context).
This is my CRUD repository:
#Repository
public interface ParentAccountRepository extends CrudRepository<ParentAccount, Long> {
}
Could someone tell me how to resolve this issue? I would prefer the solution with EAGER fetch. Thank you for any tips
EDIT: here is the schema I am using
CREATE TABLE parent_account (
id BIGINT auto_increment,
name VARCHAR(80) null,
PRIMARY KEY (`id`)
);
CREATE TABLE linked_account (
id BIGINT auto_increment,
parent_account_id BIGINT,
name VARCHAR(80) null,
FOREIGN KEY (`parent_account_id`) REFERENCES `parent_account` (`id`),
PRIMARY KEY (`id`)
);
As the first answer suggests:
Do not use Lombok's #Data annotation on #Entity classes.
Reason: #Data generates hashcode(), equals() and toString() methods that use the generated getters. Using the getter means of course fetching new data even if the property was marked with FetchType=LAZY.
Somewhere along the way hibernate tries to log the data with toString() and it crashes.
Problem solved. I was using a custom #toString method in the LinkedAccount which was referencing the ParentAccount. I had no idea that this could cause any problem and therefor I did not include the toString in my question.
Apparently, this was causing an infinite loop of lazy loading and removing this reference fixed the problem.
As user1819111 told, #Data from Lombok is not compatible with #Entity and FetchType=LAZY. I had used Lombok.Data (#Data) and I was getting this error.
As I don't want do create all get/set, I just put the Lombok #Setter and #Getter in your class and all will work fine.
#Setter
#Getter
#Entity
#Table(name = "file")
#SequenceGenerator(name = "File_Sequence", allocationSize=1, sequenceName = "file_id_seq")
public class MyClass{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "File_Sequence")
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "file", cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
private Set<Base2FileDetail> details = new HashSet<>();
}
Something like this does not work?
#Entity
public class Account {
#Id
#GeneratedValue
private long id;
private String name;
#ManyToOne(cascade={CascadeType.ALL})
#JoinColumn(name="manager_id")
private Account manager;
#OneToMany((fetch = FetchType.EAGER, mappedBy="manager")
private Set<Account> linkedAccounts = new HashSet<Account>();
}
I recently had this issue due to a poorly defined Jackson2HttpMessageConverter.
I had done something like the following.
#Bean
RestTemplate restTemplate(#Qualifier("halJacksonHttpMessageConverter")
TypeConstrainedMappingJackson2HttpMessageConverter halConverter) {
final RestTemplate template = new RestTemplateBuilder().build();
halConverter.setSupportedMediaTypes(List.of(/* some media types */));
final List<HttpMessageConverter<?>> converters = template.getMessageConverters();
converters.add(halConverter);
template.setMessageConverters(converters);
return template;
}
This caused a problem because the media types did not include all the defaults. Changing it to the following fixed the issue for me.
halConverter.setSupportedMediaTypes(
new ImmutableList.Builder<MediaType>()
.addAll(halConverter.getSupportedMediaTypes())
.add(/* my custom media type */)
.build()
);
This simple way worked for me. Just use JsonIgnoreProperties .
#JsonIgnoreProperties(value = {"linkedAccounts"})
#ManyToOne(cascade = { CascadeType.PERSIST})
#JoinColumn(name = "abc", referencedColumnName = "abc")
private ParentAccount parentAccount;
This way worked for me without removing #ToSring annotation:
#Entity
#Getter
#Setter
#ToString
#RequiredArgsConstructor
#AllArgsConstructor
#Table(name = "parent_accounts")
public class ParentAccount {
#JsonIgnoreProperties({"parentAccount"})
#OneToMany(mappedBy = "parentAccount",
cascade = CascadeType.ALL,
orphanRemoval = true)
private List<LinkedAccount> linkedAcounts;
// ...
}
#Entity
#Getter
#Setter
#ToString
#RequiredArgsConstructor
#AllArgsConstructor
#Table(name = "linked_accounts")
public class LinkedAccount {
#JsonIgnoreProperties("linkedAcounts")
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "parentAccount_id")
private ParentAccount parentAccount;
// ...
}
PS: In #JsonIgnoreProperties You can also ignore more than one field for preventing infinite loop
I am trying to map a bi-directional one-to-many relationship. I am having some trouble as the "many" side references an abstract superclass. While searching the internet for possible causes I discovered that this is a known problem but I wasn't able to find a solution for my case.
I have checked the workarounds on this blog and the "Single table, without mappedBy" looks like a solution but I really need the bi-directional association.
These are the classes I am trying to map:
Owning Side
#Entity(name = "CC_Incl_Site")
public class IncludedSite {
#OneToMany(fetch=FetchType.LAZY, mappedBy = "includedSite")
private Set<CtaContractBase> ctas = new HashSet<CtaContractBase>();
#OneToMany(fetch=FetchType.LAZY, mappedBy = "includedSite")
private Set<WoContractBase> wos = new HashSet<WoContractBase>();
}
Other Side:
#Entity
public abstract class SCContract extends Contract {
#ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
#JoinColumn(name = "incl_site_id")
private IncludedSite includedSite;
}
Contract (the superclass of SCContract):
#Entity(name = "CC_CONTRACT")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "contractType", discriminatorType = DiscriminatorType.STRING)
#ForceDiscriminator
public abstract class Contract {
...
}
When trying to run the application I get this exception:
mappedBy reference an unknown target entity property:
CtaContractBase.includedSite in IncludedSite.ctas
Another solution appears to be replacing the #Entity annotation in SCContract with #MappedSuperClass but this results in another exception (Use of #OneToMany or #ManyToMany targeting an unmapped class: StudyContract.contracts[SCContract]) because in another class (StudyContract) I have
#OneToMany(fetch = FetchType.LAZY, mappedBy = "studyContract", targetEntity = SCContract.class)
#BatchSize(size = 10)
private Set<SCContract> contracts;
and as the blog explains having a collection of the superclass is not possible anymore using this approach.
Are there any other workarounds or am I missing something?
The association in IncludedSite is defined as
#OneToMany(fetch=FetchType.LAZY, mappedBy = "includedSite")
private Set<CtaContractBase> ctas = new HashSet<CtaContractBase>();
So Hibernate looks for an attribute of type IncludedSite named includedSite in the class CtaContractBase. There is no such field. The field only exists in the subclass SCContract. This means that only SCContract instances can be the target of this association, and the association should thus be defined as
#OneToMany(fetch=FetchType.LAZY, mappedBy = "includedSite")
private Set<SCContract> ctas = new HashSet<SCContract>();