I have some uncatchable bug in my work.
For example, I have code that looks like this:
#Entity
public class Message {
#Id
#GeneratedValue(strategy = SEQUENCE, generator = "message_generator")
private long id;
private long massMessageId;
}
public class MessageDTO {
public final long id;
public final long massMessageId;
}
#Transactional
#Service
public class ExtendedMessageService {
private MessageService messageService;
public MessageDTO createMessage(MessageCreateDTO createDTO) {
var messageDTO = messageService.create();
return messageService.linkMassMessage(messageDTO.id, createDTO.massMessageId);
}
}
#Transactional
#Service
public class MessageService {
private final MessageRepository repository;
private final ObjectMapper mapper;
public MessageDTO create() {
var message = new Message();
var savedMessage = repository.save(message);
return mapper.map(savedMessage, MessageDTO.class);
}
public MessageDTO linkMassMessage(long messageId, long massMessageId) {
var message = repository.findById(messageId)
.orElseThrow(() -> new ObjectNotFoundException("Message with id " + id + " was not found"));
return mapper.map(repository.save(message.setMassMessageId(massMessageId)), MessageDTO.class);
}
}
What will happen in this situation? I have some bugs, when repository.findById(id) can't find entity and throws exception.
And i have no reason, why this bug is only on prod (i tried to repeat it on dev and nothing succeeded)
And when i try to find the reason of it, i get a question:
"Can i save entity and get it in one transaction in Spring?"
How saving works
repository.save() doesn't save anything to database, this method puts entity to the session (persistent context) in memory.
flush step — on this step actual SQL insert happens. It can be invoked manually repository.saveAndFlush(), repository.flush(). Hibernate can do flush in the background, before operations that can use saved to the database value, like JPQL statements.
Also flush happens when the end of #Transactional boundary is reached.
What can be an issue
You are using incorrect method. This method from the old version of Spring data and it doesn't perform search in the database. You have to use findById() method instead.
Hibernate: findById vs getbyId
The most simple way, if you want to use id after save — flush the data immediately.
Entity entity = new Entity(some_information);
repository.saveAndFlush(entity);
Entity findedEntity = repository.findById(entity.getId())
.orElseThrow(() -> new RuntimeException("Can't find id=" + entity.getId()));
Hibernate will not necessary perform SQL select to get findedEntity. It can get it from the session, if it happens in the same #Transactional boundaries.
So if the above code resides in the method with #Transaction SQL will not performed. if there is not #Transaction SQL will be performed.
About this question
"Can Spring or Hibernate find not flushed entity in transaction context? Or there are some other ways to do it?"
Hibernate can't find not flushed entity. if id is autogenerated, Hibernate needs to perform SQL INSERT (flush) to get the id from a database. Another option to set up an id manually. Probably in this case it will be possible to get an entity from the persistent context.
Let's start with this entity:
#Entity
public class MyEntity {
...
#Column(length = 80)
private String description;
#Column(name = "enum_column", precision = 18)
#Convert(converter = EnumColumnConverter.class)
private MyEnum enumColumn;
...
}
Here, you see two columns that are nullable (in my entity and in the database). The converter replaces the enum with a Long value in the database. A repository class is defined accordingly:
#Repository
public interface MyEntityRepository extends JpaRepository<MyEntity, Long> {}
A DTO is defined from a service package:
public class MyEntityDto {
...
private String description;
private MyEnum enumColumn;
...
}
Mapping between DTOs and entities is done using Dozer. A DTO is modified from a Java FX UI. A service has been defined between UI and persistence to save modified entities.
#Service
#Transactional
public class MyEntityService {
#Autowired MyEntityRepository myEntityRepository;
...
public List<MyEntityDto> save(List<MyEntityDto> dtosToSave) {
List<MyEntityDto> results = Collections.emptyList();
if (dtosToSave != null && !dtosToSave.empty()) {
Iterable<MyEntity> entities = convertDtosWithDozer(dtosToSave);
List<MyEntity> savedEntities = myEntityRepository.saveAll(entities);
results = convertEntitiesWithDozer(savedEntities);
}
return results;
}
From the UI, I modify an existing row where both descriptionand enumColumn are not null. Both values are set to null.
The problem is that none of them is set to null in the database. In the logs, the update request generated by Hibernate does not include these columns. When I debug the code, these columns are null in dtosToSave, entities, savedEntities and results.
I created a unit test for MyEntityRepository where I save an entity with non null description and entityColumn. I reload the entity from the database using the repository to be sure these columns are not null. I set them to null, save the entity, and load it back from the database. Now both columns are indeed null, which is what I've been expecting.
My question: what am I missing here? Why the repository does not save null columns? If I set any non null values, it works perfectly.
Thanks in advance.
UPDATE: could my problem be related to this? Jpa Repository save() doesn't update existing data
You convert your dtos to entities via dozer but as this point entities are still in detached-state.... to update existing entities you first need to load them through database via your repository. Something like repository.findById(Id id);
Then you will get entity in "attached" state and so state transitions(update on fields) will be applied.
During the save() all your entity state transitions will be translated to corresponding DML and your update should work now.
And regarding this statement
I reload the entity from the database using the repository to be sure these columns are not null. I set them to null, save the entity, and load it back from the database. Now both columns are indeed null, which is what I've been expecting.
As you said you reload entity from the database so it works
I get the following exception:
Exception in thread "main" org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:167)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:215)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:190)
at sei.persistence.wf.entities.Element_$$_jvstc68_47.getNote(Element_$$_jvstc68_47.java)
at JSON_to_XML.createBpmnRepresantation(JSON_to_XML.java:139)
at JSON_to_XML.main(JSON_to_XML.java:84)
when I try to call from main the following lines:
Model subProcessModel = getModelByModelGroup(1112);
System.out.println(subProcessModel.getElement().getNote());
I implemented the getModelByModelGroup(int modelgroupid) method firstly like this :
public static Model getModelByModelGroup(int modelGroupId, boolean openTransaction) {
Session session = SessionFactoryHelper.getSessionFactory().getCurrentSession();
Transaction tx = null;
if (openTransaction) {
tx = session.getTransaction();
}
String responseMessage = "";
try {
if (openTransaction) {
tx.begin();
}
Query query = session.createQuery("from Model where modelGroup.id = :modelGroupId");
query.setParameter("modelGroupId", modelGroupId);
List<Model> modelList = (List<Model>)query.list();
Model model = null;
for (Model m : modelList) {
if (m.getModelType().getId() == 3) {
model = m;
break;
}
}
if (model == null) {
Object[] arrModels = modelList.toArray();
if (arrModels.length == 0) {
throw new Exception("Non esiste ");
}
model = (Model)arrModels[0];
}
if (openTransaction) {
tx.commit();
}
return model;
} catch(Exception ex) {
if (openTransaction) {
tx.rollback();
}
ex.printStackTrace();
if (responseMessage.compareTo("") == 0) {
responseMessage = "Error" + ex.getMessage();
}
return null;
}
}
and got the exception. Then a friend suggested me to always test the session and get the current session to avoid this error. So I did this:
public static Model getModelByModelGroup(int modelGroupId) {
Session session = null;
boolean openSession = session == null;
Transaction tx = null;
if (openSession) {
session = SessionFactoryHelper.getSessionFactory().getCurrentSession();
tx = session.getTransaction();
}
String responseMessage = "";
try {
if (openSession) {
tx.begin();
}
Query query = session.createQuery("from Model where modelGroup.id = :modelGroupId");
query.setParameter("modelGroupId", modelGroupId);
List<Model> modelList = (List<Model>)query.list();
Model model = null;
for (Model m : modelList) {
if (m.getModelType().getId() == 3) {
model = m;
break;
}
}
if (model == null) {
Object[] arrModels = modelList.toArray();
if (arrModels.length == 0) {
throw new RuntimeException("Non esiste");
}
model = (Model)arrModels[0];
if (openSession) {
tx.commit();
}
return model;
} catch(RuntimeException ex) {
if (openSession) {
tx.rollback();
}
ex.printStackTrace();
if (responseMessage.compareTo("") == 0) {
responseMessage = "Error" + ex.getMessage();
}
return null;
}
}
}
but still, get the same error.
I have been reading a lot for this error and found some possible solutions. One of them was to set lazyLoad to false but I am not allowed to do this that's why I was suggested to control the session
If you using Spring mark the class as #Transactional, then Spring will handle session management.
#Transactional
public class MyClass {
...
}
By using #Transactional, many important aspects such as transaction propagation are handled automatically. In this case if another transactional method is called the method will have the option of joining the ongoing transaction avoiding the "no session" exception.
WARNING If you do use #Transactional, please be aware of the resulting behavior. See this article for common pitfalls. For example, updates to entities are persisted even if you don't explicitly call save
You can try to set
<property name="hibernate.enable_lazy_load_no_trans">true</property>
in hibernate.cfg.xml or persistence.xml
The problem to keep in mind with this property are well explained here
What is wrong here is that your session management configuration is set to close session when you commit transaction. Check if you have something like:
<property name="current_session_context_class">thread</property>
in your configuration.
In order to overcome this problem you could change the configuration of session factory or open another session and only then ask for those lazy loaded objects. But what I would suggest here is to initialize this lazy collection in getModelByModelGroup itself and call:
Hibernate.initialize(subProcessModel.getElement());
when you are still in active session.
And one last thing. A friendly advice. You have something like this in your method:
for (Model m : modelList) {
if (m.getModelType().getId() == 3) {
model = m;
break;
}
}
Please insted of this code just filter those models with type id equal to 3 in the query statement just couple of lines above.
Some more reading:
session factory configuration
problem with closed session
The best way to handle the LazyInitializationException is to use the JOIN FETCH directive:
Query query = session.createQuery("""
select m
from Model m
join fetch m.modelType
where modelGroup.id = :modelGroupId
"""
);
Anyway, DO NOT use the following Anti-Patterns as suggested by some of the answers:
Open Session in View
hibernate.enable_lazy_load_no_trans
Sometimes, a DTO projection is a better choice than fetching entities, and this way, you won't get any LazyInitializationException.
if you use spring data jpa , spring boot you can add this line in application.properties
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
I was getting the same error for a one to many relationships for below annotation.
#OneToMany(mappedBy="department", cascade = CascadeType.ALL)
Changed as below after adding fetch=FetchType.EAGER, it worked for me.
#OneToMany(mappedBy="department", cascade = CascadeType.ALL, fetch=FetchType.EAGER)
This exception because of when you call session.getEntityById(), the session will be closed. So you need to re-attach the entity to the session. Or Easy solution is just configure default-lazy="false" to your entity.hbm.xml or if you are using annotations just add #Proxy(lazy=false) to your entity class.
I encountered the same issue. I think another way to fix this is that you can change the query to join fetch your Element from Model as follows:
Query query = session.createQuery("from Model m join fetch m.element where modelGroup.id = :modelGroupId")
This means that the object which you are trying to access is not loaded, so write a query that makes a join fetch of the object which you are trying to access.
Eg:
If you are trying to get ObjectB from ObjectA where ObjectB is a foreign key in ObjectA.
Query :
SELECT objA FROM ObjectA obj JOIN FETCH obj.objectB objB
Faced the same Exception in different use case.
Use Case : Try to read data from DB with DTO projection.
Solution: Use get method instead of load.
Generic Operation
public class HibernateTemplate {
public static Object loadObject(Class<?> cls, Serializable s) {
Object o = null;
Transaction tx = null;
try {
Session session = HibernateUtil.getSessionFactory().openSession();
tx = session.beginTransaction();
o = session.load(cls, s); /*change load to get*/
tx.commit();
session.close();
} catch (Exception e) {
e.printStackTrace();
}
return o;
}
}
Persistence Class
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Id")
private int customerId;
#Column(name = "Name")
private String customerName;
#Column(name = "City")
private String city;
//constructors , setters and getters
}
CustomerDAO interface
public interface CustomerDAO
{
public CustomerTO getCustomerById(int cid);
}
Entity Transfer Object Class
public class CustomerTO {
private int customerId;
private String customerName;
private String city;
//constructors , setters and getters
}
Factory Class
public class DAOFactory {
static CustomerDAO customerDAO;
static {
customerDAO = new HibernateCustomerDAO();
}
public static CustomerDAO getCustomerDAO() {
return customerDAO;
}
}
Entity specific DAO
public class HibernateCustomerDAO implements CustomerDAO {
#Override
public CustomerTO getCustomerById(int cid) {
Customer cust = (Customer) HibernateTemplate.loadObject(Customer.class, cid);
CustomerTO cto = new CustomerTO(cust.getCustomerId(), cust.getCustomerName(), cust.getCity());
return cto;
}
}
Retrieving data: Test Class
CustomerDAO cdao = DAOFactory.getCustomerDAO();
CustomerTO c1 = cdao.getCustomerById(2);
System.out.println("CustomerName -> " + c1.getCustomerName() + " ,CustomerCity -> " + c1.getCity());
Present Data
Query and output generated by Hibernate System
Hibernate: select customer0_.Id as Id1_0_0_, customer0_.City as City2_0_0_, customer0_.Name as Name3_0_0_ from CustomerLab31 customer0_ where customer0_.Id=?
CustomerName -> Cody ,CustomerCity -> LA
This means you are using JPA or hibernate in your code and performing modifying operation on DB without making the business logic transaction.
So simple solution for this is mark your piece of code #Transactional
There are several good answers here that handle this error in a broad scope. I ran into a specific situation with Spring Security which had a quick, although probably not optimal, fix.
During user authorization (immediately after logging in and passing authentication) I was testing a user entity for a specific authority in a custom class that extends SimpleUrlAuthenticationSuccessHandler.
My user entity implements UserDetails and has a Set of lazy loaded Roles which threw the "org.hibernate.LazyInitializationException - could not initialize proxy - no Session" exception. Changing that Set from "fetch=FetchType.LAZY" to "fetch=FetchType.EAGER" fixed this for me.
If you are using JPQL, use JOIN FETCH is the easiest way:
http://www.objectdb.com/java/jpa/query/jpql/from#LEFT_OUTER_INNER_JOIN_FETCH_
In Spring Application Just Add
#Transactional(readOnly = true)
on your Function.
Remind that import spring Transactional annotation
import org.springframework.transaction.annotation.Transactional;
Use #NamedEntityGraph. Eagar fetch will deteriorate the performance. Refer https://thorben-janssen.com/lazyinitializationexception/ for in-depth explanation.
If you are using Grail's Framework, it's simple to resolve lazy initialization exception by using Lazy keyword on specific field in Domain Class.
For-example:
class Book {
static belongsTo = [author: Author]
static mapping = {
author lazy: false
}
}
Find further information here
In my case a misplaced session.clear() was causing this problem.
springBootVersion = '2.6.7'
hibernate = 5.6.8.Final'
For me i get the error in:
MyEntity myEntity = myEntityRepository.getById(id);
I change to this:
MyEntity myEntity = myEntityRepository.findById(id).orElse(null);
and i add #ManyToOne(fetch = FetchType.EAGER) in entity
This happened to me when I was already using #Transactional(value=...) and was using multiple transaction managers.
My forms were sending back data that already had #JsonIgnore on them, so the data being sent back from forms was incomplete.
Originally I used the anti pattern solution, but found it was incredibly slow. I disabled this by setting it to false.
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=false
The fix was to ensure that any objects that had lazy-loaded data that weren't loading were retrieved from the database first.
Optional<Object> objectDBOpt = objectRepository.findById(object.getId());
if (objectDBOpt.isEmpty()) {
// Throw error
} else {
Object objectFromDB = objectDBOpt.get();
}
In short, if you've tried all of the other answers, just make sure you look back to check you're loading from the database first if you haven't provided all the #JsonIgnore properties and are using them in your database query.
All answers about adding JOIN FETCH (or left join fetch) are correct, I want only to add this:
if you have converter be sure the getAsObject sub uses a "find" than includes in the sql the Join Fetch too.
I lost much time to fix a similar problem, and the problem was in the converter.
I was getting this error in my JAX-RS application when I was trying to get all the Departments. I had to add the #JsonbTransient Annotation to the attributes of both classes. My entities are Department and Employee, and the DB relationship is Many to Many.
Employee.java
...
#ManyToMany
#JoinTable(
name = "emp_dept",
joinColumns = {#JoinColumn(name = "emp_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "dept_id", referencedColumnName = "id")}
)
#JsonbTransient
private Set<Department> departments = new HashSet<Department>();
...
Department.java
...
#ManyToMany(mappedBy = "departments")
#JsonbTransient
private Set<Employee> employees = new HashSet<Employee>();
...
uses session.get(*.class, id); but do not load function
you could also solved it by adding lazy=false into into your *.hbm.xml file or you can init your object in Hibernate.init(Object) when you get object from db
Do the following changes in servlet-context.xml
<beans:property name="hibernateProperties">
<beans:props>
<beans:prop key="hibernate.enable_lazy_load_no_trans">true</beans:prop>
</beans:props>
</beans:property>
I'm trying to maintain state across multiple calls by using an EXTENDED_PERSISTENT_CONTEXT. My understanding is that managed entities will not detach between calls however I keep getting errors related to detached entities in calls after I have previously thrown validation errors. The state is being maintained in a stateful session bean:
#Named(SessionFacadeBean.SEAM_NAME)
#SessionScoped
#Stateful
#LocalBean
#AccessTimeout(value = 10, unit = TimeUnit.SECONDS)
public class SessionFacadeBean implements Serializable
{
public static final String SEAM_NAME = "sessionCacheBean";
#PersistenceContext(unitName = GlobalParameters.BACKEND_CODE_PERSISTENCE_CONTEXT_NAME, type = PersistenceContextType.EXTENDED)
private EntityManager em;
private ParentOne sessionData;
public synchronized ParentOne getSessionData() {
if(sessionData == null) {
sessionData = new ChildTwo();
}
return sessionData;
}
public boolean getLock() {
return true;
}
public void clearLock() {
}
// Other stuff I don’t ‘think’ is relevant.
}
The (simplified) state is being stored using hibernate. It consists of three classes (a parent, and two children, one of which contains a list of children):
#XmlRootElement(name = XMLConstants.COMPONENT_ELEMENT_NAME_IN_XML)
#XmlAccessorType(XmlAccessType.NONE)
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "Class", length = 50)
#Entity
public class ParentOne
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#XmlElement(name = "ID")
private Long iD;
#XmlElement(name = "name")
protected String friendlyName = "";
}
#XmlRootElement(name = XMLConstants.COMPONENT_ELEMENT_NAME_IN_XML)
#XmlAccessorType(XmlAccessType.NONE)
#Entity
public class ChildOne extends ParentOne
{
public ChildOne(String name, ParentOne child) {
super(name);
myChild = child;
}
#ManyToOne(cascade = CascadeType.ALL)
protected ParentOne myChild;
}
#XmlRootElement(name = XMLConstants.COMPONENT_ELEMENT_NAME_IN_XML)
#XmlAccessorType(XmlAccessType.NONE)
#Entity
public class ChildTwo extends ParentOne
{
public ChildTwo() {
super(“common”);
}
}
I’m accessing the stateful bean from a stateless bean like so:
#Stateless
#LocalBean
#Path("/")
public class MyService
{
#PersistenceContext(unitName = GlobalParameters.BACKEND_CODE_PERSISTENCE_CONTEXT_NAME)
private EntityManager em;
#Inject
private SessionFacadeBean sessionBean;
#POST
#Path("/create/item")
#ValidateRequest
public ComponentShortSummary addItem(#Form NewItemForm itemForm)
{
if(sessionBean.getLock()) {
try {
if(itemForm.getName().equals("INVALID") == true) {
throw new ConstraintViolationException("Failed", new HashSet<ConstraintViolation<?>>());
}
ChildOne child = new ChildOne(itemForm.getName(), sessionBean.getSessionData());
em.persist(child);
return null;
}
finally {
sessionBean.clearLock();
}
} else {
return null;
}
}
}
To reproduce the problem, I perform the following sequence:
Call addItem with a valid name (this persists the item to the database).
Call addItem with a name ‘INVALID’, this throws the constraint exception.
Call addItem with a valid name (this results in a detached entity error on the line em.persist(child).
What I don’t understand is how/why I’m ending up with detached entities. In the real code, I would be performing some request / state validation, before modifying the state (so there is no reason that I can see for the state to have been detached).
If I remove the call to sessionBean.getLock() then the problem goes away (the objects persist correctly). The purpose of the lock methods is essentially to serialize access to the session state, however currently the getLock() method is empty, it feels like the problem might be related to the fact that I’m calling into the stateful bean before throwing the exception.
Can anybody explain what’s going on that results in my entities becoming detached / if there is a way to avoid it (and ideally point me at any documentation that supports the explanation)?
Whilst there are probably ways that I can work around the current issue, performing validation before accessing the stateful bean at all, I’m concerned about the general case (where any exception is thrown after the stateful bean has been accessed in the call). Is there an accepted strategy for dealing with exceptions when I don’t want the entities from the extended persistent context to be detached?
It looks like this is expected behaviour. Thanks to Scott Marlow's reference to the JPA spec, section 3.3.2.
Transaction Rollback
For both transaction-scoped and extended
persistence contexts, transaction rollback causes all pre-existing
managed instances and removed instances[31] to become detached. The
instances’ state will be the state of the instances at the point at
which the transaction was rolled back. Transaction rollback typically
causes the persistence context to be in an inconsistent state at the
point of rollback. In particular, the state of version attributes and
generated state (e.g., generated primary keys) may be inconsistent.
Instances that were formerly managed by the persistence context
(including new instances that were made persistent in that
transaction) may therefore not be reusable in the same manner as other
detached objects—for example, they may fail when passed to the merge
operation.[32]
So, entities that are involved in the active transaction are detached when the transaction is rolled back and by calling out to the sessionBean I am involving it in the transaction.
One way around this appears to be to decorate acceptable exceptions with the #AppicationException annotation. This marks the exception as non-fatal and prevents the transaction from being rolled back. This approach is described in some detail by David Blevin.
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.