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
}
}
Related
I try to create query setAll , because a entity with top isValid and I want all value to false before I use saveAll for udpade my database. I use this methods synchronize my database with a batch 6000-7000 lines actually.
import org.springframework.data.repository.CrudRepository;
public interface DomRepository extends CrudRepository<Dom, String> {
public Domaine findDomByName(String dom);
public List<Dom> findAll();
public void setIsValidAll(boolean isValid);
}
import javax.persistence.*;
import org.hibernate.annotations.DynamicUpdate;
import org.springframework.data.annotation.CreatedDate;
#Entity
#DynamicUpdate
public class Dom{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
// more attribute
private boolean isValid;
Caused by: org.springframework.data.mapping.PropertyReferenceException: No property 'setIsValidAll' found for type 'Dom'!
It's not entirely clear what you want to achieve based on the description you have provided but if I get this right, you would like to create a repository when that will effectively update the value of the isValid field for all Dom objects. If that is indeed the case then you could simply create a custom repository method that will accept your flag and will make use of a simple HQL or JPQL to update all records in the database. For example something like this could work for this case:
#Modifying
#Query("UPDATE Dom SET isValid = :valid")
void updateValidAll(boolean valid);
If on the other hand you want to perform the update in memory on the fetched entities and then perform a saveAll you could do something like this:
#Transactional
public void updateAllRecords2(boolean isValid) {
var records = repository.findAll()
.stream()
.peek(dom -> dom.setValid(isValid))
.toList();
repository.saveAll(records);
}
Note that the latter is not very optimal as it uses peek which should not be used on operations that have side-effects (i.e mutations). On top of that will will perform an update query for every single entity that is present, meaning N queries for N entities.
I have a development project using Spring Data JPA and MapStruct to map between Entities and DTOs. Last week I decided it was time to address the FetchType.EAGER vs LAZY issue I have postponed for some time. I choose to use #NamedEntityGraph and #EntityGraph to load properties when needed. However I am stuck with this LazyInitializationExeption problem when doing the mapping from entity to dto. I think I know where this happens but I do not know how to get passed it.
The code
#NamedEntityGraph(name="Employee.full", ...)
#Entity
public class Employee {
private Set<Role> roles = new HashSet<>();
}
#Entity
public class Role {
private Set<Employee> employees = new HashSet<>();
}
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
#EntityGraph(value = "Employee.full")
#Override
Page<Employee> findAll(Pageable pageable);
}
#Service
public class EmployeeService {
public Page<EmployeeDTO> findAll(PageRequest pageRequest) {
Page<Employee> employees = repository.findAll(pageRequest); // ok
Page<EmployeeDTO> dtos = employees.map(emp -> mapper.toDTO(emp, new CycleAvoidMappingContext()); // this is where the exception happens
return dtos;
}
}
// also there is EmployeeDTO and RoleDTO classes mirroring the entity classes
// and there is a simple interface EmployeeMapper loaded as a spring component
// without any special mappings. However CycleAvoidingMappingContext is used.
I have tracked down the LazyInitializationException to happen when the mapper tries to map the roles dependency. The Role object do have Set<Employee> and therefore there is a cyclic reference.
When using FetchType.EAGER new CycleAvoidingMappingContext() solved this problem, but with LAZY this no longer works.
Does anybody know how I can avoid the exception and at the same time get my DTOs mapped correctly?
The problem is that when the code returns from findAll the entities are not managed anymore. So you have a LazyInitializationException because you are trying, outside of the scope of the session, to access a collection that hasn't been initialized already.
Adding eager make it works because it makes sure that the collection has been already initialized.
You have two alternatives:
Using an EAGER fetch;
Make sure that the entities are still managed when you return from the findAll. Adding a #Transactional to the method should work:
#Service
public class EmployeeService {
#Transactional
public Page<EmployeeDTO> findAll(PageRequest pageRequest) {
Page<Employee> employees = repository.findAll(pageRequest);
Page<EmployeeDTO> dtos = employees.map(emp -> mapper.toDTO(emp, new CycleAvoidMappingContext());
return dtos;
}
}
I would say that if you need the collection initialized, fetching it eagerly (with an entity graph or a query) makes sense.
Check this article for more details on entities states in Hibernate ORM.
UPDATE: It seems that this error happens because Mapstruct is converting the collection even if you don't need it in the DTO.
In this case, you have different options:
Remove the field roles from the DTO. Mapstruct will ignore the field in the entity because the DTO doesn't have a field with the same name;
Create a different DTO class for this specific case without the field roles;
Use the #Mapping annotation to ignore the field in the entity:
#Mapping(target = "roles", ignore = true)
void toDTO(...)
or, if you need the toDTO method sometimes
#Mapping(target = "roles", ignore = true)
void toSkipRolesDTO(...) // same signature as toDTO
I'm trying to get Ignite Cache data through jdbc. For that purpose I define new custom class and annotate fields like that :
public class MyClass implements Serializable {
#QuerySqlField(index = true)
public Integer id;
#QuerySqlField(index = true)
public String records_offset;
#QuerySqlField(index = true)
public Integer session_id;
...
}
Then I start ignite in this way:
CacheConfiguration conf = new CacheConfiguration();
conf.setBackups(1);
conf.setName("test");
QueryEntity queryEntity = new QueryEntity();
queryEntity.setKeyType(Integer.class.getName());
queryEntity.setValueType(CDR.class.getName());
queryEntity.setTableName("CDR");
conf.setQueryEntities(Arrays.asList(queryEntity));
IgniteConfiguration iconf = new IgniteConfiguration();
iconf.setCacheConfiguration(conf);
iconf.setPeerClassLoadingEnabled(true);
this.ignite = Ignition.start(iconf);
this.cache = ignite.getOrCreateCache("test");
Now when I try to get data from JDBC, I get error:
Error: class org.apache.ignite.binary.BinaryObjectException: Custom objects are not supported (state=50000,code=0)
I could define a set of fields to get opportunity to fetch data from JDBC
LinkedHashMap<String, String> fields = new LinkedHashMap();
fields.put("session_id", Integer.class.getName());
fields.put("records_offset", String.class.getName());
queryEntity.setFields(fields);
But Why do I need to do this if I've already annotated field in class definition?
You have three options to define SQL schema:
Annotations and CacheConfiguration.setIndexedTypes
https://apacheignite.readme.io/docs/cache-queries#section-query-configuration-by-annotations
You can configure QueryEntity:
https://apacheignite.readme.io/docs/cache-queries#section-query-configuration-using-queryentity
or just use pure SQL:
https://apacheignite-sql.readme.io/docs/create-table
In your case, you mixed [1] and [2], so you registered key and value for indexing by QueryEntity, but defined fields with annotations, so mixing of different ways doesn't work. you need to stick to open specific way like you already did by adding key and value registration for indexing with CacheConfiguration.setIndexedTypes method. So you can get rid of QueryEntity now.
My object graph consists of Hibernate entities. Now most of the objects don't exist in the new database. However some do. So my object is like this:
Class MyObject{
Set<B> bset;
Set<C> cset;
}
The items in bset need to be instantiated and persisted after deserialization. However, the items in cset already exist in the new database, so I don't want new instances created. What is the best way to tell Jackson I know how to find references to these? Right now I am thinking about using a custom serializer / deserializer for cset, which will serialize it by creating an object with the database ID, and then will deserialize it by pulling the appropriate objects out of the database.
This is kind of complicated and I am hoping there is a simpler solution. Any suggestions?
Figured it out. There are three things I needed:
A JsonCreator to take the entityManager, and the id to return an object
#JsonCreator
#IgnoredMethod
public static UiElement findById(#JacksonInject EntityManager entityManager, #JsonProperty("id") int id) {
return entityManager.find(UiElement.class, id);
}
A JsonValue getter to return an object with only the id
#JsonValue
#Transient
public Map<String,Integer> getJsonObject(){
HashMap<String,Integer> map = new HashMap<String,Integer>();
map.put("id", getId());
return map;
}
The entity manager needed to be injected into the ObjectMapper
//entitymanager for creating any static data based entities
InjectableValues injects = new InjectableValues.Std().addValue(EntityManager.class, entityManager);
mapper.setInjectableValues(injects);
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.