I am running into a problem managing my Hibernate-backed collections. The problem is illustrated in the code below; briefly,
A single session and transaction are started.
All instances of ClassA are loaded and walked to force Hibernate to load all instances of ClassC. At this point all instances of ClassC actually load via lazy fetching.
In the same transaction and session, all instances of ClassB are loaded.
All instances of ClassC are no longer available (the containing collections are empty) and do not lazy load if requested.
What's the best way to load up the data sets needed and keep them in memory?
Notes:
The project is in rapid-prototyping mode so I am using a single session and single
transaction to load up two big chunks of the application's data. This is also a desktop application so the use of a single session and transaction may not even be a problem in the long run if the code were working.
I set up ehcache and the debugger showing that Hibernate found the ehcache config file. The cache is set such that
maxElementsInMemory="5000000"
eternal="true"
the timeouts are set to 20 mins, even though eternal is set true, just to be sure the session isn't timing out.
The data set size is not particularly large, about 100k records.
The Hibernate 3.0 module packaged with NetBeans was use to create the class templates from a data model.
The mapping file looks roughly like this:
<hibernate-mapping>
<class catalog="myCatalog" name="ClassA" table="classA">
<id ... </id>
<set inverse="true" name="classCs" sort="natural">
<key>
<column length="12" name="mykey" not-null="true"/>
</key>
<one-to-many class="ClassC"/>
</set>
</class>
</hibernate-mapping>
Class definitions:
#Entity
#Table(name = "classA", catalog = "myCatalog")
public class ClassA implements java.io.Serializable {
private SortedSet<ClassC> classCs = new TreeSet<ClassC>();
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "ClassA")
public SortedSet<ClassC> getClassCs() {
return this.classCs;
}
}
#Entity
#Table(name = "classB", catalog = "myCatalog")
public class ClassB implements java.io.Serializable {
// ... It is possible to walk collections in this class to reach some instances of classA.
}
public class ClassC implements java.io.Serializable {
// ... contains no collections
}
Manifestation of problem:
// in Hibernate Util
SessionFactory sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
public static void main(String[] args) {
Application.session = HibernateUtil.getSessionFactory().openSession();
Application.tx = session.beginTransaction();
// Load data set 'A' and force collections to load
List<ClassA> listClassA = new ArrayList<ClassA>();
Query q = Application.session.createQuery("from ClassA as a").setReadOnly(true);
Iterator<ClassA> iterator = q.list().iterator();
while (iterator.hasNext()) {
ClassA ca = (ClassA)iterator.next();
// force member collection to load -- at present this is necessary even though FetchType is EAGER
ca.getClassCs();
listClassA.add(ca);
}
// The collections of classCs are in are in memory here
for(ClassA a : listClassA){
log.info(a.getId() + "-" + a.getClassCs().size());
}
// Load data set 'B'
List<ClassB> listClassB = new ArrayList<ClassB>();
String sq = "from classB as b where ...";
Query q = Application.session.createQuery(sq);
listClassB.addAll(q.list());
// The collections of classCs are NOT in memory and touching collections does not force reload
// The collections exist but now all have size zero
for(ClassA a : listClassA){
log.info(a.getId() + "-" + a.getClassCs().size());
}
return;
}
I turns out that Hibernate can optionally flush the session cache before each query. One of the settings to control the session cache is session.setFlushMode(). In Hibernate 3.2.5, setting flush mode to:
FlushMode.COMMIT or FlushMode.MANUAL
achieves the desired result. I found this while reading "Hibernate in Action".
Related
First, here are my entities.
Player :
#Entity
#JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class,
property="id")
public class Player {
// other fields
#ManyToOne
#JoinColumn(name = "pla_fk_n_teamId")
private Team team;
// methods
}
Team :
#Entity
#JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class,
property="id")
public class Team {
// other fields
#OneToMany(mappedBy = "team")
private List<Player> members;
// methods
}
As many topics already stated, you can avoid the StackOverflowExeption in your WebService in many ways with Jackson.
That's cool and all but JPA still constructs an entity with infinite recursion to another entity before the serialization. This is just ugly ans the request takes much longer. Check this screenshot : IntelliJ debugger
Is there a way to fix it ? Knowing that I want different results depending on the endpoint. Examples :
endpoint /teams/{id} => Team={id..., members=[Player={id..., team=null}]}
endpoint /members/{id} => Player={id..., team={id..., members=null}}
Thank you!
EDIT : maybe the question isn't very clear giving the answers I get so I'll try to be more precise.
I know that it is possible to prevent the infinite recursion either with Jackson (#JSONIgnore, #JsonManagedReference/#JSONBackReference etc.) or by doing some mapping into DTO. The problem I still see is this : both of the above are post-query processing. The object that Spring JPA returns will still be (for example) a Team, containing a list of players, containing a team, containing a list of players, etc. etc.
I would like to know if there is a way to tell JPA or the repository (or anything) to not bind entities within entities over and over again?
Here is how I handle this problem in my projects.
I used the concept of data transfer objects, implemented in two version: a full object and a light object.
I define a object containing the referenced entities as List as Dto (data transfer object that only holds serializable values) and I define a object without the referenced entities as Info.
A Info object only hold information about the very entity itself and not about relations.
Now when I deliver a Dto object over a REST API, I simply put Info objects for the references.
Let's assume I deliever a PlayerDto over GET /players/1:
public class PlayerDto{
private String playerName;
private String playercountry;
private TeamInfo;
}
Whereas the TeamInfo object looks like
public class TeamInfo {
private String teamName;
private String teamColor;
}
compared to a TeamDto
public class TeamDto{
private String teamName;
private String teamColor;
private List<PlayerInfo> players;
}
This avoids an endless serialization and also makes a logical end for your rest resources as other wise you should be able to GET /player/1/team/player/1/team
Additionally, the concept clearly separates the data layer from the client layer (in this case the REST API), as you don't pass the actually entity object to the interface. For this, you convert the actual entity inside your service layer to a Dto or Info. I use http://modelmapper.org/ for this, as it's super easy (one short method call).
Also I fetch all referenced entities lazily. My service method which gets the entity and converts it to the Dto there for runs inside of a transaction scope, which is good practice anyway.
Lazy fetching
To tell JPA to fetch a entity lazily, simply modify your relationship annotation by defining the fetch type. The default value for this is fetch = FetchType.EAGER which in your situation is problematic. That is why you should change it to fetch = FetchType.LAZY
public class TeamEntity {
#OneToMany(mappedBy = "team",fetch = FetchType.LAZY)
private List<PlayerEntity> members;
}
Likewise the Player
public class PlayerEntity {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "pla_fk_n_teamId")
private TeamEntity team;
}
When calling your repository method from your service layer, it is important, that this is happening within a #Transactional scope, otherwise, you won't be able to get the lazily referenced entity. Which would look like this:
#Transactional(readOnly = true)
public TeamDto getTeamByName(String teamName){
TeamEntity entity= teamRepository.getTeamByName(teamName);
return modelMapper.map(entity,TeamDto.class);
}
In my case I realized I did not need a bidirectional (One To Many-Many To One) relationship.
This fixed my issue:
// Team Class:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<Player> members = new HashSet<Player>();
// Player Class - These three lines removed:
// #ManyToOne
// #JoinColumn(name = "pla_fk_n_teamId")
// private Team team;
Project Lombok might also produce this issue. Try adding #ToString and #EqualsAndHashCode if you are using Lombok.
#Data
#Entity
#EqualsAndHashCode(exclude = { "members"}) // This,
#ToString(exclude = { "members"}) // and this
public class Team implements Serializable {
// ...
This is a nice guide on infinite recursion annotations https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion
You can use #JsonIgnoreProperties annotation to avoid infinite loop, like this:
#JsonIgnoreProperties("members")
private Team team;
or like this:
#JsonIgnoreProperties("team")
private List<Player> members;
or both.
I´m using Spring JpaRepository to access the database. My goal is to create a method which finds one entity and fully initializes it. Currently I´m doing it like that:
Hibernate.initialize(business.getCollectionA());
Hibernate.initialize(business.getCollectionB());
Hibernate.initialize(business.getCollectionC());
So I search for a method which initializes all collections at once like that:
Hibernate.initializeAll(business);
As such Hibernate or JPA does not provide any utility to initialize all lazy properties for the entity.
You need to write your recursive logic, using Java Reflection to traverse the tree and initialize the objects.
You can find here more or less what you want.
You can mark you collection properties as FetchType.EAGER to make them loaded automatically as soon as the Entity is loaded.
e.g.
#OneToMany(fetch=FetchType.EAGER)
private Set collectionA;
Add this fetchtype to any collection you want "initialized". Note that this kills performance, but it has the same effect as invoking initialize on each collection.
How about this:
import org.hibernate.SessionFactory;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.type.CollectionType;
import org.hibernate.type.Type;
// you should already have these somewhere:
SessionFactory sessionFactory = ...
Session session = ...
MyEntity myEntity = ...
// this fetches all collections by inspecting the Hibernate Metadata.
ClassMetadata classMetadata = sessionFactory.getClassMetadata(MyEntity.class);
String[] propertyNames = classMetadata.getPropertyNames();
for (String name : propertyNames)
{
Object propertyValue = classMetadata.getPropertyValue(myEntity, name, EntityMode.POJO);
Type propertyType = classMetadata.getPropertyType(name);
if (propertyValue != null && propertyType instanceof CollectionType)
{
CollectionType s = (CollectionType) propertyType;
s.getElementsIterator(propertyValue, session); // this triggers the loading of the data
}
}
Suppose we have a bunch of Entity classes that have mappings between eachoter:
#Entity
#Table(name = "legacy")
public class Legacy {
// Mappings to a bunch of other different Entities
}
#Entity
#Table(name = "new_entity")
public class NewEntity {
private Legacy legacy;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "legacy_id", referencedColumnName = "id")
public Legacy getLegacy() {
return legacy;
}
public Legacy setLegacy(Legacy legacy) {
this.legacy = legacy;
}
// Mappings to other new stuff
}
We can use the Configuration class in hibernate to generate creation scripts for some annotated classes:
Configuration config = new Configuration();
Properties properties = new Properties();
properties.put("hibernate.dialect", "org.hibernate.dialect.SQLServer2005Dialect");
config.setProperties(properties);
config.addAnnotatedClass(NewEntity.class)
String[] schema =
config.generateSchemaCreationScript(new SQLServer2005Dialect());
for (String table : schema) {
System.out.println(table);
}
This however will fail because the class Legacy has not been added to the configuration. However, if I do that I need to add a bunch of other legacy classes (which all already have "working" mappings and tables.
Is there a way to only generate scripts for the NewEntity without having to add all the mappings for Legacy? Right now I generate the script for NewEntity by commenting the Legacy mappings and then manually add them back.
If your NewEntity references and interact with the Legacy objects in it's mapped relationships you need to map it.
How is you hibernate operations work anyway if they aren't already mapped?
If you mean that you want an update script for the existing schema instead of generating a new creation db script try the generateSchemaUpdateScript method.
Hi I have two classes like this:
public class Indicator implements Serializable {
...
#OneToMany(mappedBy = "indicator",fetch=FetchType.LAZY)
private List<IndicatorAlternateLabel> indicatorAlternateLabels;
public List<IndicatorAlternateLabel> getIndicatorAlternateLabels() {
return indicatorAlternateLabels;
}
public void setIndicatorAlternateLabels(List<IndicatorAlternateLabel> indicatorAlternateLabels) {
this.indicatorAlternateLabels = indicatorAlternateLabels;
}
...
}
public class IndicatorAlternateLabel implements Serializable {
...
#ManyToOne(cascade = CascadeType.REFRESH, fetch = FetchType.EAGER)
#JoinColumn(name = "IndicatorID")
#XmlTransient
private Indicator indicator;
...
}
When I use them like this:
public MetricTypeDetail getMetricTypeDetail(Integer metricTypeId) {
Criteria crit = sessionFactory.getCurrentSession().createCriteria(Indicator.class, "sub")
.add(Restrictions.eq("number", metricTypeId))
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).setCacheable(true);
crit.setMaxResults(1);
Indicator indicator=(Indicator) crit.uniqueResult();
MetricTypeDetail metricTypeDetail=new MetricTypeDetail(indicator);
List<IndicatorAlternateLabel> indicatorAlternateLabels = null;
indicatorAlternateLabels=indicator.getIndicatorAlternateLabels();
metricTypeDetail.setIndicatorAlternateLabels(indicatorAlternateLabels);
return metricTypeDetail;
}
This code returns an exception:
failed to lazily initialize a collection of role: com.porism.service.domain.Indicator.indicatorAlternateLabels, no session or session was closed
Any idea? I'm very new to Hibernate
Lazy exceptions occur when you fetch an object typically containing a collection which is lazily loaded, and try to access that collection.
You can avoid this problem by
accessing the lazy collection within a transaction.
Initalizing the collection using Hibernate.initialize(obj);
Fetch the collection in another transaction
Use Fetch profiles to select lazy/non-lazy fetching runtime
Set fetch to non-lazy (which is generally not recommended)
Further I would recommend looking at the related links to your right where this question has been answered many times before. Also see Hibernate lazy-load application design.
It's possible that you're not fetching the Joined Set. Be sure to include the set in your HQL:
public List<Node> getAll() {
Session session = sessionFactory.getCurrentSession();
Query query = session.createQuery("FROM Node as n LEFT JOIN FETCH n.nodeValues LEFT JOIN FETCH n.nodeStats");
return query.list();
}
Where your class has 2 sets like:
public class Node implements Serializable {
#OneToMany(fetch=FetchType.LAZY)
private Set<NodeValue> nodeValues;
#OneToMany(fetch=FetchType.LAZY)
private Set<NodeStat> nodeStats;
}
Try swich fetchType from LAZY to EAGER
...
#OneToMany(fetch=FetchType.EAGER)
private Set<NodeValue> nodeValues;
...
But in this case your app will fetch data from DB anyway.
If this query very hard - this may impact on performance.
More here:
https://docs.oracle.com/javaee/6/api/javax/persistence/FetchType.html
==> 73
as suggested here solving the famous LazyInitializationException is one of the following methods:
(1) Use Hibernate.initialize
Hibernate.initialize(topics.getComments());
(2) Use JOIN FETCH
You can use the JOIN FETCH syntax in your JPQL to explicitly fetch the child collection out. This is somehow like EAGER fetching.
(3) Use OpenSessionInViewFilter
LazyInitializationException often occurs in the view layer. If you use Spring framework, you can use OpenSessionInViewFilter. However, I do not suggest you to do so. It may leads to a performance issue if not used correctly.
I know that deleting orphaned child objects is a common question on SO and a common problem for people new to Hibernate, and that the fairly standard answer is to ensure that you have some variation of cascade=all,delete-orphan or cascade=all-delete-orphan on the child collection.
I'd like to be able to have Hibernate detect that child collection has been emptied/removed from the parent object, and have the rows in the child table deleted from the database when the parent object is updated. For example:
Parent parent = session.get(...);
parent.getChildren().clear();
session.update(parent);
My current mapping for the Parent class looks like:
<bag name="children" cascade="all-delete-orphan">
<key column="parent_id" foreign-key="fk_parent_id"/>
<one-to-many class="Child"/>
</bag>
This works fine for me when updating an attached object, but I have a use case in which we'd like to be able to take a detached object (which has been sent to our API method by a remote client over HTTP/JSON), and pass it directly to the Hibernate Session - to allow clients to be able to manipulate the parent object in whichever way they like and have the changes persisted.
When calling session.update(parent) on my detached object, the rows in the child table are orphaned (the FK column is set to null) but not deleted. Note that when I'm calling session.update(), this is the first time the Hibernate Session is seeing this object instance - I am not re-attaching or merging the object with the Session in any other way. I'm relying on the client to pass objects whose identifiers correspond to actual objects in the database. For example, the logic in my API service method is something like this:
String jsonString = request.getParameter(...);
Parent parent = deserialize(jsonString);
session.update(parent);
Is it possible for Hibernate to detect orphaned children collections in detached parent objects when passed to session.update(parent)? Or am I mis-using the detached object in some way?
My hope was that I could avoid any sort of complex interactions with Hibernate to persist changes to a detached instance. My API method has no need to further modify the detached object after the call to session.update(parent), this method is merely responsible for persisting changes made by remote client applications.
Your mapping (simplified)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="br.com._3988215.model.domain">
<class name="Parent" table="PARENT">
<id name="id">
<generator class="native"/>
</id>
<bag cascade="all,delete-orphan" name="childList">
<key column="PARENT_ID" not-null="false"/>
<one-to-many class="Child"/>
</bag>
</class>
<class name="Child" table="CHILD">
<id name="id" column="CHILD_ID">
<generator class="native"/>
</id>
</class>
</hibernate-mapping>
produces
PARENT
ID
CHILD
CHILD_ID
PARENT_ID
According what you said
I would like to be able to have Hibernate detect that child collection has been removed from the parent object, and have the rows in the child table deleted from the database when the parent object is updated
Something like
Parent parent = session.get(...);
parent.getChildren().clear();
session.update(parent);
You said it works fine because you have an attached Parent instance
Now let's see the following one (Notice Assert.assertNull(second))
public class WhatYouWantTest {
private static SessionFactory sessionFactory;
private Serializable parentId;
private Serializable firstId;
private Serializable secondId;
#BeforeClass
public static void setUpClass() {
Configuration c = new Configuration();
c.addResource("mapping.hbm.3988215.xml");
sessionFactory = c.configure().buildSessionFactory();
}
#Before
public void setUp() throws Exception {
Parent parent = new Parent();
Child first = new Child();
Child second = new Child();
Session session = sessionFactory.openSession();
session.beginTransaction();
parentId = session.save(parent);
firstId = session.save(first);
secondId = session.save(second);
parent.getChildList().add(first);
parent.getChildList().add(second);
session.getTransaction().commit();
session.close();
}
#Test
public void removed_second_from_parent_remove_second_from_database() {
Parent parent = new Parent();
parent.setId((Integer) parentId);
Child first = new Child();
first.setId((Integer) firstId);
/**
* It simulates the second one has been removed
*/
parent.getChildList().add(first);
Session session = sessionFactory.openSession();
session.beginTransaction();
session.update(parent);
session.getTransaction().commit();
session.close();
session = sessionFactory.openSession();
session.beginTransaction();
Child second = (Child) session.get(Child.class, secondId);
Assert.assertNull(second);
session.getTransaction().commit();
session.close();
}
}
Unfortunately, the test do not pass. What you can do ???
Enable a long-running conversation
Hibernate reference says
Extended (or Long) Session - The Hibernate Session may be disconnected from the underlying JDBC connection after the database transaction has been committed, and reconnected when a new client request occurs. This pattern is known as session-per-conversation and makes even reattachment unnecessary. Automatic versioning is used to isolate concurrent modifications and the Session is usually not allowed to be flushed automatically, but explicitely.
disclaimer: i do not have any scenario which uses long running conversation. Java EE Stateful session beans support long running conversation. But its support is for JPA (not Hibernate)
Or you can create an alternative mapping which enables your Child as composite elements. Because its lifecycle depends on the parent object, you can rely on composite elements to get what you want
Create a class named AlternativeParent which extends Parent
public class AlternativeParent extends Parent {}
Now its mapping (Notice Child as composite element instead of plain #Entity)
<class name="AlternativeParent" table="PARENT">
<id name="id">
<generator class="native"/>
</id>
<bag name="childList" table="CHILD">
<key column="PARENT_ID" not-null="false"/>
<composite-element class="Child">
<property column="CHILD_ID" name="id"/>
</composite-element>
</bag>
</class>
Now implement a convenient equals method in the Child class
public boolean equals(Object o) {
if (!(o instanceof Child))
return false;
Child other = (Child) o;
// identity equality
// Used by composite elements
if(getId() != null) {
return new EqualsBuilder()
.append(getId(), other.getId())
.isEquals();
} else {
// object equality
}
}
If i refactor the test case shown above (Now by using AlternativeParent instead)
#Test
public void removed_second_from_parent_remove_second_from_database() {
AlternativeParent parent = new AlternativeParent();
parent.setId((Integer) parentId);
Child first = new Child();
first.setId((Integer) firstId);
/**
* It simulates the second one has been removed
*/
parent.getChildList().add(first);
Session session = sessionFactory.openSession();
session.beginTransaction();
session.update(parent);
session.getTransaction().commit();
session.close();
session = sessionFactory.openSession();
session.beginTransaction();
Child second = (Child) session.get(Child.class, secondId);
Assert.assertNull(second);
session.getTransaction().commit();
session.close();
}
I see a green bar
i think,when using detached session, you might face problem, with collections. i will suggest you to first load the entity with collection, and then update that entity with the changes, that will help.