I'm trying to optmize a query that is currently taking a little longer than expected. The query returns about 11000 entities, but since they are a bit complicated and have nested entities it's somewhat slow. Since I'm not going to modify the entities, I tried setting the query/session to read-only, but it hasn't helped, it still takes just as long, maybe I'm doing something wrong. Below is a simplified code, sorry it's a little messy:
#Entity
#NamedQueries(value = {#NamedQuery(name = "demand.all", query = "select d from Demand d")})
public class Demand {
private Long ID;
private Division division;
private Client client;
private Product product;
private String code;
...
}
#Transactional(readOnly=true)
public List<Demand> getAll() {
SessionImpl sessionImpl = ((SessionImpl)em.getDelegate());
Session session = sessionImpl.getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
try {
sessionImpl.connection().setReadOnly(true);
Query query = session.getNamedQuery("demand.all");
List<Demand> resultList = query.setReadOnly(true).setCacheable(false).setFlushMode(FlushMode.MANUAL).list();
sessionImpl.connection().setReadOnly(false);
tx.commit();
} catch(Exception e) {
resultList = null;
}
session.close();
return resultList;
}
I read that making the query read-only is not enough, so I tried setting the connection and transaction read-only too, but I'm not sure if it's necessary. Anyways, am I doing something wrong? What other way is there to optimize this query?
One way of doing this faster would be fetching the objects in a lazy way or depending on which are necessary and which are not. Like maybe you only need to show 5 columns in a table, instead of every single object in the hierarchy, so you create DTO to get them. If you need more information on one of them.. lets say the user clicks on a row, then you would bring the whole object hierarchy of it..
It may not apply in your case, but its one way of efficiently getting data.
Related
Here are the relevant pieces of the code I inherited. The object "process" is the old process that is passed to the method. The object "newProcess" is what I am replacing it with, using different fields of the user's choosing.
try
{
final EntityManager em = getEntityManager();
em.getTransaction().begin();
JpaProcessDAO pDao = new JpaProcessDAO(em);
Process newProcess = pDao.findById(processId);
newProcess.setName(process.getName());
newProcess.setDataBaseVersion(process.getDataBaseVersion());
newProcess.setNotes(process.getNotes());
newProcess.setReadyForUse(process.getReadyForUse();
newProcess.setSteps(process.getSteps());
em.merge(newProcess); <---- WHERE PROBLEM OCCURS
em.persist(newProcess);
em.getTrasaction().commit();
}
RESULT: Every field that I change is changed in newProcess EXCEPT "Steps". During the merge step in the code, that list goes back to whatever the steps were in the original object "process".
Now this could be because that "Step" is an object itself, not a primitive like all of the other fields I set in "newProcess":
Mapping in Process.java
#OneToMany(mappedBy="process")
private List<Step>
// getter, setter
In Step.java there is a collection of objects, some of which are lists of nonprimitive objects themselves.
Step.java
public class Step implements Serializable {
#Id
#Column(name = "step_id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int stepId;
private String duration;
private String name;
private String notes;
private Integer sort;
#OneToMany(mappedBy="step", cascade=CascadeType.REMOVE)
private List<Constituent> constituents;
#OneToMany(mappedBy="step")
private List<Reference> references;
#ManyToOne
#JoinColumn(name ="process_id")
private Process process;
#OneToMany(mappedBy="step",cascade=CascadeType.REMOVE)
private List<StepEquipment> stepEquipments;
public Step() {
}
// getters/setters
}
Does anybody know what this inherited code I have could possibly do wrong?
ADDITIONS TO CODE ON 11/29:
public T findById(final Integer id) throws CPDPersistenceExceptin {
return findByPrimaryKey(id,templateClass);
}
public T findBYPrimaryKey(Object key, Class<T> clazz) {
T t = getEntityManager().find(clazz,key);
getEntityManager.merge(t);
getEntityManager.refresh(t);
return t; <-------------- newProcess is returned by this statement.
}
newProcess does not have the steps that were in the original process,nor does it have the ProcessCategories that were in process. The Hibernate logs say
that select is going on for process_id, database_version, process_name, process_notes, and process_ready_to_use only in the merge and refresh statements.
You need to synchronize both sides of the association. In your code you're only setting newProcess.setSteps(...), but each Step doesn't set a Process. From here:
However, we still need to have both sides in sync as otherwise, we break the Domain Model relationship consistency, and the entity state transitions are not guaranteed to work unless both sides are properly synchronized.
So in other words, you would need to do something along the lines of:
newProcess.setSteps(process.getSteps());
process.getSteps().forEach(s -> s.setProcess(newProcess));
As in answer from dyslexit told you need to set the Process to each Step.
But in addition you need to have the new Steps persisted and old ones removed. You can do this manually per Step but easier way would be to alter your code a bit.
Mofify the mapping annotation in step like:
#OneToMany(mappedBy = "process", cascade=CascadeType.PERSIST, orphanRemoval=true)
private List<Step> steps;
so tell persist to cascade to Steps also and to remove all Steps that are detached from Process.
Modify the update logic:
// newProcess.setSteps(process.getSteps());
// em.merge(newProcess); <---- WHERE PROBLEM OCCURS
// em.persist(newProcess);
newProcess.getSteps().clear(); // remove old steps
newProcess.getSteps().addAll(process.getSteps()); // add new steps
// You need to set the other side of association also as below
newProcess.getSteps().forEach(s -> s.setProcess(newProcess));
// em.persist(newProcess); // not sure if needed
SO: do not REPLACE the list but instead MODIFY the original list.
ALSO: there might not be a need for any merge/persist operation (and certainly doing both in series is not something that should ever be done). But because you use mystical JpaProcessDAO I can not be sure so check that.
And also see for what those are really used, great explanation here.
I am guessing that entity manager might handle everything just fine - without persist/merge stuff -because I think you already got managed entity when called pDao.findById(processId);, that is why I have commented it out.
Another story is then the mappings you have in your Step class. Those might also need changes to persistence & cascade setting.
As a side note: have also a look at this question how you might have update done easier with ModelMapper.
Simple question here :
If i've got an object with initialized and uninitialized values in it. Is there an easy way to find in my db all the Entities that fit this one with hibernate ? (without listing and checking every variable of the object)
Example :
I got this class :
public class User {
private int id;
private String name;
private String email;
private boolean activ;
}
I would like to be able to do that :
User user1 = new User();
user.setActive() = true;
User user2 = new User();
user.setActive(true);
user.setName("petter")
listUser1 = findAllUser(user1);
listUser2 = findAllUser(user2);
Here listUser1 will contain all the active users and listUser2 will contain all the active user that are named petter.
Thx guys !
Edit/Solution
So my here is my code (i used a class wich is similar at the one of my example).
It work just fine but the problem is that according to Eclipse : "The method createCriteria(Class) from the type SharedSessionContract is deprecated"...
public static List<Personne> findAllPersonne(Personne personne) {
List<Personne> listPersonne;
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("testhibernate0");
EntityManager entityManager = entityManagerFactory.createEntityManager();
Session session = entityManager.unwrap(Session.class);
Example personneExample = Example.create(personne);
Criteria criteria = session.createCriteria(Personne.class).add(personneExample);
listPersonne = criteria.list();
entityManager.close();
return listPersonne;
}
So .. How could i do that in a better way? I've looked into CriteriaQuery but i can't find how to use it with an example.
Yes it exists : the key word for google is "query by exemple" or "qbe".
https://dzone.com/articles/hibernate-query-example-qbe
In general, if an entity instance is already in your Persistence context, you can find it by primary key with EntityManager.find. Otherwise, you can pick up a result from your database by way of JPQL or native querying.
For your particular use case, it sounds like a querying solution would be the best fit; use one of the linked query creation methods from your entity, then use the Query.getResultList() method to pick up a list of objects that match the query criteria.
QueryByExample is also a good and valid solution, as Mr_Thorynque indicates, but as the article he linked mentions, that functionality is specific to certain JPA providers (Hibernate among them) and not JPA provider agnostic.
Assume I have a model like following
class Chest {
public Id id;
public List<Drawer> drawers;
public Price price;
}
class Drawer {
public Id id;
public Price price;
}
And a JOOQ query to fetch a Chest object with its Drawers:
dsl.selectFrom(CHEST.join(DRAWERS).onKey()).where(CHEST.ID.eq(1)).fetch()
What is the best way to construct the Chest object from the result of the query above?
Thanks.
In general, using JOIN to materialise object graphs won't really work well, as you're denormalising your database entities into a table (with duplicates) before you try to normalise the data again in a mapping algorithm. JPA hides these things from you by offering an alternative query language that doesn't expose so many SQL features.
In your particular case, however, you can get this to run via the jOOQ API by using the Result.intoGroups() methods. Thus:
Map<Record, Result<Record>> result =
dsl.selectFrom(...).fetch().intoGroups(CHEST.fields());
List<Chest> list = new ArrayList<>();
for (Entry<Record, Result<Record>> entry : result.entrySet()) {
Record chest = entry.getKey();
Result<Record> drawers = entry.getValue();
list.add(new Chest(
chest.into(Id.class), // These into(Class<?>) methods assume that you
drawers.into(Drawer.class) // want to use jOOQ's DefaultRecordMapper
));
}
The above algorithm is probably incomplete, or not exactly what you need. But it'll give you a general idea of what's possible out-of-the-box via jOOQ API.
I am kind of new to JPA and have read a bit about second level caching in JPA. And I think it should apply correctly to my scenario.
I have a table (say A) whose content will never change unless a new release is applied.
I need to query some data from the database : for that I have a JPQL working correctly and that uses a join between table A and an additional table (say B).
Since the contents of table A never change through the life of application, I could essentially mark this table A as Cacheable and reuse the content from Cache - rather than going to the database for that.
I have read about #NamedQuery that enables a one time JPQL to SQL translation for the life of the application. And it is not what I am looking for.
I want to know how should I go about using the Cacheable property for my purpose.
This is what I have done so far :
Marked tableA as cacheable -
#Entity
#Cacheable
#Table(name = "TableA")
public class Table{
#Id
#NotNull
#Column(updatable = false)
private String uuid;
#NotNull
#Size(min = 1)
private String description;
.
.
.
}
2. There is a DAO that does a find using the JPQL -
public Collection findAll(String description) {
final Cache cache = entityManager.getEntityManagerFactory().getCache();
if (cache.contains(TableA.class, "abc")) {
System.out.println("cached");
} else {
System.out.println("not cached");
}
final Query query = entityManager
.createQuery("Select distinct A from TableA A, IN(A.TableB) B where A.description = :description"); //$NON-NLS-1$
query.setParameter("description", description); //$NON-NLS-1$
return query.getResultList();
}
</code>
Can I take advantage of using Cacheable property in my scenario. If so, then could you suggest how?
Also, "not cached" gets printed always no matter what the value of string I use (from table) instead of "abc".
Appreciate your help. Thanks
I am using JPA on top of App Engine (I am quite new to both) and I am currently facing a behaviour I do not understand.
Each time I refresh the page, the order of the fetched items changes.
Here is the code snippet:
Set<Cast> results = new HashSet<Cast>();
EntityManager entityManager = entityManagerFactory.createEntityManager();
Query query = entityManager.createQuery(FIND_ALL_SORTED_BY_DESCENDING_BROADCAST_DATE);
#SuppressWarnings("unchecked")
List<Cast> casts = query.getResultList();
for (Cast cast : casts) {
if (verifySecondaryFields(cast)) {
results.add(synchronizeTechnicalFields(cast));
}
}
entityManager.close();
return Collections.unmodifiableSet(results);
where FIND_ALL_SORTED_BY_DESCENDING_BROADCAST_DATE actually is SELECT cast FROM Cast cast ORDER BY cast.broadcastDate DESC.
entityManagerFactory is an autowired member of my repository class.
The thing is the ORDER BY clause seems to be ignored and the results show up randomly. Can you spot what is wrong?
Sets don't preserve order. Lists do. Try List<Cast> = new ArrayList<Cast>(); and carry on from there.