i am struggling with the following thing:
i have a stateless bean as a repository for a entity, this bean has a entity manager declared.
When i call this bean from another stateless bean , a entity is returned, then if a call a relation in this new returned entity a exception is thrown for "org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role". As i understand, the persistence context its attached to the transaction, or create a new one if transaction doesnt exist, but in this case, the transaction exist and its started in the client stateless bean that calls the repository bean.
here its a simple example:
#Entity
public class Config{
Long id;
String description;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="equipment")
private Equipment equipment;
}
#Entity
public class Equipment{
Long id;
String name;
#OneToMany(mappedBy = "equipment")
Config config;
}
#Stateless
public class EquipmentRepo{
#PersistenceContext(type=PersistenceContextType.TRANSACTION)
EntityManager em;
public Equipment find(Long id) {
return em.find(Equipment.class, id);
}
}
#Stateless
public class ServiceFacade {
#Inject
EquipmentRepo repo;
public List<Config> findEquipmentConfig(Long id) {
Equipment element = repo.find(id);
List<Config> configurations = element.getConfig();
return configurations;
}
}`
The lazy initialization also requires an active transaction. If you were to check carefully, the cause of the exception would be something to do with missing transaction.
When the stateless method returns, the active transaction is ended, and since the transaction is container managed, hibernate expects an active transaction when you call the entity method to load the referenced lazy loaded properties.
This problem is normally solved by ensuring that your entity domains are not used beyond the transaction/service level. When a service returns, it should return a dto of the mapped entity, with required fields. In this case therefore, if you require the lazy loaded properties, it will still be executed within the same transaction and mapped to your own dto.
First of all, I'm assuming that your Equipment class should look like:
#Entity
public class Equipment{
Long id;
String name;
#OneToMany(mappedBy = "equipment")
List<Config> config;
}
i.e config should be a collection.
#OneToMany relationships are lazily initialised by default.
When you execute:
Equipment element = repo.find(id);
it returns a single Equipment object that has a collection of Config proxies rather the entities themselves.
If you iterated over the element.config within the findEquipmentConfig method then the JPA implementation would load each Config entity as it is referenced.
This works because the entity manager and transaction are still active.
But you're iterating from the method that called findEquipmentConfig which I imagine is a servlet or some other kind of web controller that has no transactional context active.
Therefore you get the org.hibernate.LazyInitializationException.
At this point you might be considering adding a dummy load loop to preload all the configs.
But this is a bad idea because it leads to the infamous N+1 SELECT problem and an application that scales poorly. If an equipment entity had a 1000 config items associated with it then your application will execute 1001 SELECT statements to load the object (the extra one is to load the initial Equipment entity).
It is in fact one of the motivations for the join fetch query syntax that is available in JPA QL.
You can change your find method so that it looks something like:
public Equipment find(Long id) {
TypedQuery<Equipment> query = em.createQuery(
"SELECT e FROM Equipment e LEFT JOIN FETCH e.config WHERE e.id = :id", Equipment.class);
return query.setParameter("id", id).getSingleResult();
}
This will ensure that all of your equipment config is loaded at once.
Related
I have just come upon something that I can't describe in any other way than bizarre.
I have a service that is supposed to do this:
it gets passed an external identifier of a customer
it looks up the customer's internal ID
then loads and returns the customer
I'm using optionals as there is a potential chance that external identifiers can't be resolved.
#Transactional(readOnly = true)
public Optional<Customer> getCustomerByExternalReference(String externalId, ReferenceContext referenceContext) {
return externalIdMappingService.resolve(externalId, referenceContext, InternalEntityType.CUSTOMER)
.map(x->new CustomerId(x.getTarget()))
.map(customerRepository::getById);
}
what's noteworthy is here is that: externalIdMappingRepository.resolve returns an Optional<ExternalIdReference> object. If that is present, I attempt to map it to a customer that I then look up from the database. customerRepository is a regular spring data JPA repository (source code below)
However, when trying to access properties from Customer outside the service, I get an exception like this:
org.hibernate.LazyInitializationException: could not initialize proxy [Customer#Customer$CustomerId#3e] - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:176)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:322)
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45)
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95)
at Customer$HibernateProxy$R0X59vMR.getIdName(Unknown Source)
at CustomerApiModel.<init>(CustomerApiModel.java:27)
I understand that this means, that Hibernate decided to lazy load that entity. Once outside the transactional boundaries of the service, it's not able to load the data for that object anymore.
My Question is: Why does Hibernate/Spring Data try a lazy fetching strategy when I essentially just load a specific object by ID from a Spring Data Repository and how I can disable this behaviour the right way.
I'm aware that there is a couple of workarounds to fix the problem (such as allowing hibernate to open sessions at will, or to access properties of that object inside the service). I'm not after such fixes. I want to understand the issue and want to ensure that lazy fetching only happens when it's supposed to happen
Here's the code for customer (just the part that I think is helpful)
#Entity
#Table(name="customer")
#Getter
public class Customer {
#EmbeddedId
private CustomerId id;
#Embeddable
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
public static class CustomerId implements Serializable {
private long id;
public long asLong() {
return id;
}
}
}
and here's the source code of the repository:
public interface CustomerRepository extends Repository<Customer, CustomerId> {
List<Customer> findAll();
Customer getById(CustomerId id);
Optional<Customer> findOneById(CustomerId id);
Optional<Customer> findOneByIdName(String idName);
}
By declaring the method Customer getById(CustomerId id); in your CustomerRepository interface, you chose to let your repostory selectively expose the corresponding method with the same signature from the standard spring-data repository methods, as explained by the Repository java doc:
Domain repositories extending this interface can selectively expose CRUD methods by simply declaring methods of the same signature as those declared in CrudRepository.
Different to what the doc says, this also includes methods from JpaRepository.
In the case of Customer getById(CustomerId id);, you therefore invoke the JpaRepository method with the same signature: T getOne(ID id);, which only invokes EntityManager#getReference , as suggested by it's doc:
[...] Returns a reference to the entity with the given identifier. Depending on how the JPA persistence provider is implemented this is very likely to always return an instance and throw an {#link javax.persistence.EntityNotFoundException} on first access. Some of them will reject invalid identifiers immediately. [...]
#see EntityManager#getReference(Class, Object) for details on when an exception is thrown.
When calling EntityManager#getReference, Hibernate first returns a non-initialized proxy of the Entity without executing any SQL statement at all, which is why your method only returns the non-initialized entity.
To fix this, you could change your service logic as follows:
#Transactional(readOnly = true)
public Optional<Customer> getCustomerByExternalReference(String externalId, ReferenceContext referenceContext) {
return externalIdMappingService.resolve(externalId, referenceContext, InternalEntityType.CUSTOMER)
.map(x->new CustomerId(x.getTarget()))
.map(id -> customerRepository.findOneById(id).get()); // <-- changed call
}
This way, spring-data would invoke CrudRepository#findById, which would internally call EntityManager#find and therefore return an initialized entity (or an empty Optional if none was found in the DB).
Related:
When use getOne and findOne methods Spring Data JPA
Why "findById()" returns proxy after calling getOne() on same entity? (attention when using getOne and findById in the same transaction)
In my web-apllication, in service-layout, I'm using proxy for the "restaurant" entity (FetchType.Lazy on "restaurant" field).
User user = userRepository.get(userId);
/*
Getting proxy here, not restaurant object
*/
Restaurant userRestaurantRef = user.getRestaurant();
if (userRestaurantRef != null){
restaurantRepository.decreaseRating(userRestaurantRef.getId());
}
restaurantRepository.increaseRating(restaurantId);
/*
"getReference" invokes "getOne()"
*/
user.setRestaurant(restaurantRepository.getReference(restaurantId));
userRepository.save(user);
After calling this method via controller in tests, all other RestaurantRepository's getting methods (such as findById()) returns
proxy also.
But, if I called "findById()" method before my service's method, it's all OK.
For example:
mockMvc.perform(put(REST_URL + RESTAURANT1_ID)
.param("time", "10:30")
.with(userHttpBasic(USER)))
.andExpect(status().isNoContent());
Restaurant restaurant = restaurantRepository.get(RESTAURANT1_ID);
"restaurant" is PROXY
Restaurant restaurantBefore = restaurantRepository.get(RESTAURANT1_ID);
mockMvc.perform(put(REST_URL + RESTAURANT1_ID)
.param("time", "10:30")
.with(userHttpBasic(USER)))
.andExpect(status().isNoContent());
Restaurant restaurantAfter = restaurantRepository.get(RESTAURANT1_ID);
"restaurantAfter" is real Object
"get()" into repository:
#Override
public Restaurant get(int id) {
return repository.findById(id).orElse(null);
}
Do you have #Transactional annotation on the method or service class itself?
This could explain the observed behavior.
When a method is executed in a transaction, entities acquired or merged/saved from/to the database are cached until the end of the transaction (usually the end of the method). That means that any call for entity with same ID will be returned directly from the cache and will not hit the database.
Here are some articles on Hibernate's caching and proxies:
Understanding Hibernate First Level Cache with Example
How does a JPA Proxy work and how to unproxy it with Hibernate
The best way to initialize LAZY entity and collection proxies with JPA and Hibernate
Back to your example:
call findById(id) first and then getOne(id) returns the same entity object for both
call getOne(id) first and then findById(id) returns the same proxy for both
That's because they share the same id and are executed in the same transaction.
Documentation on getOne() states that it could return an instance instead of reference (HibernateProxy), so having it returning an entity could be expected:
T getOne(ID id)
Returns a reference to the entity with the given identifier.
Depending on how the JPA persistence provider is implemented this is very likely
to always return an instance and throw an EntityNotFoundException on
first access. Some of them will reject invalid identifiers immediately.
Parameters:
id - must not be null.
Returns:
a reference to the entity with the given identifier.
Documentation on findById() from the other hand does not have any hints in the direction that it could return anything but Optional of entity or empty Optional:
Optional findById(ID id)
Retrieves an entity by its id.
Parameters: id - must not be null.
Returns: the entity with the given id or Optional#empty() if none found
I've spend some time looking for a better explanation, but failed to find one so I'm not sure if it is a bug in the implementation of findById() or just a not (well) documented feature.
As workarounds to the problem I could suggest:
Do not acquire the same entity twice in the same transactional method. :)
Avoid using #Transactional when not need. Transactions can be managed manually too. Here are some good articles on that subject:
5 common Spring #Transactional pitfalls
Spring Transactional propagation modes
Spring pitfalls: transactional tests considered harmful.
Detach first loaded entity/proxy before (re-)loading using the other method:
import javax.persistence.EntityManager;
import org.springframework.transaction.annotation.Transactional;
#Transactional
#Service
public class SomeServiceImpl implements SomeService {
private final SomeRepository repository;
private final EntityManager entityManager;
// constructor, autowiring
#Override
public void someMethod(long id) {
SomeEntity getOne = repository.getOne(id); // Proxy -> added to cache
entityManager.detach(getOne); // removes getOne from the cache
SomeEntity findById = repository.findById(id).get(); // Entity from the DB
}
Similar to the 3rd approach, but instead of removing a single object from the cache, remove all at once using the clear() method:
import javax.persistence.EntityManager;
import org.springframework.transaction.annotation.Transactional;
#Transactional
#Service
public class SomeServiceImpl implements SomeService {
private final SomeRepository repository;
private final EntityManager entityManager;
// constructor, autowiring
#Override
public void someMethod(long id) {
SomeEntity getOne = repository.getOne(id); // Proxy -> added to cache
entityManager.clear(); // clears the cache
SomeEntity findById = repository.findById(id).get(); // Entity from the DB
}
Related articles:
When use getOne and findOne methods Spring Data JPA
Hibernate Session: evict() and merge() Example
clear(), evict() and close() methods in Hibernate
JPA - Detaching an Entity Instance from the Persistence Context
Difference between getOne and findById in Spring Data JPA?
EDIT:
Here is a simple project demonstrating the problem or the feature (depending on the point of view).
Some extension to the - already accepted - answer:
If you use Spring Boot then it automatically enable the Open Session In View filter, which basically works as a transaction for each request.
If you want to turn off this feature add the following line to the application.properties:
spring.jpa.open-in-view=false
OSIV is really a bad idea from a performance and scalability perspective.
My project use to have a lot of #Transactional method everywhere. Now because of business logic I do not want to rollback when I have an issue, but want to set my object to an error status (aka saved in the db, so definitly not rollback), so I removed a few #Transactional to start.
Now the issue is where there is a lazy loading there is no session, thus spawning a LazyInitializationException.
Now here are my following trouble-shooting and solution seeking so far :
We're using annotation configuration, so no xml configuration here.
For each action using the database, an EntityManager (defined as an attribute and #Autowired in the service) is created and then deleted (I can clearly see it in the logs when adding the configuration to see them), which apparently is normal according to the Spring documentation.
Using #PersistenceContext or #PersistenceUnit, either with a EntityManagerFactory or with EntityManager doesn't work.
I can load the lazy-loaded attribute I want to use with Hibernate.initialize(), and it then doesn't spawn a LazyInitializationException.
Now my question is : Why can't hibernate do it by itself ? It's seems trivial to me that if I'm using a lazy loading, I want Hibernate to create a session (which he seems perfectly able to do when doing Hibernate.initialize()) to load the date automatically.
Would there be a way to spawn a new entity manager to be use inside my method so Hibernate doesn't create and recreate one all the time ? I really feel like I'm missing something basic about Hibernate, lazy loading and sessions that makes this whole mess a lot less complicated.
Here is an example :
#Entity
#Table(name = "tata")
public class Tata {
#Id
#Column(name = "tata_id")
private Long id;
// getter / setter etc
}
#Entity
#Table(name = "toto")
public class Toto {
#Id
#Column(name = "toto_id")
private Long id;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "tata_id")
private Tata tata;
// getter / setter etc
}
#Service("totoManager")
public class TotoManager extends GenericManagerImpl {
#Autowired
private EntityManager entityManager;
#Autowired
private TotoRepository totoRepository;
public void doSomethingWithTotos() throws XDExceptionImpl {
List<Toto> totos = this.totoRepository.findAll();
for (toto toto : totos) {
// LazyInitializationException here
LOGGER.info("tata : " + toto.getTata().getId());
}
}
}
Hibernate can do it by itself. With setting property hibernate.enable_lazy_load_no_trans=true (for spring boot it should be spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true) you can load any lazy property when transaction is closed. This approach has huge drawback: each time you load lazy property, hibernate opens session and creates transaction in background.
I would recommed fetch lazy properties by entityGraphs. So you doesnt have to move persistent context do upper level or change fetch type in your entities.
Try to read something about Open session in view to understand why Lazy loading does not work out of session/transaction. If you want you can set property spring.jpa.open-in-view=true and it will load your lazy loaded data.
Hibernate persists modified entities at the of transactional methods, I can avoid by using session#evict(entity).
If I detach it from the persistence context, the entities whithin it will also be detached?
For instance, I have this classes:
#Entity
public class User extends BaseEntity{
#Column(name = "email")
private String email;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private List<Address> addresses;
// getters and setters
}
#Entity
public class Address extends BaseEntity{
#Column(name = "email")
private String email;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "USER_ID")
private User user;
// getters and setters
}
If I detach a user object, but change the address object in it, will the address be persisted at the end of transaction? Like this:
User user = userDAO.getById(id);
session.evict(user);
Address address = user.getAddresses().get(0);
address.setNumber(number);
addressDAO.saveOrUpdate(address); //will this work?
Entities that are updated or deleted using a EntityManager.createQuery() are not loaded into the Persistence Context, this only happens for select queries, and when you use find()or merge().
After you do an update or delete query your persistence context may actually be out-of-sync with the database, because the query doesn't update the entities which has already been loaded into the persistence context (you need to call refresh() to see the changes).
If you load a number of user (into the persistence context), and later doUpdate User set status='active' where id IN (:ids), then you have not modified any of the users in the persistence context, you have only modified the database. To modify a user, you must modify the actually managed Entity by calling `aUser.setStatus('active'), when the transaction commits, JPA will check all managed entities against a copy created when it was loaded, and if anything has changed it will do an Update.
If you are loading 5000 objects into the Persistence it may take some time for JPA to run though the entity graph, and detect the changes when the transaction commits. If you didn't modify anything, and would like to speed up the change-detection, there are two ways to do this. Load your entities using a read-only query, this tells JPA that it does not need to keep a copy of the loaded entity. The other option is to call EntityManager.clear() to throw away all managed entities. However, if you are interested in performance, the best solution is probably to avoid loading the entities into the persistence context. As I understand you problem, you need to do a Update User set ... where id IN (:ids)and for that you only need the user's id so you don't need to load the user, you just need the ids, and therefore you can do List<Long> ids = em.createQuery("select u.id from User u where ...", Long.class).getResultList();
Hope this clarifies things for you :)
EDIT: this is written from a JPA perspective, but for hibernate EntityManager just forwards directly to SessionImpl, so the behavior is exactly as described, except for find() being called get()in native Hibernate.
Since JPA 2.0
given an EntityManager you can call detach with the entity you want to be detached as parameter
void detach(Object entity)
more here
if you use injection then you can inject an EntityManger in the service where you want to detach the required entity.
I am writing an application that has typical two entities: User and UserGroup. The latter may contain one or more instances of the former. I have following (more/less) mapping for that:
User:
public class User {
#Id
#GeneratedValue
private long id;
#ManyToOne(cascade = {CascadeType.MERGE})
#JoinColumn(name="GROUP_ID")
private UserGroup group;
public UserGroup getGroup() {
return group;
}
public void setGroup(UserGroup group) {
this.group = group;
}
}
User group:
public class UserGroup {
#Id
#GeneratedValue
private long id;
#OneToMany(mappedBy="group", cascade = {CascadeType.REMOVE}, targetEntity = User.class)
private Set<User> users;
public void setUsers(Set<User> users) {
this.users = users;
}
}
Now I have a separate DAO class for each of these entities (UserDao and UserGroupDao). All my DAOs have EntityManager injected using #PersistenceContext annotation, like this:
#Transactional
public class SomeDao<T> {
private Class<T> persistentClass;
#PersistenceContext
private EntityManager em;
public T findById(long id) {
return em.find(persistentClass, id);
}
public void save(T entity) {
em.persist(entity);
}
}
With this layout I want to create a new user and assign it to existing user group. I do it like this:
UserGroup ug = userGroupDao.findById(1);
User u = new User();
u.setName("john");
u.setGroup(ug);
userDao.save(u);
Unfortunately I get following exception:
object references an unsaved transient instance - save the transient
instance before flushing: x.y.z.model.User.group ->
x.y.z.model.UserGroup
I investigated it and I think it happens becasue each DAO instance has different entityManager assigned (I checked that - the references in each DAO to entity manager are different) and for user entityManager does not manager the passed UserGroup instance.
I've tried to merge the user group assigned to user into UserDAO's entity manager. There are two problems with that:
It still doesn't work - the entity manager wants to overwrite the existing UserGroup and it gets exception (obviously)
even if it worked I would end up writing merge code for each related entity
Described case works when both find and persist are made using the same entity manager. This points to a question(s):
Is my design broken? I think it is pretty similar to recommended in this answer. Should there be single EntityManager for all DAOs (the web claims otherwise)?
Or should the group assignment be done inside the DAO? in this case I would end up writing a lot of code in the DAOs
Should I get rid of DAOs? If yes, how to handle data access nicely?
any other solution?
I am using Spring as container and Hibernate as JPA implementation.
Different instances of EntityManager are normal in Spring. It creates proxies that dynamically use the entity manager that is currently in a transaction if one exists. Otherwise, a new one will be created.
The problem is that your transactions are too short. Retrieving your user group executes in a transaction (because the findById method is implicitly #Transactional ). But then the transaction commits and the group is detached. When you save the new user, it will create a new transaction which fails because the user references a detached entity.
The way to solve this (and to do such things in general) is to create a method that does the whole operation in a single transaction. Just create that method in a service class (any Spring-managed component will work) and annotate it with #Transactional as well.
I don't know Spring, but the JPA issue is that you are persisting a User that has a reference to a UserGroup, but JPA thinks the UserGroup is transient.
transient is one of the life-cycle states a JPA entity can be in. It means it's just created with the new operator, but has not been persisted yet (does not have a persistent identity yet).
Since you obtain your UserGroup instance via a DAO, it seems like something is wrong there. Your instance should not be transient, but detached. Can you print the Id of the UserGroup instance just after your received it from the DAO? And perhaps also show the findById implementation?
You don't have cascade persist on the group relation, so this normally should just work if the entity was indeed detached. Without a new entity, JPA simply has no way to set the FK correctly, since it would need the Id of the UserGroup instance here but that (seemingly) doesn't exist.
A merge should also not "overwrite" your detached entity. What is the exception that you're getting here?
I only partially agree with the answers being given by the others here about having to put everything in one transaction. Yes, this indeed may be more convenient as the UserGroup instance will still be 'attached', but it should not be -necessary-. JPA is perfectly capable of persisting new entities with references to either other new entities or existing (detached) entities that were obtained in another transaction. See e.g. JPA cascade persist and references to detached entities throws PersistentObjectException. Why?
I am not sure how but I've managed to solve this. The user group I was trying to assign the user to had NULL version field in database (the field annotated with #Version). I figured out it was an issue when I was testing GWT RequestFactory that was using this table. When I set the field to 1 everything started to work (no changes in transaction handling were needed).
If the NULL version field really caused the problem then this would be one of the most misleading exception messages I have ever got.