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
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.
#Entity
#Table(name = "MATCHES")
public class Match implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "MATCH_ID")
private Long id;
#ManyToMany(mappedBy = "matches", cascade = CascadeType.ALL)
private Set<Team> teams = new HashSet<Team>();
}
#Entity
#Table(name = "Teams")
public class Team implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "TEAM_ID")
private long id;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "TEAM_MATCH", joinColumns = { #JoinColumn(name = "TEAM_ID") }, inverseJoinColumns = {
#JoinColumn(name = "MATCH_ID") })
private Set<Match> matches = new HashSet<Match>();
}
I got those classes, now I want to get all the matches and let's say, print names of both teams.
public List getAllMatches() {
Session session = HibernateUtil.getSession();
Transaction t = session.beginTransaction();
Criteria criteria = session.createCriteria(Match.class, "match");
criteria.createAlias("match.teams", "mt", JoinType.LEFT_OUTER_JOIN);
List result = criteria.list();
t.commit();
session.close();
return result;
}
But when I invoke that method, result has size 2 when I got only 1 match in my table. Both of those matches in result have 2 teams, which is correct. I have no idea why this happends. What I want is to have one Match object with two Team objects in 'teams' set, but I have two of those Match objects. They are fine, but there are two of them. I'm completely new to this and have no idea how to fix those criterias. I tried deleting 'FetchType.LAZY' from #ManyToMany in Team but it doesn't work. Team also has properties like Players/Trainer etc. which are in their own tables, but I don't want to dig that deep yet, baby steps. I wonder tho if doing such queries is a good idea, should I just return Matches and then if I want to get Teams, get them in another session?
Edit: I added criteria.setResultTransformer(DistinctRootEntityResultTransformer.INSTANCE); and it works, is that how I was suppose to fix that or this is for something completely different and I just got lucky?
I think the duplication is a result of your createAlias call, which besides having this side effect is redundant in the first place.
By calling createAlias with those arguments, you are telling Hibernate to not just return all matches, but to first cross index the MATCHES table with the TEAM_MATCH table and return a result for each matching pair of rows. You get one result for a row in the matches table paired with the many-to-many mapping to the first team, and another result for the same row in the matches table paired with the many-to-many mapping to the second team.
I'm guessing your intent with that line was to tell Hibernate to fetch the association. This is not necessary, Hibernate will fetch associated objects on its own automatically when needed.
Simply delete the criteria.createAlias call, and you should get the result you expected - with one caveat. Because the association is using lazy fetching, Hibernate won't load it until you access it, and if that comes after the session is closed you will get a LazyInitializationException. In general I would suggest you prefer solving this by having the session opened and closed at a higher level of abstraction - getting all matches is presumably part of some larger task, and in most cases you should really use one session for the duration of the entire task unless there are substantial delays (such as waiting for user input) involved. Changing that would likely require significant redesign of your code, however; the quick solution is to simply loop over the result list and call Hibernate.initialize() on the teams collection in each Match. Or you could just change the fetch type to eager, if the performance cost of always loading the association whether or not you need it is acceptable.
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
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 have a hierarchical data structure with a fixed depth of 4. For a better understanding, let's assume the following (just an example):
The "root" level is called countries
Each country contains an arbitrary amount of states
Each state countains an arbitrary amount of counties
Each county contains an arbitrary amount of cities
So there are always 1-N relationships between the levels.
A very important usecase (given the id of a country) is to load the whole "content" of a country at once with the smallest possible impact on the performance of the database.
In a first naive approach, I created 4 entitiy classes in Java where the entity "Country" contains a list of the type "State", the entity "State" contains a list of the type "County" and so on...
But what JPA creates afterwards are of course not 4 tables, but 7 (4 for the entities + 3 for the connection between the levels due to 1-N). I don't know if this is a good solution since there is a lot of joining going on under the hood.
I also tried to map the subtypes to their parent types (a city belongs to one county, a county belongs to one state, a state belongs to one country). This results in 4 tables, but makes it more difficult to retrieve all data at once from the application's point of view. If I'm not wrong, I would need 4 different requests instead of one.
How could I solve this problem? Is there a way to combine a simple table layout (with four tables, not seven) with easy to use entity classes (a parent type should know its children)?
If not, how would you realize this?
I'm using JPA with Hibernate and PostgreSQL.
You can avoid the 3 extra mapping tables by using the #JoinColumn annotation rather than the #JoinTable annotation that I suspect you are using.
So for example:
COUNTRY
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy="country")
private List<State> stateList;
STATE
#ManyToOne
#JoinColumn(name="country_id")
private Country country
The database tables would be as follows:
Country
country_id => primary key
State
state_id => primary key
country_id => foreign key
This way the mapping tables between all the 4 entities can be avoided.
You can achieve this pretty easily using JPQL:
SELECT DISTINCT country
FROM Country country
JOIN FETCH country.states states
JOIN FETCH states.counties counties
JOIN FETCH counties.cities cities
WHERE country.id = :countryId
Using fetchType = FetchType.EAGER on #OneToMany/#ManyToOne(believe that one is already EAGER by default) will achieve similar results.
It's very simple use bidirectional mapping. Go through that link
How to delete Child or Parent objects from Relationship?
Make some changes like below
Country Entity:
------
#OneToMany(mappedBy="Country ",cascade = CascadeType.ALL)
private List<States > states;
#OneToMany(mappedBy="Country ",cascade = CascadeType.ALL)
private List<Counties> counties;
#OneToMany(mappedBy="Country ",cascade = CascadeType.ALL)
private List<Cities> cities;
-------
setters & getters
States Entity:
-----
#ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="countryId")
private Country country ;
-----
Counties Entity:
--------
#ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="countryId")
private Country country ;
-------
Cities Entity:
#ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="countryId")
private Country country ;
---------
After compilation of all entity's do your insertion . Only 4 will create and read your data by using Country object id.
You already have the solution: four table is the way to go, with bidirectional relationships (use the mappedBy property in the not-owning side of every relationship). If the relationships are EAGER-fetched, than all entities are automatically loaded. If you want to use LAZY fetching, you could try a named query in order to load the entity with all relationships loaded:
SELECT DISTINCT c FROM Country c LEFT JOIN FETCH c.states s LEFT JOIN FETCH s.counties co...
Did you try to declare the fetch type of the relations explicitely to eager with your second approach (default is lazy, that's why you have to do four queries).
E.g.
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn ...
private ...;
see here: http://www.concretepage.com/hibernate/fetch_hibernate_annotation
Here is how your entities will look like:(You can use EAGER Loading instead of LAZY as well if you want)
Entity: Country
#Id
private Integer id;
#OneToMany(orphanRemoval=true fetch=FetchType.LAZY)
#JoinColumn(name="COUNTRY_ID")
private List<State> stateList;
Entity: State
This table has COUNTRY_ID that is Foreign Key to Country
#Id
private Integer id;
#OneToMany(orphanRemoval=true fetch=FetchType.LAZY)
#JoinColumn(name="STATE_ID")
private List<County> countyList;
#Column(name="COUNTRY_ID")
private Integer countryId;
Entity: County
This table has STATE_ID that is Foreign Key to State
#Id
private Integer id;
#OneToMany(orphanRemoval=true fetch=FetchType.LAZY)
#JoinColumn(name="COUNTY_ID")
private List<City> cityList;
#Column(name="STATE_ID")
private Integer stateId;
Entity: City
This table has COUNTY_ID that is Foreign Key to County
#Id
private Integer id;
#Column(name="COUNTY_ID")
private Integer countyId;
Your JPQL will be:
Select o from Country o where o.id=10
This will pick The Country Entity along with all the mappings like below.
Country
Holding List of States
Each States Holding List of Counties
Each Counties Holding LIst of Cities
For a requirement like yours, I would suggest to have a tree-like structure to maintain the hierarchical location data. It is relatively easy to implement & maintain and is more scalable & extensible.
In order to implement tree you need to have 2 tables LOCATION_NODE (Location ID, Location Name, Location Type[country, state, county, city]) & LOCATION_REL (Relation ID, Parent ID, Child ID). Below is the basic implementation of the tree idea.
public class LocationRel<T> {
private LocationNode<T> root;
public LocationRel(T rootData) {
root = new LocationNode<T>();
root.data = rootData;
root.children = new ArrayList<LocationNode<T>>();
}
public static class LocationNode<T> {
private T data;
private LocationNode<T> parent;
private List<LocationNode<T>> children;
}
}
This is the basic building block for a tree. You may need to add methods for add to, removing from, traversing, and constructors. But, once implemented, you have the freedom to add any new location type, change the hierarchy, add node, delete node etc with your hierarchical data.
Think out of the box.
Shishir
If you need the performance, I would suggest to de-normalize your tables and create 4 entities with following attributes (columns):
Country: id, name
State: id, countryId, name
County: id, countryId, stateId, name
City: id, countryId, stateId, countyId, name
(mapping is obvious)
Then you will be able to build a simple SQL queries.
If you need performance, prefer named queries as they are compiled at initialization time.
E.g. select all cities by country: "SELECT id, name FROM city WHERE country_id=?"
You may even not declare a references between entities using #ManyToOne, but just declare a simple #Columns. API call will, most likely, accept IDs (countryId, stateId), so you'll be better to pass that IDs as parameters to DAO. Most likely, you have a locations tables filled in once by sql script and the data should not be modified. Create foreign keys to guarantee data integrity.
And do you really need a tree-like structure in memory? If so, create it by hand, it is not very complex.
Searching Online, I found a couple of Links on JPQL which I think might help.
Link 1
Link 2
Anyways,
JPQL is one of the best ways to achieve this, try out this Query
SELECT DISTINCT country FROM Country country JOIN FETCH country.states states JOIN FETCH states.counties counties JOIN FETCH counties.cities cities WHERE country.id = :countryId
A solution that is useful, if you have relations that point to their parent only is the following:
With records:
#Entity
public class Country
{
#Id
private Long id;
}
#Entity
public class State
{
#Id
private Long id;
#ManyToOne(optional = false)
#JoinColumn(name = "country_id", referencedColumnName = "id", nullable = false)
Country country;
}
#Entity
public class County
{
#Id
private Long id;
#ManyToOne(optional = false)
#JoinColumn(name = "state_id", referencedColumnName = "id", nullable = false)
State state;
}
#Entity
public class City
{
#Id
private Long id;
#ManyToOne(optional = false)
#JoinColumn(name = "county_id", referencedColumnName = "id", nullable = false)
County county;
}
You can get all cities of a country with:
public interface CityRepository extends JpaRepository<City, Long>
{
List<City> findByCounty(County county); // county is a direct field of City
#Query("SELECT c FROM City c WHERE c.county.state.country = ?1")
List<City> findByCountry(Country country);
}