I have a one-to-many relationship defined as below
#Cacheable
#Entity
#NamedEntityGraph(
name = "Parent.Child",
attributeNodes = {
#NamedAttributeNode("children"),
}
)
public class Parent {
private Set<Child> children;
// getter - setter
}
Now in my DAL, i'm calling this method
#Override
public Parent getParentWithChildren(int id) {
EntityGraph<?> graph = entityManager.getEntityGraph("Parent.Child");
Map<String, Object> props = new HashMap<>();
props.put("javax.persistence.fetchgraph", graph);
return entityManager.find(Parent.class, id, props);
}
Since i have loaded Parent with Children, i should be able to use children collection outside of the transaction. But i'm getting Lazyinitialization Exception. This happens only when hibernate level 2 cache - ehcache is enabled. If i disable it from config, it works as expected. Also if i initialize collection explicitly after find, it works as expected. So is it a bug?. I'm using Hibernate 5.2.6.Final with JPA 2.1.
EDIT: One more thing i noticed was that entity loads fine for the first time, so that problem must be related with hibernate & cache provider.
In order for Hibernate to use an entity graph one must bypass the second-level cache (e.g. EhCache) by using the javax.persistence.cache.retrieveMode hint with the value CacheRetrieveMode.BYPASS:
final EntityGraph<?> graph = em.getEntityGraph("myGraph");
final Map<String, Object> hints = new HashMap<>();
hints.put("javax.persistence.cache.retrieveMode", CacheRetrieveMode.BYPASS);
hints.put("javax.persistence.fetchgraph", graph);
final SomeEntity entity = em.find(SomeEntity.class, 42, hints);
Note that the second-level cache will still be populated as usual.
No. It's not a bug. I guess Hibernate is delaying the loading from cache until really necessary. The implementation can decide to be lazy as see fit unless you ask for eager fetching.
So the general rule is to load everything you need before going out of the transaction (before closing the Hibernate session to be precise).
Related
How can I get actual child collection, when adding new one in separated transactional method, while updating parent.
I have spring boot app with hibernate/jpa and one-to-many unidirectional model:
parent:
#Entity
public class Deal {
private UUID id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Rate> rates;
....
}
child:
#Entity
public class Rate {
private UUID id;
....
}
And I have non transactional method for do some business logic by rest call:
public Deal applyDeal(UUID dealId) {
dealService.apply(dealId);
return dealService.getById(dealId);
}
Method apply in DealService has several methods in separate transactions (all methods doLogic() annotated with #Transactional(Propagation.REQUIRES_NEW):
public void apply(UUI dealId) {
someService1.do1Logic(...);
someService2.do2Logic(...);
someService3.do3Logic(...);
}
In do2Logic() I have some logic that adding new Rate entity to my parent entity with dealId and direct call of save method for Deal object.
#Transactional(Propagation.REQUIRES_NEW)
publid void do2Logic(...) {
...
var deal = dealService.getById(...);
deal.getRates().add(new Rate());
dealService.save(deal);
}
But when I get response from root method applyDeal the new child entity is absent.
If after that I will try to get this parent in separate rest call (getDeal) I get actual parent entity with new child in collection.
How to get actual child collection in parent response of applyDeal method?
I tried to make all logic in one #Transactional but it doesn't works.
I also don't understand why when I am try to get deal instance to return in applyDeal I get old data.
Thank you.
I guess you are running MySQL or MariaDB? These two database by default use the repeatable read transaction isolation level, which can cause this behavior. Try configuring the read committed isolation level instead, and/or remove the REQUIRES_NEW propagation if possible, since that will suspend an already running transaction to start a second one.
(Please feel free to edit the title after reading this question)
I have quite simple #ManyToOne bidirectional mapping between entities Parent and Child.
The list of children Collection<Child> children in Parent is never initialized so it should be null.
When using EntityManager.find(...) for previously persisted Parent and then getting the list from that Parent gives ArrayList even there are no children yet with this Parent and it is fine.
However if persisting or merging a new Parent in the same transaction collection of children will be null even if the persisted/merged Parent is fetched again with EntityManager.find(...).
So i wonder this different behavior and if it is happening only in my environment.
I assume it has something to do with the caching of entities: entity is found from cache and it is returned instead of fetching it from db AND the initialization of empty collections will happen only when fetched from db, maybe depending on the JPA implementation.
Is my assumption even near the truth and if not what is the reason ?
Entities and test cases below. My test environment listed in tags.
// using lombok
#Slf4j
#RunWith(Arquillian.class)
public class NoPersistTest {
#PersistenceContext
private EntityManager em;
#Deployment
public static final WebArchive deploy() {
WebArchive wa = ShrinkWrap.create(WebArchive.class, "test.war")
.addAsWebInfResource("test-persistence.xml", "persistence.xml").addClasses(Parent.class, Child.class);
return wa;
}
#Test
#Transactional
public void testWithPreviouslyPersistedParent() {
Parent parent = em.find(Parent.class, 1); // has no children in db
// before
Child child = new Child();
child.setParent(parent);
parent.getChildren().add(child);
log.info("type of Collection<Child> is {}", parent.getChildren().getClass().getName());
// above logs "type of Collection<Child> is
// org.apache.openjpa.util.java$util$ArrayList$proxy"
}
#Test(expected = NullPointerException.class)
#Transactional
public void testPersistingParentInSameTransaction() {
Parent parent = new Parent();
em.persist(parent);
Parent parent2 = em.find(Parent.class, parent.getId());
Child child = new Child();
child.setParent(parent2);
log.info("Collection<Child> is {}", parent2.getChildren());
// above logs Collection<Child> is null
parent2.getChildren().add(child);
}
#Test(expected = NullPointerException.class)
#Transactional
public void testMergingParentInSameTransaction() {
Parent parent = new Parent();
parent = em.merge(parent);
Parent parent2 = em.find(Parent.class, parent.getId());
Child child = new Child();
child.setParent(parent2);
log.info("Collection<Child> is {}", parent2.getChildren());
// logs Collection<Child> is null
parent2.getChildren().add(child);
}
}
#Entity #Getter #Setter
public class Parent {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy="parent", cascade=CascadeType.ALL, orphanRemoval=true)
private Collection<Child> children;
private Date created = new Date(); // just to have something to persist
}
#Entity #Getter #Setter
public class Child {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private Date created = new Date(); // just to have something to persist
#ManyToOne(optional=false)
private Parent parent;
}
If you create the Parent the collection is not initialized because you don't do it. And also when persisting the Parent JPA will leave the collection as it is.
But when you read the Parent with Hibernate the collection will contain a proxy because toMany relationships are fetched LAZY and this proxy is used to fetch the children on demand.
My recommendation is to always initialize collection to avoid NullPointerExceptions. That's good programming style.
The answer below is correct, I'd just like to add some more information as I was asked to in a comment elsewhere.
JPA uses caching to avoid database hits where possible, and where a database hit is still required, caching avoids the cost of rebuilding objects and allows maintaining Identity - ensuring you get back the same A instance when traversing A->B->A circular references.
When you persist an entity, you are placing it in the EntityManager cache as a managed entity - calling find on that EntityManager will return you the same exact instance you just passed in.
A initialA = new A();
A managedA = em.persist(initialA);
managedA==initialA
The persist call itself will not change anything within your entity (except possibly the ID if a sequence that allows preallocation to be used), so any null references will still be null.
Eventually the transaction commits and depending on your provider, entities can be cached in a second level cache. I'll assume you aren't using it for the sake of brevity; unless you force the EM to refresh this instance (flush first if its a new one!) or read it in a separate EntityManager, you will always get that same instance back with any null references.
If you refresh it or otherwise cause it to be reloaded, your JPA provider is required to set everything in the object as it is in the database, according to your mappings. Since null isn't a persistable state for a collection mapping, that means it will either eagerly fetch your references, or place proxies in there for lazy relationships, causing you to find an empty collection.
I'm working with Spring Boot 1.3.0.M4 and a MySQL database.
I have a problem when using modifying queries, the EntityManager contains outdated entities after the query has executed.
Original JPA Repository:
public interface EmailRepository extends JpaRepository<Email, Long> {
#Transactional
#Modifying
#Query("update Email e set e.active = false where e.active = true and e.expire <= NOW()")
Integer deactivateByExpired();
}
Suppose we have Email [id=1, active=true, expire=2015/01/01] in DB.
After executing:
emailRepository.save(email);
emailRepository.deactivateByExpired();
System.out.println(emailRepository.findOne(1L).isActive()); // prints true!! it should print false
First approach to solve the problem: add clearAutomatically = true
public interface EmailRepository extends JpaRepository<Email, Long> {
#Transactional
#Modifying(clearAutomatically = true)
#Query("update Email e set e.active = false where e.active = true and e.expire <= NOW()")
Integer deactivateByExpired();
}
This approach clears the persistence context not to have outdated values, but it drops all non-flushed changes still pending in the EntityManager. As I use only save() methods and not saveAndFlush() some changes are lost for other entities :(
Second approach to solve the problem: custom implementation for repository
public interface EmailRepository extends JpaRepository<Email, Long>, EmailRepositoryCustom {
}
public interface EmailRepositoryCustom {
Integer deactivateByExpired();
}
public class EmailRepositoryImpl implements EmailRepositoryCustom {
#PersistenceContext
private EntityManager entityManager;
#Transactional
#Override
public Integer deactivateByExpired() {
String hsql = "update Email e set e.active = false where e.active = true and e.expire <= NOW()";
Query query = entityManager.createQuery(hsql);
entityManager.flush();
Integer result = query.executeUpdate();
entityManager.clear();
return result;
}
}
This approach works similar to #Modifying(clearAutomatically = true) but it first forces the EntityManager to flush all changes to DB before executing the update and then it clears the persistence context. This way there won't be outdated entities and all changes will be saved in DB.
I would like to know if there's a better way to execute update statements in JPA without having the issue of the outdated entities and without the manual flush to DB. Perhaps disabling the 2nd level cache? How can I do it in Spring Boot?
Update 2018
Spring Data JPA approved my PR, there's a flushAutomatically option in #Modifying() now.
#Modifying(flushAutomatically = true, clearAutomatically = true)
I know this is not a direct answer to your question, since you already have built a fix and started a pull request on Github. Thank you for that!
But I would like to explain the JPA way you can go. So you would like to change all entities which match a specific criteria and update a value on each. The normal approach is just to load all needed entities:
#Query("SELECT * FROM Email e where e.active = true and e.expire <= NOW()")
List<Email> findExpired();
Then iterate over them and update the values:
for (Email email : findExpired()) {
email.setActive(false);
}
Now hibernate knows all changes and will write them to the database if the transaction is done or you call EntityManager.flush() manually. I know this won't work well if you have a big amount of data entries, since you load all entities into memory. But this is the best way, to keep the hibernate entity cache, 2nd level caches and the database in sync.
Does this answer say "the `#Modifying´ annotation is useless"? No! If you ensure the modified entities are not in your local cache e.g. write-only application, this approach is just the way to go.
And just for the record: you don't need #Transactional on your repository methods.
Just for the record v2: the active column looks as it has a direct dependency to expire. So why not delete active completely and look just on expire in every query?
As klaus-groenbaek said, you can inject EntityManager and use its refresh method :
#Inject
EntityManager entityManager;
...
emailRepository.save(email);
emailRepository.deactivateByExpired();
Email email2 = emailRepository.findOne(1L);
entityManager.refresh(email2);
System.out.println(email2.isActive()); // prints false
We upgraded our project from Spring 3x/Hibernate 3x to Spring 4.1.5/Hibernate 4.3.8.
Initially we were using Hibernate Tempate. During upgrade we removed the Hibernate Template of spring and used Spring's declarative transaction management.
Earlier our query with hibernateTemplate used to take very sort time to retrive 3500 records from DB. Now when are using query.list(), the running time is coming in minutes (4-5 mins approx).
Old Code
DAO Class:
---------
public List<LatestRecVO> getListRecs(String listId) {
HibernateTemplate ht = new HibernateTemplate(getSessionFactory());
List<LatestRecVO> listOfRecs = ht.find(" from LatestRecVO a where a.listId = ? order by a.listRecId asc", Long.valueOf(listId) );
return listOfRecs;
}
Service Class:
--------------
#Transactional
public List<LatestRecVO> getListRecs(String listId) {
List<LatestRecVO> listOfRecs = listDao.getListRecDetails(listId);
return listOfRecs;
}
New Code
DAO Class:
---------
public List<LatestRecVO> getListRecs(String listId) {
Query q = getSession().createQuery(" from LatestRecVO a where a.listId = (:listId) order by a.listRecId asc");
q.setParameter("listId", Long.valueOf(listId));
List<LatestRecVO> listOfRecs = q.list();
return listOfRecs;
}
Service Class:
--------------
#Transactional
public List<LatestRecVO> getListRecs(String listId) {
List<LatestRecVO> listOfRecs = listDao.getListRecDetails(listId);
return listOfRecs;
}
The entity class LatestRecVO do not have any associated entity with it.
I checked the Hibernate Template's find() method and saw its uses some caching.
Tried 2nd level cache along with query cache but it didnt helped. I may have configured 2nd level cache incorrectly but to try it again i want to be sure that its the way out else i would be wasting time.
I made show_sql as true and can see it just ran a singly query. On DB the same query takes some milliseconds to run. It seems like hibernate is taking time to build objects from the result.
On one of the post it was mentioned that its mandate to have a default constructor in our entities. I have not created any constructor in my entity class so i assume that i do have java's default constructor in place.
My table has 36 columns and in total there are 4k records to be fetched.
Any pointer in this will be really helpful.
Update
Sorry, I cannot post the complete code here, so just giving the details. I have the composite primary key for LatestRecVO. I have created a class LatestRecPK for primary key, it implements serializable and have #Embeddable annotation. In LatestRecVO i have given #IdClass(LatestRecPK.class) to include the primary key class. LatestRecVO has a CLOB property along with String, Long and #Temporal(TemporalType.DATE) properties and corresponding setters/getters.
I have a very wired problem.
JPA/Hiberante don't generate join sql for FetchType.EAGER while Spring #Transactional annotated. But if I remove the #Transactional . Everything is fine.
Here is the code:
public class Item {
#ManyToOne
private Order order;
}
public class Order {
#OneToMany(mappedBy = "item", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<Item> items;
}
#Test
#Transactional
public void testFetch() throws Exception {
Item randomItem = new Item();
Order randomOrder = new Order();
//OrderService and itemService is implemented by Spring Roo standard.
orderService.saveOrder(randomOrder);
randomItem.setOrder(randomOrder);
itemService.saveItem(randomItem);
Order OrderResult = orderService.findOrder(randomOrder.getId());
final List<Item> itemSearchResult = OrderResult.getItems();
Assert.assertNotNull(itemSearchResult);
}
The assertNotNull will fail if #Transactional on. But will success if #Transactional commented.
I debug more information. Just to find out when #Transactional on Hibernate will not generate join sql for
orderService.findOrder(randomOrder.getId());
Alos I try to switch to elicpseLink as JPA provider. Things become worse, when #Transactional commented, orderService.findOrder(randomOrder.getId()) will return a empty list(not null, size 0).
Any advice? Many Thanks!
I can't comment on the joins in Hibernate except that you should specify fetch join in your query to be portable to other JPA providers. EclipseLink in particular does not join eager relationships without JPA settings or native query hints or #JoinFetch annotations.
As for the collection being empty on EclipseLink. This is because you only setting one side of the relationship. JPA requires you to set both sides of bidirectional relationships so that they remain consistent with what is in the database. When #Transactional is commented out, you are getting back the same randomOrder instance that had an empty items collection when persisted.
Try calling randomOrder.addItems(randomItem); and randomItem.setOrder(randomOrder); before the orderService.saveOrder(randomOrder); call.