JPA (#OneToMany) query without joining child table - java

I'm writing a service with JPA and Postgres db. I have a class named Student:
public class Student {
#id
private String id;
private String firstName;
private String lastName;
#OneToMany(targetEntity = Phone.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "student_id", referencedColumnName = "id")
private Set<Phone> phones;
// Setter and Getter
}
And a Phone class:
public class Phone {
#id
private String id;
private String number;
// Setter and Getter
}
Now there will be two tables in my db, following are columns in them:
student: id, first_name, last_name
phone: id, number, student_id (generated by #JoinColumn)
Now every time when I query the student, JPA will join the phone table into student table and the student result contains the phone info of this student. This is exactly what I want.
But now I met a problem. When I query the list of the students, the phone info is not useful in this case, only the id, firstName and lastName are necessary. But JPA does the same "join" operation for me too. I know this consumes lots of time. In such case how could I just return the info in the student table? Without joining the phone table?
I tried something in the repository like
#Query(SELECT s.id, s.firstName, s.lastName FROM student s)
public List<Student> findAllStudentWithoutPhone();
but it returns me the list of the values, but not converted into a Student object. How could I achieve this feature?

In the one-to-many mapping (refer below) you have set the fetch type to be lazy fetch = FetchType.LAZY hence hibernate won't fetch set of phones corresponding to the student until you access the set of phones via getter method, so no need to worry about it.
#OneToMany(targetEntity = Phone.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "student_id", referencedColumnName = "id")
private Set<Phone>;
However in case you need to verify whether things are working fine or not you can set the property show_sql to true and check the sql generated via hibernate.

fetch = FetchType.LAZY provides you possibility don't query Phone table until of the first call from the code. Please see: http://docs.oracle.com/javaee/7/api/javax/persistence/FetchType.html#LAZY
In case if you want to retrieve the list of the students without phones query should be:
#Query(SELECT * FROM student s where phones IS NULL)
For automatically converting results into Student object please don't use s.id, s.firstName, s.lastName in your query.

Answer given by #Sahil is absolutely correct but to add that.
#Cong
You do not need to add FetchType.LAZY as by default its already LAZY.
& as a note the Student class property for phone missing variable name.

Related

How to get Count of OneToMany field in JPA Enity?

How can we get the count of OneToMany field of JPA entity as querying count for each parent entity while fetching as a list is costly and there is no way in JPA Repository.
I want to get the number of likes and comments for each PostEntity. The field is Lazy fetch type and if I call likes.size() or comments.size() then it will load all of the comments and likes from database and there can be thousands of comments and likes.
I know I can create a seperate repo for likes and comments to get the counts but while calling method from PostRepository how to get the counts for each and every entity? What is the best and efficient way?
Parent Entity
#Entity
#Table(name = "posts")
#Getter
#Setter
public class PostEntity extends MappedSuperClassEntity<UserEntity> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Nullable
private String title;
#Nullable
private String postText;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="user_id")
private UserEntity user;
#Nullable
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "community_id")
private CommunityEntity community;
#OneToMany(fetch = FetchType.LAZY)
private List<CommentEntity> comments;
#OneToMany(fetch = FetchType.LAZY)
private List<LikeEntity> likes;
#Transient
private int numberOfLikes;
#Transient
private int numberOfComments;
}
I would like to get the likes and comments count for each PostEntity while querying for the list of posts.
My Repo
public interface PostsRepository extends PagingAndSortingRepository<PostEntity, Integer> {
#Query(value = "SELECT P FROM PostEntity P WHERE P.user.id = :userId ORDER BY P.createdDate DESC")
Page<PostEntity> getUserPosts(int userId, Pageable pageable);
#Query(value = "select P from PostEntity P where p.community.id = :communityId order by P.createdDate desc")
Page<PostEntity> getCommunityPosts(int communityId, Pageable pageable);
}
I searched for a lot and someone suggested to use #Formula annotation for custom queries on the entity field but #Formula is hibernate specific and don't know if it works with #Transient field. Is there any JPA specific way to do that as it's a common problem.
You need "LazyCollection" annotation with EXTRA option.
#OneToMany(fetch = FetchType.LAZY)
#LazyCollection(LazyCollectionOption.EXTRA)
private List<CommentEntity> comments;
This annotation would allow to access "size()" without loading.
You can check this article.
https://www.baeldung.com/hibernate-lazycollection
Sometimes, we're only concerned with the properties of the collection, and we don't need the objects inside it right away. For example, going back to the Branch and the Employees example, we could just need the number of employees in the branch while not caring about the actual employees' entities. In this case, we consider using the EXTRA option. Let's update our example to handle this case. Similar to the case before, the Branch entity has an id, name, and an #OneToMany relation with the Employee entity. However, we set the option for #LazyCollection to be EXTRA:
I try to add comment but i have no writing comment access because of reputation so i send an answer.

Java JPA - save() an object with #JoinTable attribute

I'm trying to insert an row into my database. I have following sql setup (its just an example):
Table person:
(id, name)
Table person_street:
(person_id, street_id)
Table street
(id, name)
This should be a many to many relation. This example doesn't make sense, but I think you'll understand the idea.
Now I have these entities in java
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#JoinTable(
name = "person_street",
joinColumns = #JoinColumn(name = "person_id"),
inverseJoinColumns = #JoinColumn(name = "street_id")
)
#ManyToMany
private List<Street> streets;
}
This is a good solution to work with my person objects (after reading them from my database).
But I have problems while inserting/creating person objects.
I want to create a new person in my frontend. It calls an REST interface at my backends side with String name, List<Long> streets.
Now I want to insert this new person (with the given name and streets) into my database.
But I don't want to do a select for all List<Long> streets on my street table. There is no need to change any value of the street objects. I just want to insert the link between the new person and the existing streets (in the table person_street).
What is the easiest way to do that?
Can I use my Person class for this, or does I need a new different class.
T
you can add this method to the Person class
public void addStreets(Street street) {
if(streets==null) {
streets=new ArrayList<Street>();
}
streets.add(street);
}
and after that, you get the street by the id from the street table and added to the corresponding person which you are getting from the front end after that you save the whole person.

Hibernate #OneToMany - child owns the relationship, but only has the parents id

I have a #OneToMany relationship defined on the parent class like so:
public class Course {
#OneToMany(
mappedBy = "courseId",
fetch = FetchType.EAGER,
cascade = CascadeType.ALL,
orphanRemoval = true)
private Set<Student> students;
}
On the other side of the relationship, I simply keep the id of the parent entity:
public class Student {
private Long courseId;
}
When I save a Course with new Students, hibernate first persists the Course, and then tries to persist each Student which is what I would expect. (I can see this via hibernate logging.)
However, when it goes to insert each Student, it is passing a null for the courseId. The database ends up throwing this error:
ERROR: null value in column "courseid" violates not-null constraint
I have other examples of this working correctly in the code, but for some reason this one is behaving differently.
Is there a reason it's not using the id from the Course it just saved? Is there some other configuration I need to add to support this?
I think you need to change your Student class:
public class Student {
#ManyToOne
#JoinColumn(name="course_id", nullable=false)
private Course course;
}

JPA nativeQuery returns cached resultList

I have following classes:
Company.class:
public class Company {
#JoinTable(name = "company_employee", joinColumns = #JoinColumn(name = "company_id") , inverseJoinColumns = #JoinColumn(name = "employee_id") )
#ManyToMany(fetch = FetchType.LAZY)
private Set<Employee> employees;
#Column(name = "score")
private BigDecimal score;
}
and Employee.class
public class Employee {
#ManyToMany(fetch = FetchType.EAGER, mappedBy="employees")
private Set<Company> companies;
}
The Score column of Company is always null in the db and never updated via dao, because there is other table containing score for each unique pair Company-Employee.
I need the value of Score, only for the case when I fetch Employee by id, so this case all Company instances in the Set should contain score, thus I will get Employee-Company score pairs where employee is fetched Employee.
I have following code to achieve that:
public Employee get(Long id) {
Employee emp = (Employee) dao.find(id);
List<Company> compList = compnanyService.getByEmpId(id);
Set<Company> compSet = new HashSet<Company>(compList);
emp.setCompanies(compSet);
return emp;
}
And Company Dao contains method:
public List<Company> getByEmpId(Long id) {
final Query query = this.entityManager.createNativeQuery("select company.comp_id, ...some other fields, score.score from company join score on company.company_id=score.company_id where score.employee_id=:employee_id",
Company.class);
query.setParameter("employee_id", id);
List<Company> comps = query.getResultList();
return comps;
}
The problem is that getByEmpId(id) gives a ResultList where company.score is null though executed in the db it is not null.
I suspected that there is some caching intervening, so I tried to remove some columns from the native query, and it should have invoked an exception with "no column found" (or alike) message while mapping, but this method still gives List<Company> with all fields on their places though Hibernate prints out my native query in the console with all changes I make.
What am I doing wrong here and how to achieve what I need? Thank you.
It might be associated with first level cache, which can be out of sync when using native SQL queries. From here:
If you bypass JPA and execute DML directly on the database, either
through native SQL queries, JDBC, or JPQL UPDATE or DELETE queries,
then the database can be out of synch with the 1st level cache. If you
had accessed objects before executing the DML, they will have the old
state and not include the changes. Depending on what you are doing
this may be ok, otherwise you may want to refresh the affected objects
from the database.
So you can try using refresh method from EntityManager.
So I ended up doing that:
Created view in db from the query:
CREATE VIEW companyscore AS select company.comp_id, score.emp_id ...some other fields, score.score from company join score on company.comp_id=score.comp_id;
Created corresponding entity CompanyScore with composite primary id as comp_id and emp_id and created view as table.
Changed Employee entity to:
public class Employee {
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "emp_id")
private Set<CompanyScore> companies;
}
This way I not only have score field always consistent, but I can choose set of fields to show as the whole Company class is quite extensive and I don't need all the fields for this particular case.

Complex query for hibernate

I have three tables with entities in hibernate. DB - MySQL. I need to get fields from entity "Item" where ModelsMm.id has some value. At first I tried to do separate queries, it was huge amount of requests in sum. So, i tried to do complex query, but it became a very long run.
I think there is a simpler way, but I do not know what.
My query and entities.
List<Item> itemIds = session.createQuery("select it from Item it where :id in elements(it.mmPrice.modelsMm)");
#Entity (name = "MODELS_MM")
public class ModelsMm {
#Id
private int Id;
#ManyToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name="parth_mm", joinColumns={#JoinColumn(name="MODEL_ID", referencedColumnName="ID")}, inverseJoinColumns={#JoinColumn(name="PART_ID", referencedColumnName="ID")})
private List<MmPrice> mmPrices;
#Entity (name = "MM_PRICE")
public class MmPrice {
#Id
private int id;
private String article;
#OneToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = "article", referencedColumnName = "article",insertable = false, updatable = false)
private Item item;
#ManyToMany
#JoinTable(name="parth_mm", joinColumns={#JoinColumn(name="PART_ID", referencedColumnName="ID")}, inverseJoinColumns={#JoinColumn(name="MODEL_ID", referencedColumnName="ID")})
private List<ModelsMm> modelsMm;
#Entity
#Table(name="SHOP_ITEMS")
public class Item implements Serializable {
#Id
private int id;
private String article;
#OneToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = "article", referencedColumnName = "article",insertable = false, updatable = false)
private MmPrice mmPrice;
In console i have that query
Hibernate: select item0_.ID as ID0_, item0_.ARTICLE as ARTICLE0_, item0_.article as article0_ from SHOP_ITEMS item0_ cross join MM_PRICE mmprice1_ where item0_.article=mmprice1_.article and (? in (select modelsmm2_.MODEL_ID from parth_mm modelsmm2_ where mmprice1_.ID=modelsmm2_.PART_ID))
Thanks.
First, you'll have to fix your mapping. In a bidirectional association, one side MUST be the inverse side, and thus use the mappedBy attribute. For example, if you choose ModelsMm to be the inverse side, then its mmPrices attribute should be declared as
#ManyToMany(mappedBy = "modelsMm")
private List<MmPrice> mmPrices;
You should also forget about CascadeType.ALL on ManyToMany associations: it makes no sense. You don't want to delete all the courses of a student when you delete a student, since the course is also followed by several other students.
Now, regarding your query, it's not very clear what you want to do. If you want to select all the items which have a price which have at least one model whose ID is in a collection of IDs, then you simply need the following query:
select distinct i from Item i
join i.mmPrice p
join p.modelsMm m
where m.id in :modelIds
Side note: please fix your naming. This inconsistent and unnecessary usage of mm as a prefix or suffix makes the code unreadable. Name your class Price, the fields of type Price price, and the collections of prices prices. Just as you would do in English: an Item has a price, and a price has models.

Categories