I am working on my school project and kind of replicating Stack Exchange site (project purpose only)
There is a table called Posts which might be a Question same as an Answer, based on PostTypeId. When it is a Question, it's column called AcceptedAnswerId is populated. However, there is no column listing all Answers for that Question.
The only relation between the Question and it's Answers is a column ParentId in the Answers. Meaning #ManyToOne
Now I need to get all Answers per that Question.
Any idea whether such thing is even possible?
So far, I've got something like this:
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "ParentId", referencedColumnName = "Id")
public Post getParentPost(){
return parentPost;
}
public void setParentPost(Post parentPost) {
this.parentPost = parentPost;
}
Simplest way is to also map the other end like this :
#OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY, mappedBy = "ParentPost")
public List<Post> getChildPosts();
see also JPA JoinColumn vs mappedBy
Related
I want to create one to many mapping like Post has many Comments. I have two solutions for adding comments. The first solution is to create a repository for the comment and the second solution is to use PostRepository and get post and add comment to the post. Each solution has its own challenges.
In the first solution, creating repositories per entity increases the number of repositories too much and based on the DDD, repositories should be created for Aggregate Roots.
In the second solution, there are performance issues. To load, add or remove nested entities, the root entity must be loaded first. To add entity, other related entities like User Entity in Comment Entity must be loaded from userRepository. As a result, these additional loadings cause a decrease in speed and total performance.
What is the best practice to load, add or remove nested entities?
File Post.java
#Entity
#Table(name = "posts")
#Getter
#Setter
public class Post
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Size(max = 250)
private String description;
#NotNull
#Lob
private String content;
#OneToMany(mappedBy = "post", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<Comment> comments = new HashSet<>();
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "user_id", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
private User user;
}
File Comment.java
#Entity
#Table(name = "comments")
#Getter
#Setter
public class Comment {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Lob
private String text;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "post_id", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
private Post post;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "user_id", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
private User user;
}
#Entity
#Table(name = "Users")
#Getter
#Setter
public class User
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<Comment> comments = new HashSet<>();
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<Post> posts = new HashSet<>();
}
"best" is not well defined.
But here is what is probably to be considered the canonic stance the Spring Data Team has on this question.
You definitely should NOT have one repository per entity (s. Are you supposed to have one repository per table in JPA?).
The reason is certainly not that you'd have to many classes/interfaces.
Classes and interfaces are really cheap to create both at implementation time and at run time.
It is kind of hard to have so many of them that it poses a significant problem.
And if it would, already the entities would cause a problem.
The reason is that repositories handle aggregates, not entities.
Although, admittedly the difference is hard to see in JPA based code.
So your question boils down to: What should be an aggregate.
At least part of the answer is already in your question:
In the second solution, there are performance issues. To load, add or remove nested entities, the root entity must be loaded first. To add entity, other related entities like User Entity in Comment Entity must be loaded from userRepository. As a result, these additional loadings cause a decrease in speed and total performance.
The concepts of aggregate and repository are widely adopted in the microservice community because they lead to good scalability.
This certainly isn't the same as "speed and total performance" but certainly related.
So how go these two view together?
Andrey B. Panfilov is onto something with their comment:
#OneToMany is actually #OneToFew like "person may be reachable by a couple of phone numbers".
But it only describes a heuristic.
The real rule is: An aggregate should group classes that need to be consistent at all times.
The canonical example is a purchase order with its line items.
Line items on their own don't make sense.
And if you modify a line item (or add/remove one) you might have to update the purchase order, for example in order to update the total price or in order to maintain constraints like a maximum value.
So purchase order should be an aggregate including its line items.
This also means that you need to completely load an aggregate.
This in turn means that it can't be to big, because otherwise you'd run into performance problems.
In your example of Post, Comment, and User, Post might form an aggregate with Comment.
But in most systems the number of comments is close to unlimited and can be huge.
I therefore would vote for making each entity in your example its own aggregate.
For more input about aggregates and repositories you might find Spring Data JDBC, References, and Aggregates interesting.
It is about Spring Data JDBC not Spring Data JPA, but the conceptual ideas do apply.
N+1 problem: fetch data in loop and If you have 2000+ data for posts and comments, you need to avoid to fetch for each data.
// Ex: 2000 posts is fetched
for(Post post: userRepository.findById("1").getPosts()) {
// fetching in loop: you go to database for each post(2000) and get comments of posts.
Set<Comment> comments = post.getComments();
}
Solution: create a repository for Post and fetch with custom repository. There are a lot of way to fetch eagerly. Ex: EntityGraph, FetchType.EAGER, JPQL ...
#Query(value = "select p from Post p fetch left join p.comments c where p.id=:postId)
public Set<Post> postsWithComments(#Param("postId") Long postId)
Set<Post> posts = postRepository.postWithComments(1L);
Even you need to be careful when fetching data eagerly, If there are a lot of comments for post simply use another repository for Comment.
public Set<Comment> findByPostId(String postId);
Set<Comment> comments = commentRepository.findByPostId(1L);
Even if there are 60000 comments for a single post. you need to fetch with pagination which can be helpful in critical times.
public Page<Comment> findByPostId(Long postId, Pageable pageable);
Page<Comment> comments = commentRepository.findByPostId(1L, PageRequest.of(2000));
int loopCounter = comments.getTotalElements() % 2000 == 0 ? comments.getTotalElements() / 2000 : comments.getTotalElements() / 2000 + 1;
int i=1;
do{
// do something
i++;
}while(i <= loopCounter);
For further things you need to use cache strategies for improving performance.
Also you need to define what can be the response time of request and what is actual response time. You can use fetch with left join or simply another request. In the long running processes you can use async operations as well.
I have 3 elements like this:
public class ItemType {
#Id
private Long id = null;
...
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "itemTypeVO")
#MapKey(name = "company.id")
private Map<Long, ItemTypePurpose> purposeHash = null;
...
}
public class ItemTypePurpose {
#Id
private Long id = null;
...
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "idcompany")
private Company company = null;
...
}
public class Company {
#Id
private Long id = null;
...
}
My problem is, I want the ID of Company as key of my map inside ItemType .
I can compile and deploy the application without any errors. Can persist ItemType, and everything goes well to DB. But when I get it back, the Map key is "wrong", I don't know what information is being used, but for sure it's not the Company id. Perhaps the ItemTypePurpose's ID.
The Company is being loaded into Map correctly, just the map key is wrong. I've tryied to google, bu can't find anything. Does any way to JPA create my map with this "nested attribute"?
*Sorry about my english, feel free if you understand what I need and can do a better writing in english to edit my question.
This doesn't exactly solves the question, but solve my needs for now.
Since que ID of Company was in table of ItemTypePurpose, I could change the MapKey to:
public class ItemType {
#Id
private Long id = null;
...
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "itemTypeVO")
#MapKeyColumn(name = "idcompany", insertable = false, updatable = false)
private Map<Long, ItemTypePurpose> purposeHash = null;
...
}
Instead of #MapKey, I used #MapKeyColumn. The #MapKeyColumn(name = "idcore_company", insertable = false, updatable = false is to turn the "Key Information" ReadOnly and avoid mapping conflict, since the same column is used in ItemTypePurpose to map the Entity.
Not exactly an answer, but "worked around" to solve my needs. This solution does not cover if you want a field as Map Key other than the ID.
Late reply, but can be helpful to someone else.
#MapKeyColumn seems to be the official solution here. As per the documentation, it seems the annotation to be used depends on the key type of the Map, regardless of the mapped fields. In your case, the key type is a Long, hence below will apply:
https://docs.oracle.com/cd/E19226-01/820-7627/giqvn/index.html
Using Map Collections in Entities
Collections of entity elements and
relationships may be represented by java.util.Map collections. A Map
consists of a key and value.
If the key type of a Map is a Java programming language basic type,
use the javax.persistence.MapKeyColumn annotation to set the column
mapping for the key. By default, the name attribute of #MapKeyColumn
is of the form RELATIONSHIP FIELD/PROPERTY NAME_KEY. For example, if
the referencing relationship field name is image, the default name
attribute is IMAGE_KEY.
In summary:
For nested fields go for MapKeyColumn(name="myNestFiled_key"), then you will set the value manually in your code like:
ItemType.getPurposeHash().put(ItemTypePurpose.getCompany().getId(), ItemTypePurpose);
I am new to Spring and need help.
I have three entity Exam Category, Exam and Subject
In the Exam Category entity:
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "EXAM_CATEGORY_ID")
private Set<Exam> exams;
In Exam entity I have defined the relation:
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#OrderBy
#JoinColumn(name = "EXAM_ID")
private Set<Subject> subjects;
But when I make a get call for all Exam Category it returns me all data like all exam category with all exams and in exams all subjects.
I don't want subject data in this get call.
But I want subject data when I make a get call for particular any exam.
Thanks in advance :-)
I think you can use and excerpt projection that does not include the subject. This excerpt would be used when the collection resource is requested. The single resource would stay the same.
For more information you could consult the documentation:
http://docs.spring.io/spring-data/rest/docs/2.4.0.RELEASE/reference/html/#projections-excerpts.excerpts
I've been searching over the web to find out a solution for this. It seems nobody has the answer... I start thinking i'm in wrong way adressing the problem.
Let's see if i can explain easy.
Im developing a contract maintenance. (table: contrat_mercan). For the contract, we will select a category (table: categoria), each category has qualities (table: calidad) in relation 1 - N (relationship table categoria_calidad).
This qualities must have a value for each contract where the category is selected, so I created a table to cover this relationship: contrato_categoria_calidad.
#Entity
#Table(name = "contrato_categoria_calidad")
public class ContratoCategoriaCalidad implements Serializable{
// Constants --------------------------------------------------------
private static final long serialVersionUID = -1821053251702048097L;
// Fields -----------------------------------------------------------
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "CCC_ID")
private int id;
#Column(name = "CONTRAT_MERCAN_ID")
private int contratoId;
#Column(name = "CATEGORIA_ID")
private int categoriaId;
#Column(name = "CALIDAD_ID")
private int calidadId;
#Column(name = "VALOR")
private double valor;
.... getters/ setters
In this table I wanted to avoid having an Id, three fields are marked as FK in database and first attempts where with #JoinColumn in the three fields. But it does not worked for hibernate.
Anyway, now ContratoCategoriaCalidad is behaving okay as independent entity. But I will need to implement all maintenance, updates, deletes for each case manually... :(
What I really want, (and I think is a better practice) is a cascade when I saveOrUpdate the contract as the other entities do, but I don't find the way to make a List in contrat_mercan table.
This is working perfect for other relationships in same table:
#OneToOne
#JoinColumn(name="CONDICION")
private Condicion condicion;
#OneToMany (cascade = {CascadeType.ALL})
#JoinTable(
name="contrato_mercan_condicion",
joinColumns = #JoinColumn( name="CONTRATO_MERCAN_ID")
,inverseJoinColumns = #JoinColumn( name="CONDICION_ID")
)
private List<Condicion> condiciones;
But all my attempts to map this failed, what i want, is to have in my Java entity contrat_mercan a field like this:
private List<ContratoCategoriaCalidad> relacionContratoCategoriaCalidad;
not a real column in database, just representation of the relationship.
I found solutions to join multiple fields of the same table, here, and here, but not to make a relationship with 3 tables...
Any idea? Im doing something wrong? Maybe i must use intermediate table categoria_calidad to perform this?
Thanks!!
If you want to access a list of related ContratoCategoriaCalidad objects from Contrato entity you need to declare a relationship between those two entities using proper annotations.
In ContratoCategoriaCalidad class change field to:
#ManyToOne
#JoinColumn(name = "CONTRATO_ID")
private Contrato contrato;
In Contrato class add field:
#OneToMany(mappedBy = "contrato")
private List<ContratoCategoriaCalidad> relacionContratoCategoriaCalidad;
If you want to enable cascade updates and removals consider adding cascade = CascadeType.ALL and orphanRemoval = true attributes to #OneToMany annotation.
Hope this helps!
I having a hard time with JPA hopefully someone can help me.
I have 3 tables:
Rol (CPE_ROL)
TipoUsuario (GTV_TIPOUSU)
RolTipoUsuario (CPE_ROLTUS - Join Table)
Rol.java
#JoinTable(name = "CPE_ROLTUS", joinColumns = {
#JoinColumn(name = "CPE_ROLTUS_TIPOUSU_ID", referencedColumnName = "GTV_TIPOUSU_ID")}, inverseJoinColumns = {
#JoinColumn(name = "CPE_ROLTUS_ROL_ID", referencedColumnName = "CPE_ROL_ID")})
#ManyToMany(fetch = FetchType.LAZY, cascade={CascadeType.REFRESH})
private List<TipoUsuario> tipoUsuarioList;
TipoUsuario.java
#ManyToMany(mappedBy = "tipoUsuarioList", fetch = FetchType.LAZY, cascade={CascadeType.REFRESH})
private List<Rol> rolesDefault;
For some reason rolesDefault is never filled up, I wondering if I'm missing something.
Thanks in advance.
Daniel
My guess is when you create the objects you are not setting both sides of the relationship. You must maintain bi-directional relationships in JPA. When you add to one side, also add to the other.
See,
http://en.wikibooks.org/wiki/Java_Persistence/Relationships#Object_corruption.2C_one_side_of_the_relationship_is_not_updated_after_updating_the_other_side
You most likely have caching enabled, or are using the same EntityManager, so when reading you get objects from the cache. You could also disable the shared cache, or refresh the object, but fixing your persist code is best.
Otherwise, enable logging on finest and see what SQL is executed.