Hibernate #OneToMany causing multiple select statements - java

I am working on a Hibernate issue, which involves 2 separate Entity beans defined separately in their own classes:
Store
StoreServer
Note that a Store will have more than one StoreServer - hence the use of the #OneToMany annotation. Please see the code snippets as follows:
Store:
#Entity
#Table(name="Store")
public class Store implements Serializable {
/**
* Serializable class - generated UID
*/
private static final long serialVersionUID = 5644190852867691168L;
#Id
#Column(name="STORE_NO", nullable=false)
private int storeNumber;
#Column(name="STORE_NAME", nullable=false)
private String storeName;
#Column(name="STORE_PHONE", nullable=false)
private String storePhone;
//other Store fields...
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name="STORE_NO", insertable=false, updatable=false)
private List<StoreServer> storeServers = new ArrayList<StoreServer>();
//getters and setters
StoreServer:
#Entity
#Table(name="Store_Server")
public class StoreServer implements Serializable {
/**
* Serializable class - generated UID
*/
private static final long serialVersionUID = -5410564578856243437L;
#Id
private StoreServerPK storeServerPK;
#Column(name="IP_ADDRESS", nullable=true)
private String ipAddress;
//other StoreServer fields...getters and setters
Since StoreServer has a composite Primary Key, here is StoreServerPK:
#Embeddable
public class StoreServerPK implements Serializable {
/**
* Serializable class - generated UID
*/
private static final long serialVersionUID = -1401889029390423604L;
#Column(name="STORE_NO", nullable=false)
protected int storeNumber;
#Column(name="SERVER_NO", nullable=false)
protected String serverNumber;
//getters and setters
At present, I am getting the correct results, but the performance is unacceptably SLOW. I have switched on logging in Hibernate and I can see that a separate SELECT query is being run for each Store Entity in order to obtain the associated StoreServer records.
Currently, in the logs, I see a single SELECT statement to obtain the Store records (more than 200 results returned). Then for each store, a new SELECT statement to get the StoreServer records. My question is...Why is Hibernate not doing a join (running one query)?
Please could I get some help on how to tell Hibernate to run a single query, using a JOIN?
Thank you

It is called N+1 problem
The solution actually depends on how do you make your query - in case if you are using Criteria API you should use Root.fetch method:
CriteriaBuilder qb = em.getCriteriaBuilder();
CriteriaQuery<Store> cq = qb.createQuery(Store.class);
Root<Store> root = cq.from(Store.class);
root.fetch(App_.storeServers, JoinType.LEFT);
cq.select(root);
return em.createQuery(cq).getResultList();
If you are using HQL you should use fetch keyword:
select distinct st from Store st left join fetch st.storeServers
It might be a good idea to validate the number of queries generated by Hibernate in your unit tests using in-memory database like H2 and JDBC Sniffer

Related

Spring Boot / JPA / mySQL - many to one relationship creates too many SQL queries

I have a simple spring boot rest app connected with mySQL db and I'm trying to optimize number of queries within simple function:
List<Message> messages = messagesRepository.findBySenderIdOrReceiverIdOrderByTimeDesc(senderId, receiverId);
MessagesRepository:
public interface MessagesRepository extends CrudRepository<Message, Long> {
List<Message> findBySenderIdOrReceiverIdOrderByTimeDesc(Long senderId, Long receiverId);
}
Message:
#Entity
#Table(name="s_messages")
public class Message implements Serializable
{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Transient
private int internalId;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="senderId", referencedColumnName = "id", updatable=false, insertable=false)
private ProfileLite sender;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="receiverId", referencedColumnName = "id", updatable=false, insertable=false)
private ProfileLite receiver;
#Column(columnDefinition="TEXT")
private String message;
private long time;
private MessageStatus status;
}
ProfileLite:
#Entity
#Table(name="s_profiles")
public class ProfileLite implements Comparable<ProfileLite>
{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String nickname;
private String country;
private String thumb;
private Gender gender;
}
After executing method mentioned above, hibernate generates about 40 SQL's (based on 40 profiles) like this:
SQL Log - PasteBin
so first collecting messages and then for each message creates another sql to gather profile.
Is it any possibility to push hibernate to create just one simple sql instead of 40 like: select * from s_messages m join s_profiles s1 on m.sender_id = s1.id join s_profiles s2 m_receiver_id = s2.id ? (pseudo code)
Thanks!
This could be a n + 1 problem.
You can use a JOIN FETCH in your JPA query to fix this.
A "fetch" join allows associations or collections of values to be initialized along with their parent objects using a single select. This is particularly useful in the case of a collection. It effectively overrides the outer join and lazy declarations of the mapping file for associations and collections.
Update your JPA repository like so
public interface MessagesRepository extends CrudRepository<Message, Long> {
#Query("Select m from Message m join fetch m.sender ms join fetch m.receiver mr where ms.id = :senderId or mr.id = :receiverId order by m.time desc")
List<Message> findBySenderIdOrReceiverIdOrderByTimeDesc(Long senderId, Long receiverId);
}
For a more detailed explanation check out this answer.
PS: I havent tested the query.

efficiently loading collection of collections from database in hibernate

I have an web application with hibernate which manages data in multiple languages. Currently basically every request generates a shower of select statements on the languagetranslations. The models are roughly as following:
Data <1-1> Placeholder <1-many> languageTranslation <many-1> language
If I query for all/many Dataobjects, I see lots of single selects which select one languageTranslation for the placeholder. The SQL I optimally would want to generate:
SELECT * FROM data join placeholder join languagetranslation
WHERE data.placeholder_id = placeholder.id
AND languagetranslation.placeholder_id = placeholder.id
AND languagetranslation.language_id = ?
so that I get every data with placeholder with translation in one single call. The languagetranslations have an composite primary key of language_id and placeholder_id.
I have no HBM file, everything is managed with annotations. Modelcode (only relevant sections are shown):
#Entity
public class Data {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, optional = false)
#Fetch(FetchMode.JOIN)
private Placeholder content;
}
public class Placeholder {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "primaryKey.placeholder", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#Fetch(FetchMode.JOIN)
private Set<LanguageTranslation> languageTranslations = new HashSet<>();
}
public class LanguageTranslation {
#EmbeddedId
private LanguageTranslationPK primaryKey = new LanguageTranslationPK();
#Type(type = "org.hibernate.type.StringClobType")
private String text;
}
#Embeddable
public class LanguageTranslationPK {
#ManyToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
private TextPlaceholder textPlaceholder;
#ManyToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
private Language language;
}
public class Language {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
I experimented with FetchType and FetchMode but couldn't generate the behavior I want, it always single selects for single languageTranslations.
I also tried multiple ways to query, criteria based, HQL, and raw SQL. My current raw SQL query is the following:
String sql_query = "select data.*, lt.* from Data as data join languagetranslation as lt on data.content_id = lt.textplaceholder_id";
Query q = getSession().createSQLQuery(sql_query).addEntity("data", Data.class).addJoin("data.content_id", "data.title").addJoin("lt", "data.content.languageTranslations").setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
return q.list();
Am I doing something generally wrong here? How can I convince hibernate to get all entities in one single database call? Or is there some other methods to improve performance in my case (e.g. batch selecting)?
You may create proxy pojo which have your all entity variables with getter setter and constructor. then initialize this constructor in hibernate query so that you just get all needed data from database.
import com.proxy;
class userProxy{
private string name;
private string password;
private string address;
private int pincode;
private byte[] profilePic;
private int age;
public userProxy(string name,string password){
this.name = name;
this.password = password;
}
//Getter and setter of all variable...
}
Then use this constructor to Hibernate query like
select new com.proxy.userProxy(user.name,user.password) from usertable
Am I doing something generally wrong here?
No, you are not. That is how Hibernate works.
How can I convince hibernate to get all entities in one single database call
You have to use HQL or SQL query to do that. You do not need to have HBM file. It can be done through #NamedQueries / #NamedQuery annotation with list method.
There are many samples on Internet as example simple one:
http://www.mkyong.com/hibernate/hibernate-named-query-examples/

play framework 2 ebean #manytoone Column specified twice

I'm running to some problems with ebean (using play framework 2 version 2.2.1)
I have two classes:
my graph class:
public class Graph extends Model {
#Id
#Column(name="id")
private String id;
#Column(name="type")
private String type;
#OneToMany(mappedBy="valGraph", cascade=CascadeType.ALL)
private List<Val> valItems;
and my value class (with Val.graphId foreign key Graph.id):
public class Val extends Model
#Id
#Column(name="valId")
private String valId;
#Id
#Column(name="graphId")
private String graphId;
#Column(name="Key")
private String Key;
#Column(name="Value")
private String Value;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="graphId")
private Graph valGraph;
but when trying to save a new item i get this error:
javax.persistence.PersistenceException: ERROR executing DML bindLog[] error[Column 'graphId' specified twice]
After numerous searchers around the web I found this answer here - thanks to jtal!
Just to summaries the problem:
Using Ebean i have made a #ManyToOne entity that is not implemented in the database in anyway,
even more the join field, in my case
graphId
is a valid field that has values of its own.
when trying to join the column on that field, it will always fail because it creates this sql query:
SELECT
*
FROM
Val;
select
t0.valId c0,
t0.graphId c1,
t0.Key c2,
t0.Value c3,
t0.graphId c4 <---- notice this duplicate
from
graph_val t0
in order to solve this, i tell ebean not to use the second set of properties.
my new ebean element looks like this:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="graphId", insertable = false, updatable = false)
private Graph valGraph;
and it works! =)

Persist an entity that have associated a list of entities that use #idclass

I have an Evaluation entity that has an associated list of EvaluationEvaluator. I need to explicitly create that entity because it required an extra column "STATUS". Before I continue evaluation. I do: evaluation.setEvaluationEvaluator(listEvaluator) where listEvaluator is a list of EvaluationEvaluator type. Then persist(evaluation). When I run this, it does not throw any kind of exception. But in the database, it inserts in the Evaluation table, and not inserted into the EvaluationEvaluator table.
Below my Evaluation entity.
#Entity
public class Evaluation implements Serializable{
#Id
#GeneratedValue
private Long id;
//MORE FIELDS
#OneToMany(mappedBy="evaluation")
private List<EvaluationEvaluator> evaluators;
//CONSTRUCTORS
//GETTER AND SETTERS
}
This is my EvalutionEvaluator Entity:
#Entity
#Table(name= "EVALUATION_EVALUATOR")
#IdClass(EvaluationEvaluatorId.class)
public class EvaluationEvaluator implements Serializable{
#Id
#Column(name="EMPLOYEE_ID", insertable=false , updatable=false)
private Long EmployeeID;
#Id
#Column(name="EVALUATION_ID", insertable=false, updatable=false)
private Long EvaluationID;
#ManyToOne
#JoinColumn(name"EMPLOYEE_ID")
private Employee employee;
#ManyToOne
#JoinColumn(name"EVALUATION_ID")
private Evaluation evaluation;
#NotNull
private String status;
//CONSTRUCTORS
//GETTER AND SETTERS
}
This is my EvaluationEvaluatorId class
public class EvaluationEvaluatorId implements Serializable{
private Long employeeID;
private Long evaluationID;
//CONSTRUCTOR
//GETTER AND SETTERS
}
And finally, this is my EvaluationBean class
#Stateful
#Named
#LocalBean
#ConversationScoped
public class EvaluationBean {
#PersistentContext(type= PersistenceContextType.EXTENDED)
private EntityManager em;
#Inject
Conversation conversation;
private Evaluation evaluation;
//IN MY WEBPAGE I IMPLEMENT PRIMEFACES PICKLIST AND IT REQUIRE DUALIST TO HANDLE
private DualListModel<Employe> evaluators;
private EvaluationEvaluator evaluationEvaluator;
private List<EvaluationEvaluator> listEvaluators;
#Inject
private EmployeeList employeeList;
//GETTER AND SETTERS
public String begin(){
if (conversation.isTransient()){
converstaion.begin();
}
evaluationEvaluator = new EvaluationEvaluator();
listEvaluators = new ArrayList<EvaluationEvaluator>();
evaluation = new Evaluation();
List<Employee> source = employeeList.findAll();
target = new ArrayList<Employee>();
evaluators = new DualListModel<Employee>(source, target);
return "/evalution/evaluationAsig.xhtml"
}
public String save(){
Iterator<Employee> iterator = evaluators.getTarget().iterator();
while (iterator.hasNext()){
EvaluationEvaluator ev = new EvaluationEvaluator();
ev.setEmployee(iterator.next());
listEvaluators.add(ev);
}
evalution.setEvaluationEvaluators(listEvaluators);
if(evaluation.getId()==null){
em.persist(evalution);
} else{
em.merge(evalution);
}
if(!conversation.isTransient()){
convesation.end();
}
return "/evalution/evaluationsAsig.xhtml"
}
}
When I debug my application,apparently everything is correct, but I mentioned above, doesn't persist in EvaluationEvaluator table.
Your #OneToMany association is missing cascading configuration.
Add cascade = CascadeType.ALL or cascade = {CascadeType.PERSIST, CascadeType.MERGE} to the #OneToMany annotation. JPA assumes no cascading by default so you would need to persist each EvaluationEvaluator by yourself explicitely otherwise.
UPDATE
There is another thing wrong with the code - the Ids of EvaluationEvaluators are never assigned. You have a complex key made of two Long columns. Both are marked not insertable nor updatable which tells to JPA that the id is going to be somehow generated on database level and it should not care about it. There is however no sequence configured explicitely in your entity (although it is not necessarily required) and also from your comment:
I did what you recommended but it throws the following exception. "A different object with same identifier was already associated with the session"
I assume that this is not the case and both id column values default to null or zero and are same for all EvaluationEvaluators you are trying to persist. If you'd like the database to generate the id for you automatically use #GeneratedValue - Configure JPA to let PostgreSQL generate the primary key value - here you can find explanation how to do this (the database part is database dependent, this is for PostgreSQL). The most common use case however, is to configure the sequence but let hibernate pick the next value, instructions here - Hibernate sequence on oracle, #GeneratedValue(strategy = GenerationType.AUTO)

Different results fetching results with query api vs. hql

I have the following entity (not exact but gives a general idea):
#Entity
public class WebElement implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private Long id;
#ManyToMany(fetch = FetchType.EAGER, cascade = { CascadeType.ALL })
private Set<CoreElement> coreElements;
private String agent;
// ... omitting const' get/set hashcode equals etc.
}
public class CoreElement implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private Long id;
private String value;
// ... omitting const' get/set hashcode equals etc.
}
My problem is when trying to fetch WebElements using the Criteria API vs. HQL
When executing the following I get an empty list.
getCurrentSession().createCriteria(WebElement.class)
.createCriteria("coreElements").add(
Restrictions.eq("value", value)).list();
But when executing the following HQL I get the correct result.
select distinct we from WebElement we, in(we.coreElements) core
where core.value = :inputValue
Can you help finding what am I doing wrong or different between those calls?(NOTE My preference is to work with the Criteria API instead of HQLs.
In your HQL you are creating an inner join which causes Hibernate to fetch the elements.
In the Criteria Query you can use setFetchMode() using FetchMode.JOIN
Since your query is static, I would recommend using HQL - it's easier to understand.
You are using a Restrictions.eq instead of Restrictions.in()..as you are using the HQL.

Categories

Resources