JPA: caching queries - java

I'm using JPA to load and persist entities in my Java EE-based web application. Hibernate is used as an implementation of JPA, but I don't use Hibernate-specific features and only work with pure JPA.
Here is some DAO class, notice getOrders method:
class OrderDao {
EntityManager em;
List getOrders(Long customerId) {
Query q = em.createQuery(
"SELECT o FROM Order o WHERE o.customerId = :customerId");
q.setParameter("customerId", customerId);
return q.getResultList();
}
}
Method is pretty simple but it has a big drawback. Each time the method is called following actions are performed somewhere within JPA implementation:
JPQL expression is parsed and compiled to SQL.
Either Statement or PreparedStatement instance is created and initialized.
Statement instance is filled with parameters and executed.
I believe that steps 1 and 2 of above should be implemented once per application lifetime. But how to make it? In other words, I need that Query instances to be cached.
Of course I can implement such a cache on my side. But wait, I am using modern powerful ORM's! Didn't they already made this for me?
Notice that I'm not mentioning something like Hibernate query cache which caches result of queries. Here I'd like to execute my queries a bit more quickly.

Use statically defined named queries. They are more efficient because the JPA persistence provider can translate the JP QL string to SQL once at application startup time, as opposed to every time the query is executed, and are recommended in particular for queries that are executed frequently.
A named query is defined using the #NamedQuery annotation that is typically used on the entity class of the result. In your case, on the Order entity:
#Entity
#NamedQueries({
#NamedQuery(name="Order.findAll",
query="SELECT o FROM Order o"),
#NamedQuery(name="Order.findByPrimaryKey",
query="SELECT o FROM Order o WHERE o.id = :id"),
#NamedQuery(name="Order.findByCustomerId",
query="SELECT o FROM Order o WHERE o.customerId = :customerId")
})
public class Order implements Serializable {
...
}
It is also recommended to prefix named queries with the entity name (to have some kind of name space and avoid collisions).
And then in the DAO:
class OrderDao {
EntityManager em;
List getOrders(Long customerId) {
return em.createNamedQuery("Order.findByCustomerId")
.setParameter("customerId", customerId);
.getResultList();
}
}
PS: I reused the query you suggested as example but it's somehow weird to have the customerId on the Order, I would expect a Customer instead.
References
JPA 1.0 Specification
Section 3.6.4 "Named Queries"

There is a query plan cache in Hibernate. So the HQL is not parsed every time the DAO is called (so #1 really occurs only once in your application life-time). It's QueryPlanCache. It's not heavily documented, as it "just works". But you can find more info here.

NamedQueries is the concept you're looking for.

JPA 2.1, section "3.1.1 EntityManager Interface":
The Query, TypedQuery, StoredProcedureQuery, CriteriaBuilder,
Metamodel, and EntityTransaction objects obtained from an entity
manager are valid while that entity manager is open.
The lesson to take home from this quote is that the enlisted query types can only be cached for as long as the entity manager remains open - which we have no saying about for container-managed entity managers.
Three solutions come to mind. 1) Named queries as others have pointed out. 2) Cache a CriteriaQuery instead and hopefully the provider can toss in some kind of optimizations out of it. 3) Use an application-managed entity manager (that remains open).
Cache a CriteriaQuery
#Stateless
public class OrderRepository
{
#PersistenceUnit
EntityManagerFactory emf;
#PersistenceContext
EntityManager em;
private CriteriaQuery<Order> query;
private Parameter<Long> param;
#PostConstruct
private void constructQuery() {
CriteriaBuilder b = emf.getCriteriaBuilder();
query = b.createQuery(Order.class);
param = b.parameter(long.class);
...
}
public List<Order> findByCustomerKey(long key) {
return em.createQuery(query)
.setParameter(param, key)
.getResultList();
}
}
Use an application-managed entity manager
#Stateless
public class OrderRepository
{
#PersistenceUnit
EntityManagerFactory emf;
private EntityManager em;
private TypedQuery<Order> query;
#PostConstruct
private void initialize() {
em = emf.createEntityManager();
query = em.createQuery("SELECT o FROM Order o WHERE o.id = ?1", Order.class);
}
public List<Order> findByCustomerKey(long key) {
try {
return query.setParameter(1, key)
.getResultList();
}
finally {
em.clear(); // returned entities are detached
}
}
#PreDestroy
private void closeEntityManager() {
em.close();
}
}

What you want is a NamedQuery. On your Order entity you put:
#NamedQueries({
#NamedQuery( name = "getOrderByCustomerId", query = "SELECT o FROM Order o WHERE o.customerId = :customerId")
})
Then in your DAO use em.createNamedQuery("getOrderByCustomerId") instead of recreating the query.

You can't prepare queries that are not named. That is the main reason you should try to have named queries rather than simple queries inside your code.
Also, named queries can be cached while simple queries inside your java code cannot. Of course this is an optional feature and is enabled using hints on your named query.

Related

Delete data in Java Batch Job (JSR352)

While I have Java Batch jobs that read data, process it and store it in other places in the database, now I need a step to actually remove data from the database. All I need to run is a delete query via JPA.
The chunk based Reader/Processor/Writer pattern does not make sense here. But the Batchlet alternative is giving me a headache either. What did I do?
I created a Batchlet that gets invoked via CDI. At that moment it is easy to inject my JPA EntityManager. What is not easy is to run the update query. Code looks like this:
package ...;
import javax.batch.api.BatchProperty;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
#Named("CleanerBatchlet")
public class CleanerBatchlet extends AbstractBatchlet {
public static final Logger log = LogManager.getLogger(CleanerBatchlet.class);
#PersistenceContext(unitName = "...")
private EntityManager entityManager;
#Inject
#BatchProperty(name = "technologyIds")
private String technologyIds;
private void clearQueue(long technologyId) {
//EntityManager entityManager = ...getEntityManager();
//entityManager.getTransaction().begin();
Query q = entityManager.createQuery("delete from Record r where r.technologyId=:technologyId");
q.setParameter("technologyId", technologyId);
int count = q.executeUpdate();
//entityManager.getTransaction().commit();
log.debug("Deleted {} entries from queue {}", count, technologyId);
//entityManager.close();
}
#Override
public String doProcess() throws Exception {
log.debug("doProcess()");
out.println("technologyIds=" + technologyIds);
log.info("technologyIds=" + technologyIds);
try {
String[] parts = technologyIds.split(",");
for (String part: parts) {
long technologyId = Long.parseLong(part);
clearQueue(technologyId);
}
} catch (NullPointerException | NumberFormatException e) {
throw new IllegalStateException("technologyIds must be set to a string of comma-separated numbers.", e);
}
return "COMPLETED";
}
}
As you can see some lines are commented out - these are the ones I am experimenting with.
So if I run the code as-is, I get an exception telling me that the update query requires a transaction. This is regardless of which of the two persistence units in my project I use (one is configured for JTA, the other is not).
javax.persistence.TransactionRequiredException: Executing an update/delete query
It also does not matter whether I uncomment the transaction handling code begin/commit. I still get the same error that a transaction is required to run the update query.
Even when I try to circumvent CDI and JTA completely by creating my own EntityManager via the Persistence API (and close it afterwards, respectively) I do get the very same exception.
So how can I run this delete query or other update queryies from within the batch job?
I'd suggest using plain jdbc to run this delete query, with either auto commit or manual transaction commit.
During the batchlet processing, the incoming transaction is suspended. So the entity manager does not have a transaction context.
Ultimately I made it work by following this tutorial: https://dzone.com/articles/resource-local-vs-jta-transaction-types-and-payara
and going for the Classic RESOURCE_LOCAL Application pattern.
It involves injecting the nonJTA EntityManagerFactory, using that to create the entitymanager and closing it after use. Of course the transaction has to be managed manually but after all now it works.
The essential excerpt of my code looke like this:
#PersistenceUnit(unitName = "...")
private EntityManagerFactory emf;
#Inject
#BatchProperty(name = "technologyIds")
private String technologyIds;
private void clearQueue(long technologyId) {
EntityManager entityManager = emf.createEntityManager();
entityManager.getTransaction().begin();
Query q = entityManager.createQuery("delete from Record r where r.technologyId=:technologyId");
q.setParameter("technologyId", technologyId);
q.executeUpdate();
entityManager.getTransaction().commit();
entityManager.close();
}

Prevent hibernate entity changes from being persisted

I am updating my application from Spring Boot 1.4.5 / Hibernate 4.3.5 to Spring Boot 2.0.9 / Hibernate 5.2.18 and code that used to work in the previous configuration is no longer working.
The scenario is as follows:
Start a transaction by entering a method annotated with #Transactional
Hydrate the entity
Change the entity
Make another query
Detect a problem. As a result of this problem, determine that changes should not persist.
Evict the entity
Exit the method / transaction
With Hibernate 4.3.5, calling entityManager.detach() would prevent the changes from being persisted. However, with Hibernate 5.2.18, I'm finding that changes are persisted even with this call. I have also tried to evict() from the session and I have tried to clear() all entities from the session (just to see what would happen).
So I ask - is it possible to discard entity changes in Hibernate 5.2.18 the way that I was able to do in Hibernate 4.3.5?
The relevant code is below...
#Entity
public class Agreement {
private Long agreementId;
private Integer agreementStateId;
#Id
#Column(name = "agreement_id")
public Long getAgreementId() {
return agreementId;
}
public void setAgreementId(Long agreementId) {
this.agreementId = agreementId;
}
#Basic
#Column(name = "agreement_state_id", nullable = false)
public Integer getAgreementStateId() {
return agreementStateId;
}
public void setAgreementStateId(Integer agreementStateId) {
this.agreementStateId = agreementStateId;
}
}
#Component
public class Repo1 {
#PersistenceContext(unitName = "rights")
private EntityManager entityManager;
public void evict(Object entity) {
entityManager.detach(entity);
}
public Agreement getAgreement(Long agreementId) {
// Code to get entity is here.
// Agreement with an agreementStateId of 5 is returned.
}
public void anotherQuery() {
// Code to make another query is here.
}
}
#Component
public class Service1 {
#Autowired
Repo1 repo;
#Transactional
public void doSomething() {
Agreement agreement = repo.getAgreement(1L);
// Change agreementStateId. Very simple for purposes of example.
agreement.setAgreementStateId(100);
// Make another query
repo.anotherQuery();
// Detect a problem here. Simplified for purposes of example.
if (agreement.getAgreementStateId() == 100) {
repo.evict(agreement);
}
}
}
I have found the problem and it has nothing to do with evict(). It turns out that an additional query was causing the session to flush prior to the evict() call.
In general, the application uses QueryDSL to make queries. Queries made in this way did not result in the session flushing prior to making a query. However in this case, the query was created via Session.createSQLQuery(). This uses the FlushMode already assigned to the session which was FlushMode.AUTO.
I was able to prevent the flush by calling setHibernateFlushMode(FlushMode.COMMIT) on the query prior to making the query. This causes the session FlushMode to temporarily change until after the query has been run. After that, the evict() call worked as expected.

JPA generic repository, not working for a certain name

I have made a Jax-RS endpoint, with a JPA integration, where I try to make a query, based on a generic name, to create a query, to get data from the database.
#Override public Set<E> get() {
EntityManager em = emf.createEntityManager();
List<E> results = null;
try {
results = em.createQuery("SELECT e FROM " + entityClass.getSimpleName() + " e", entityClass)
.getResultList();
} finally {
em.close();
return new HashSet<E>(results);
}
}
when I make an instance of my repository, I specify the class name and the primary key in the SQL-database (usually an integer)
public class BaseRepository<E, PK> implements CRUDOperations<E, PK> {
private Class<E> entityClass;
protected EntityManagerFactory emf;
}
I tried this out for a dummy class, with just a string, in it works fine,
I tested it in the debugger.
however, when I try to do it for an actual class I created, I just get null back (not even an empty set=
lastly, I checked the database, and the tables have the same name in the database, and that matched.
The reason was that I did not add a no-argument constructor to the entity class, and therefore it failed to fetch a set of entities from the database.

#Transactional and Time taken for execution

I'm developing a java web application that uses spring like application container. Now while I'm was using the transaction support to spring, I noticed that the time processing of the my annotated method is doubled. Try to descibe the method in a better way:
#Service
public class MyServiceImpl implements MyService{
#Autowired
UtilService utilService;
#Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void loadContracts(File fileToProcess,UtilDTO dto){
List<MyObject> objects = utilService.readSomethings("xxx","yyy")
//I modify Them
//I save or update them
}
}
#Service
public class UtilServiceImpl implements UtilService{
#PersistenceContext
EntityManager entityManager;
public List<MyObject> readSomethings(String p1,String p2){
String queryString = "from MyObject o where o.param1 = :param1 "
+ " and o.param2 = :param2 ";
Query q = entityManager.createQuery(queryString);
q.setParameter("param1", p1);
q.setParameter("param2", p2);
return q.getResultList();
}
}
For example:
The method readSomething is too late while If I remove the annotation its time processing improves.
Why is there this difference?
The performance is likely due to you creating a new transaction and suspending the existing transactions every time that method is invoked.
From the documentation on Propagation.REQUIRES_NEW, emphasis mine:
Create a new transaction, and suspend the current transaction if one exists. Analogous to the EJB transaction attribute of the same name.
Unless you have specific requirements about creating a new transaction for this method, I would recommend letting it fall through to default behavior - that is, Propagation.REQUIRED.

Reusing Persistence Context in JPA

i was just working on #EmbededId code, i want to do an auto increment before the entity is persisted, this is want to do without use of #GeneratedValue and an identity column,
below is the table with composite id,
create table TBL_EMPLOYEE_002(
ID integer,
COUNTRY varchar(50),
NAME varchar(50),
constraint PK_EMP_00240 primary key(ID,COUNTRY)
)
this is the code for Entity mapping,
#Entity
#Table(name="TBL_EMPLOYEE_002")
public class EmployeeEntitySix implements Serializable{
// contructor's
#EmbeddedId
private EmployeeIdTwo id;
#Column(name="NAME")
private String employeeName;
// getters and setter's
#PrePersist
public void incId(){
EntityManager em = null;
Query q = null;
EntityManagerFactory emf = null;
try{
emf = Persistence.createEntityManagerFactory("forPractise");
em = emf.createEntityManager();
q = em.createQuery("select max(e.id.employeeId) from EmployeeEntitySix e");
List list = q.getResultList();
Integer i = (list != null && list.size() > 0) ? Integer.valueOf(list.get(0).toString()) : 0;
this.getId().setEmployeeId(++i);
}catch(Exception e){
System.out.println("EXCETION WHILE INCREASING COUNTER...");
e.printStackTrace();
}finally{
if(em != null && em.isOpen()){
em.close();
}
if(getEmf() != null && getEmf().isOpen()){
getEmf().close();
}
}
}
This is the composite id mapping,
#Embeddable
public class EmployeeIdTwo implements Serializable{
#Column(name="ID")
private Integer employeeId;
#Column(name="COUNTRY",length=50)
private String empCountry;
// getters and setters
}
this code is of my main method, this main method is in some other class,
public static void main(String [] args){
EntityManagerFactory emf = null;
EntityManager em = null;
EntityTransaction tx = null;
try{
emf = Persistence.createEntityManagerFactory("forPractise");
em = emf.createEntityManager();
tx = em.getTransaction();
tx.begin();
EmployeeEntitySix employee = new EmployeeEntitySix(new EmployeeIdTwo("ZIMBABWE"), "Henry Olanga");
em.persist(employee);
....
}
Now the above code runs fine,
whenever i persist the entity "EmployeeEntitySix", the method annotated with #PerPersist runs, which will first fetch the max id, increments its, set it into the id in the embeded entity and persist the entity.
Now my question is,
I am creating EntityManagerFactory twice,
first in the main method,
second time in the #PrePersist method in entity EmployeeEntitySix. So whether i can use the first Entitymanagerfactory created in main method in the entity EmployeeEntitySix while pre-persist, or else whether i can reuse the entitymanager created in first time in main method in the #PrePersist method in entity.
Just for information, I am using plain java environment, I am not using a Java EE container.
Hibernate by default tries to persist all fields of an entity class or embedded id, including the field emf, but it does not know how to persist a field of the type EntityManagerFactory.
Of course it does not make sense to persist an EntityManagerFactory. You could mark the field as #Transient to prevent it from being persisted, but then you are just going to face different problems.
The injection of an EntityManagerFactory with a #PersistenceUnit annotation only works on CDI Beans and EJBs in applications that run on a Java EE-compliant application server. As you are using a main method, I assume that your example is a simple JSE program.
Furthermore you should not access EntityManagers in lifecycle callback methods such as #PrePersist. A quote from the JPA Specification (JSR 317: JavaTM Persistence API, Version 2.0):
In general, the lifecycle method of a portable application should not invoke EntityManager
or Query operations, access other entity instances, or modify relationships within the
same persistence context. A lifecycle callback method may modify the non-relationship
state of the entity on which it is invoked.
I suggest that you keep the EntityManagerFactory out of your embedded id class and also get rid of the incId-Method. Instead you could execute the query to determine the new employeeId in your main method, before calling persist. This works fine as long as only one instance of the program works with the database. When there are multiple programs trying to insert new employees there could be race conditions where the two programs try to insert the same id.
In order to prevent this you can use a database sequence to generate the employeeId, with the annotations #GeneratedValue and #SequenceGenerator. You find more information about id generation here: http://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing#Sequencing

Categories