This question already has answers here:
Infinite Recursion with Jackson JSON and Hibernate JPA issue
(29 answers)
Closed 3 years ago.
straight to the point:
I have a group that contains projects. I want that association to be handle with a foreign key, which is why it has a mappedby tag. My issue is that if I query for groups I get into an inifinite loop where the group lists the projects which contain the group which list the project which again contains the group.....and so on. My entities (minimal version):
#Entity
public class DBGroup {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
#OneToMany(mappedBy = "group",cascade=CascadeType.ALL,fetch = FetchType.EAGER)
private List<Project> projects = new ArrayList<>();
}
#Entity
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn//added this because i read somewhere this would help somehow but it didnt
private DBGroup group;
}
Can anyone help on how to avoid that loop? If I change the fetchtype to lazy in DBGroup I get a LazyInitializationEXception.
Any help is appreciated.
When the transaction ends you obtain an LazyInitializationEXception for all objects you didn't fetch.
If you get the object with a query add join fetch like:
select p from Project p join fetch p.group g
You can fetch a list via code calling the size method before exit the ejb.
Use FetchType.LAZY all time you can for prevent this especially if is a list.
Related
I am working on a project and I have felt like in #OneToMany Unidirectional association with #JoinColumn in JPA with springboot generates extra queries. For example if we have 2 entities
#Entity(name = "Post")
#Table(name = "post")
public class Post {
#Id
#GeneratedValue
private Long id;
private String title;
#OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
#JoinColumn(name = "post_id")
private List<PostComment> comments = new ArrayList<>();
//Constructors, getters and setters removed for brevity
}
#Entity(name = "PostComment")
#Table(name = "post_comment")
public class PostComment {
#Id
#GeneratedValue
private Long id;
private String review;
//Constructors, getters and setters removed for brevity
}
when we run the following code
Post post = new Post("First post");
post.addComment(
new PostComment("My first review")
);
post.addComment(
new PostComment("My second review")
);
post.addComment(
new PostComment("My third review")
);
entityManager.persist(post);
it generates following queries
insert into post (title, id)
values ('First post', 1)
insert into post_comment (review, id)
values ('My first review', 2)
insert into post_comment (review, id)
values ('My second review', 3)
insert into post_comment (review, id)
values ('My third review', 4)
update post_comment set post_id = 1 where id = 2
update post_comment set post_id = 1 where id = 3
update post_comment set post_id = 1 where id = 4
Now my question is that why does JPA updates post_comment records after inserting post_comment records ? why didn't JPA insert the post_comment records with the post_id while it was inserting records in post_comment table so that it don't have to update records again ?
You are depending on the CascadeType.ALL to insert the PostComments for you. Why is that? I consider that a JPA anti-pattern. JPA specifies that the owner of the relationship will persist relations. See If the relationship is bidirectional, the mappedBy element must be used to specify the relationship field or property of the entity that is the owner of the relationship. You have not specified an owner of the relationship.
What in the JPA specification can you point to that dictates that using the CascadeType should be implemented in any specific fashion?
What you show kind of makes sense to me. The Post is saved, the PostComments are saved b/c of the CascadeType, and then the Post id updated into the PostComments. You didn't set the Post id into the PostComments yourself so you don't have any say about how the implementation does it. I have seen CascaseType do some interesting things, but it's still an anti-pattern as far as I'm concerned. If you don't understand it, don't use it!
Besides that, you have also added a static = new ArrayList<>() on comments. Another anti-pattern. The majority of those new ArrayLists will be tossed onto the garbage heap in very short order. Even though Java manages memory for you, you should have some idea of how you are using it.
Short answer, the PostComments should be saved by you specifically, but only when you already have saved the Post and it is ready to be set into the PostComment. Of course, you should have the Post and the ManyToOne in the PostComment so you can do that.
I don't know if you've investigated the Query behaviors of the related Entities, but I think you'll find more surprises there as well. Since I have already done that, I make these comments.
References:
What is the "owning side" in an ORM mapping?
37.1 Entities
I found this question with different programming languages or different database
Im trying to make a portal to generate multiple choice quizes
the front end will send me a huge json at some point there will be an array of Questions objects
evry object should have:
the question
the right answer
a list of wrong answers ( min 2 max 4 )
the id of the CourseSpecifics object...
I need to store (if possible) the list of wrong answers ( Strings ) in a single record of my postgresql db
this is my class
#Entity
#Table(name = "questions")
public class Questions {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
#Column(name ="question",nullable = false)
String question;
#Column(name ="answer",nullable = false)
String answer;
#Column(name ="wrong_answers",nullable = false)
List<String> wrongAnswers;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name ="fk_specifics",nullable = false)
CourseSpecifics fkSpecifics;
}
This is the exact point where i have some doubts (lets skip the rest for the moment, i know i can improve it )
#Column(name ="wrong_answers",nullable = false)
List<String> wrongAnswers;
im also using spring.jpa.hibernate.ddl-auto=create to generate db from spring structure but its not mandatory
My questions are :
What is the best data type to use on spring in this situation for wrongAnswers ?
What is the best column definition to use on db according to the data type of wrongAnswers ?
You're likely looking for the #ElementCollection annotation. This will allow you to have a list of strings as you have it right now.
However, it use a new table! Which you don't seem to want. Without binding yourself to a single database (by using arrays or something) your best bet may be to write a converter to serialize a list of strings into a single string.
I'd personally revaluate if your single record is a super strong requirement.
I have a question about performance and common practice, if someone could explain this to me.
I have recently started using JPA and hibernate and have come across an Entity that has a foreign key and I need to get some data from it. So for example: CustomerAddress has a City and that city has a lot of detail and also a name.
SQL:
select
CA.Id, CI.Name
from
CustomerAddress as CA
inner join City as CI
on CA.CityID = CI.Id
So now in Java JPA Entity I can have a one-to-many annotation:
#Entity
#Table(name = "CustomerAddress")
public class CustomerAddressEntity {
#Id
#Column(name = "Id", unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#NotFound(action = NotFoundAction.IGNORE)
#JoinColumn(name = "city", referencedColumnName = "id", insertable = false, updatable = false)
private City city;
}
Where City is also an #Entity with #Id and simple object.
Which in my opinion does not turn out the best because it makes a lot of SQL requests.
And then I have the option having just two findAll() calls at the beginning, where I would collect all the City Entities in a HashMap<String, City> and when needing the name I would just call hashmap.get(key).getName().
EDIT (thanks for the heads up :)):
And when using this HashMap I can use a simpler Entity without the #JoinColumn
#Entity
#Table(name = "CustomerAddress")
public class CustomerAddressEntity {
#Id
#Column(name = "Id", unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "CityID")
private Long cityId;
}
In the hashmap case I only get two SQL calls and I think it works much faster. Is there a way to get this behavior also using JPA and hibernate?
If my question and code needs some more refinement please let me know.. I can edit the question with more details and perhaps if necessary provide a working example. Thank you for your thoughts :)
And the same would go for OneToMany, where the hashmap would be: new HashMap<String, List<City>> for example - I mean the whole example should be created a bit differently - I guess it could even be a HashMap<String, HashMap<String,City>> - if one would need quick access to the City by Id or sth... but i digress :) I will edit the question and respond to comments as I will go.. and refine the question if necessary.. I would just like to hear some thoughts and where my thinking is wrong :) and what am I failing to see and missing :)
EDIT: For example a code that would create a lot of SQL requests:
public interface CustomerAddressRepository extends JpaRepository<CustomerAddressEntity, Long> {
#Override
List<CustomerAddressEntity> findAll();
}
This for example creates an SQL Query (I would use findAll() at the beginning to list all - or most of the Entities for the user) and you would get an SQL query for every Entity because it would want to find the Name of the City as well - because the ID of the City Entity does not really help to the user.
Also - I like to have all the Entities in my RAM so I can do a quick search for the user more responsive - So a search does not always do SQL Query + #(found results) Queries.
The HashMap has nothing to do with the fact that Hibernate issues a query when you want to get the name of the City object.Here's why it's happening.
In your CustomerAddressEntity you have a OneToOne with City , and since you have a #JoinColumn there ,it means that CustomerAddressEntity database Table will have the Primary Key of the City table as a foreign key , and since you specified in your class that it should be fetched LAZY,Hibernate will create a Proxy object wrapping the City object,ready to get queried from the database in case you call any getMethod ,like getName() ,(excluding the getId() method since the ID exists prealably in the proxy object,you can check the sql query logs and see that the query selects the foreign key with all the other fields of CustomerAddressEntity ),that's why when you trigger the getName() method Hibernate will fetch that entity from the database.
In our "Process" table there is a "Type" column. This column's valueset is defined in an enum in our code. However there are obsolete rows in this table. Meaning that there are rows where "type" is a value that is not present in the code's enum. The problem is whenever we acces ANY (not the obsolete ones) of the rows in this table we get an error that there is an unkown value for that column. Is there a way to disable this feature in hibernate as we do not want to delete these rows.
#EqualsAndHashCode(callSuper = true, of = {})
#Table(name = ProcessEntity.TABLE_NAME)
public class ProcessEntity extends BaseEntity implements ValidityHolder {
public static final String TABLE_NAME = "PROCESS";
#OneToMany(mappedBy = "consent", cascade = CascadeType.ALL)
private Set<ConsentAnswerEntity> consentAnswers;
#OneToMany(mappedBy = "consent", cascade = CascadeType.ALL)
private List<ProcessConsentEntity> processConsents;
#OneToMany(mappedBy = "consent", cascade = CascadeType.ALL)
private Set<ProcessTypeConsentEntity> processTypeConsents;
#Enumerated(EnumType.STRING)
#Column(name = "TYPE_ID")
private Type TpyeId;
If these "obsolete" records no longer fit into your Hibernate data model, then I recommend just moving them to some archive table. After all, you can't really select them now anyway using Hibernate, so at least at the application level, they serve no purpose.
For a more general way to logically delete a record without physically removing it, look into soft deletion. Using soft deletion, you would add a single boolean column to the table which, if marked, would indicate that the record is logically no longer there.
I am creating a Spring 4 / Spring Data application for an existing database. The database structure and data are defined by a closed source software.
One aspect of the existing system is that you can create a comment on any other item in the system. This means, that an article, a document, a media file (all entities in the system) can have any number of comments, and each comment is exactly for one entity in the system. All comments are in the same comment table.
The way this is implemented is that the table comment has a column comment_for that holds a concatenated/namespaced/prefixed reference to the actual entity it is a comment for. The current system seems to just builds the join query by prefixing the primary key with the table name:
+----+-------------------+----------------+
| id | comment_for | comment |
+----+-------------------+----------------+
| 1| article:12345 | This is nice...|
| 2| document:42 | Cool doc! |
+----+-------------------+----------------+
This sample shows two comments, one for an Article with an article.id of 12345 and one for a document with document.id of 42. I created #Entities matching the database tables and the corresponding Repository Interfaces with the query methods I need.
I would like to make use of Spring Data Repositories / Entities to populate the collections of my entities with the corresponding comments, like this (pseudocde) for Entity Article.
#OneToMany(mappedBy = "comment_for", prefix = "article:")
private List<Comment> comment = new ArrayList<>();
I only need it unidirectional. My entities (at the moment Article, Document and Mediafile) should hold a collection of their comments. I don't need comments to hold a reference back to the entity.
Is there a way to do this? The resulting SQL query should be something like
SELECT * FROM .... WHERE comment.comment_for = concat('<entityname>:', <entity>.id);
I looked at #JoinColumn but I can't modify the used value for the join, only the column name. The only solution I have at the moment are manual #Querys on the CommentRepository Interface, which gives me an ArrayList of all comments for a certain Entity / ID combination. But I would like to have the comments automatically joined as part of my Business Entity.
Update : It looks like I am able to split the namespace and id from comment_for into two new columns without interrupting the existing software. The two columns are now comment_for_id and comment_for_entityname
You could also break out comment_for to contain only the id like your entities. Adding an additional column like entity_type would allow you to avoid duplicate id values between different entities.
Also you could use #JoinColumn on the owner side of the relationship between Entity and Comments. It looks like in your case that would be the Comment entity/table, since there are many comments per each entity.
Example:
#Entity
#NamedQueries({ #NamedQuery(name = "Comments.findAll", query = "select o from Comments o") })
#IdClass(CommentsPK.class)
public class Comments implements Serializable {
private static final long serialVersionUID = 4787438687752132432L;
#Id
#Column(name = "COMMENT_TEXT", nullable = false, length = 30)
private String commentText;
#Id
#Column(name = "ENTITY_TYPE", nullable = false, length = 30)
private String entityType;
#ManyToOne
#Id
#JoinColumn(name = "COMMENT_FOR")
private EntityDemo entityDemo;
Note that I set the combination of all three fields as the primary key, I am not sure what criteria is used as the PK in your current set up.
Here is an example of an Entity. The attributes have been made up for the purpose of demonstration.
#Entity
#NamedQueries({ #NamedQuery(name = "EntityDemo.findAll", query = "select o from EntityDemo o") })
#Table(name = "ENTITY_DEMO")
public class EntityDemo implements Serializable {
private static final long serialVersionUID = -8709368847389356776L;
#Column(length = 1)
private String data;
#Id
#Column(nullable = false)
private BigDecimal id;
#OneToMany(mappedBy = "entityDemo", cascade = { CascadeType.PERSIST, CascadeType.MERGE })
private List<Comments> commentsList;