Why do I have to evict my entity from the EclipseLink shared cache before querying with an entity graph will load my lazy properties? - jpa

Before updating an entity in my Jakarta EE application running on GlassFish 5.1.0 with EclipseLink 2.7.4 and Derby 10.14.2.0, I compare the updated entity to the saved entity and document the changes. I noticed recently that my compare code was not working with #OneToMany relationship properties and #ElementCollection properties, and I tracked the problem to Lazy loading of the #OneToMany and #ElementCollection properties. I was able to resolve the using the fetch attribute as follows:
Fetch Eager Entity
#Entity
public class Container implements Serializable {
#OneToMany(mappedBy = "container", fetch = FetchType.EAGER)
private List<AssetSerial> assets;
#ElementCollection (fetch = FetchType.EAGER)
private List<Reference> references;
I wasn't entirely happy with this solution, because I assumed that the developers defaulted these relationship types to lazy loading for a purpose, so I continued researching and was excited to find many references to JPA Entity Graphs. I immediately create the following code to force EclipseLink to initialize my lazy loading properties before documenting the entity changes.
Entity Graph Entity
#Entity
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
#NamedEntityGraph(
name = "Container.eager",
attributeNodes = {
#NamedAttributeNode("assets"),
#NamedAttributeNode("references") })
public class Container implements Serializable {
Entity Manager Initialization
#PersistenceContext(unitName = "MYPU")
private EntityManager em;
Find Method that only Works once per Entity
Map<String, Object> props = new HashMap<String, Object>();
props.put("javax.persistence.loadgraph", em.getEntityGraph("Container.eager"));
Container managedContainer = em.find(Container.class, updatedContainer.getId(), props);
PersistenceUnitUtil tester = em.getEntityManagerFactory().getPersistenceUnitUtil();
logger.debug("Assets: {}", tester.isLoaded(managedContainer, "assets"));
logger.debug("References: {}", tester.isLoaded(managedContainer, "references"));
Unfortunately, the isLoaded test methods only return true the first time I call the find method on a specific entity. The second and subsequent times isLoaded returns false. I struggled with this issue for many hours and determined that this issue was that the EclipseLink shared cache was not honoring the entity graph hint I was passing to the find method. I solved the problem by evicting the entity from the cache immediately before calling the find as shown below.
Find Method that Works
em.getEntityManagerFactory().getCache().evict(Container.class, updatedContainer.getId());
Map<String, Object> props = new HashMap<String, Object>();
props.put("javax.persistence.loadgraph", em.getEntityGraph("Container.eager"));
Container managedContainer = em.find(Container.class, updatedContainer.getId(), props);
PersistenceUnitUtil tester = em.getEntityManagerFactory().getPersistenceUnitUtil();
logger.debug("Assets: {}", tester.isLoaded(managedContainer, "assets"));
logger.debug("References: {}", tester.isLoaded(managedContainer, "references"));
Now the isLoaded test always returns true, and I'm able to document all the changed in the updated entity.
In summary, I have the following questions:
Why is EclipseLink not honoring my entity graph?
Am I going to encountering problems manually evicting my entity from the cache?
Is there a better way to force EclipseLink to initialize my lazy loading properties?

Related

inject a JPA repository in Spring Boot test, without session issue

I am adding some tests on my Spring Boot 2.4 application that works well in production.
In one of my SpringBootTest , I call the API (using mockMvc) and compare the result with what I have in the DB.
#SpringBootTest
#AutoConfigureMockMvc
#ActiveProfiles("test")
class TicketIT {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper objectMapper;
#Autowired
private TicketTypeRepository ticketTypeRepository;
#Test
void shouldReturnListOfTicketTypes() throws Exception {
RequestBuilder request =
MockMvcRequestBuilders.get(RESOURCE_BASE_URL + "/types").contentType(APPLICATION_JSON);
String responseAsString =
mockMvc
.perform(request)
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
List<TicketTypesRepresentation> ticketTypes =
objectMapper.readValue(
responseAsString, new TypeReference<List<TicketTypesRepresentation>>() {
});
assertThat(ticketTypes).hasSameSizeAs(ticketTypeRepository.findAll());
}
}
I have the feeling I've written that type of tests hundreds of times, but on this one, I am facing a problem : my application is configured correctly, because I receive a list of items in the API response.
However, what I find strange is that I get an exception from the ticketTypeRepository.findAll() call :
failed to lazily initialize a collection of role ... could not initialize proxy - no Session
I understand the issue, and I can fix it either by making the relation eager (with #Fetch(FetchMode.JOIN) on the entity), or my making the test #Transactional but I am not sure I like any of the options..
I don't remember facing that issue in the past in other Spring Boot tests so I am a bit puzzled.
Am I missing something to make sure that all the calls made to ticketTypeRepository are made within a transaction ? TicketTypeRepository is a wrapper around a CrudRepository, is it the reason why it doesn't work directly ?
Here's the entity and repository code :
public class JpaTicketTypeRepository implements TicketTypeRepository {
public List<TicketType> findAll() {
var allTicketTypesEntity= jpaTicketTypesEntityRepository.findAll();
return StreamSupport.stream(allTicketTypesEntity.spliterator(), false)
.map(TicketTypeEntity::toTicketTypeList)
.collect(Collectors.toList())
.stream().flatMap(List::stream)
.collect(Collectors.toList());
}
}
and the entity (simplified) :
#Table(name = "TICKET_TYPES")
#Entity
#Slf4j
public class TicketTypeEntity {
#Id
private Long id;
#OneToMany
#JoinTable(name = "TICKET_TYPES_GROUPS",
joinColumns =
{#JoinColumn(name = "TICKET_TYPE_ID", referencedColumnName = "ID")},
inverseJoinColumns =
{#JoinColumn(name = "TICKET_GROUP_ID", referencedColumnName = "ID")})
#Nonnull
private List<TicketGroupsEntity> ticketGroupsEntity;
#Nonnull
public List<TicketType> toTicketTypeList() {
log.info("calling toTicketTypeList for id "+id);
log.info(" with size : "+ticketGroupsEntity.size());
return ticketGroupsEntity.stream().map(group -> TicketType.builder()
.id(id)
.build()).collect(Collectors.toList());
}
}
The exception happens the first time size() is called on the collection :
failed to lazily initialize a collection of role:
my.service.database.entities.TicketTypeEntity.ticketGroupsEntity,
could not initialize proxy - no Session
org.hibernate.LazyInitializationException: failed to lazily initialize
a collection of role:
my.service.database.entities.TicketTypeEntity.ticketGroupsEntity,
could not initialize proxy - no Session at
org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:606)
at
org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:218)
at
org.hibernate.collection.internal.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:162)
at
org.hibernate.collection.internal.PersistentBag.size(PersistentBag.java:371)
at
my.service.database.entities.TicketTypeEntity.toTicketTypeList(TicketTypeEntity.java:78)
at
java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
at
java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655)
at
java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at
java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at
java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
at
java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at
java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
at
my.service.database.JpaTicketTypeRepository.findAll(JpaTicketTypeRepository.java:29)
I believe you mis-interpret the stack trace. The problem is not in calling the method size() on the result of findAll(), but in the method findAll itself.
In findAll you call TicketTypeEntity.toTicketTypeList, which converts DB entity to DTO. This method touches ticketGroupsEntity, which is a lazy collection.
The code fails in unit test, but runs when accessed via springs controller.
This is due to Open Session In View, which is enabled by default.
See:
A Guide to Spring’s Open Session In View
The Open Session In View Anti-Pattern
You could solve it multiple ways:
#Transactional findAll (be aware of lazy loading issues)
explicit fetch in query
entityGraph
But to my eyes your entity mapping looks suspicious, you seem to have all data needed to construct TicketType in TicketGroupsEntity. Maybe you could query that entity instead?

Multiple representations of the same entity are being merged with #OneToMany

I have a web application built with Spring Boot connected with a db PostgreSql, the project is about a education institute and manage Students and invoicing...
I generate manually all the invoices. I add the first cuota(invoice) normally but when i want to generate the second, i've this issue:
java.lang.IllegalStateException: Multiple representations of the same entity [com.codeboros.app.entity.Cuota#1] are being merged. Detached: [com.codeboros.app.entity.Cuota#1788e1df]; Managed: [com.codeboros.app.entity.Cuota#2697e3fc]
I've this entities:
#Entity
#Table(name="ALUMNOS")
public class Alumno implements Serializable {
#OneToMany(mappedBy="alumno", cascade= {CascadeType.DETACH,CascadeType.PERSIST,CascadeType.DETACH,CascadeType.REMOVE,CascadeType.REFRESH,CascadeType.MERGE}, fetch=FetchType.LAZY)
#NotFound(action = NotFoundAction.IGNORE)
#JsonIgnore
private List<Cuota> cuotas;
}
#Entity
#DiscriminatorValue(value="Cuota")
public class Cuota extends Factura implements Serializable {
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="alumno_cod")
#NotFound(action = NotFoundAction.IGNORE)
Alumno alumno;
}
And the AlumnoController
#PostMapping("/alumnoGenCuota/{id}")
public String GenCuota(#PathVariable Long id, Cuota cuota) {
Alumno alumno = alumnoService.get(id);
cuota.setAlumno(alumno);
cuota.setMonto(alumno.getCurso().getCuota());
cuota.setDetalle(alumno.getCurso().getNombre()+": $"+alumno.getCurso().getCuota()); //detalle
alumno.AgregarCuota(cuota);
alumnoService.save(alumno);
return "redirect:/alumnocuotas/"+id;
}
I tried to remove CascadeType.MERGE but do not save the news Cuotas
If you are not using Hibernate, remove CascadeType.MERGE from the entity that is not allowing you to persist the detached entity or put all cascade type other than CascadeType.MERGE
If you are using Hibernate, add the following lines to your persistence.xml -
<property name="hibernate.event.merge.entity_copy_observer" value="allow"/>
When you set hibernate.event.merge.entity_copy_observer=allow, Hibernate will merge each entity copy detected while cascading the merge operation. In the process of merging each entity copy, Hibernate will cascade the merge operation from each entity copy to its assocations with cascade=CascadeType.MERGE or CascadeType.ALL. The entity state resulting from merging an entity copy will be overwritten when another entity copy is merged.

How to filter child entities collections with predicate?

I have an entity service on which I need to filter a collection of child entity, based on a list of id's. My service have a public method which receive the id of the parent entity and a list of id's of some of his children entities.
By default, I know that JPA will fetch all related entities and this his the actual behavior. But we need to work on the performance of the service. So instead of getting all related entities and filter them with many loop (filter on id's and also on other properties like date property), I want to get only entities concerned by my request.
My Parent entity
#Entity
#Table(name = "MyParent")
public class MyParentEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "SEQ_MyParent")
#SequenceGenerator(allocationSize = 1, name = "SEQ_MyParent",
sequenceName = "SEQ_MyParent")
#Column(name = "ID_PARENT")
private Long id;
#OneToMany(mappedBy = "myParent", cascade = CascadeType.ALL,
fetch = FetchType.EAGER, orphanRemoval = true)
private final List<MyChildEntity> myChild = new ArrayList<MyChildEntity>();
}
My Child Entity
#Entity
#Table(name = "MyChild")
public class MyChildEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "SEQ_MyChild")
#SequenceGenerator(allocationSize = 1, name = "SEQ_MyChild",
sequenceName = "SEQ_MyChild")
#Column(name = "ID_CHILD")
private Long id;
#ManyToOne
#JoinColumn(name = "ID_PARENT")
private MyParentEntity myParent;
}
I'm using Spring-data CrudRepository to get data from my DB and I also extends JpaSpecificationExecutor to use Predicate.
public interface MyParentRepository extends CrudRepository<MyParentEntity, Long>,
JpaSpecificationExecutor<MyParentEntity> {
}
This let me use CrudRepository findOne() method but with a Specification object instead of the regular Long parameter.
Also, I combine multiples Specification's object with the following call:
this.myParentRepository.findOne(Specifications
.where(firstSpecification(parentId))
.and(secondSpecification(childrenIdsList)));
I created a simple junit test with one Parent linked to two children entities. In my request, I'm able to get the parent entity with the provided Id. But even if I provide the child id, I always get both children entities in the list inside the parent.
In my method which return a new Specification object, in which the toPredicate method is override, I'm unable to create a Predicate that will filter my children collection and only get those one I'm interested. I know that the Hibernate Criteria has the possibility to add "Restrictions" but this is not available in the CriteriaBuilder that is provided with the toPredicate method.
public static Specification<MyParentEntite> firstSpecification(final Long id) {
return new Specification<MyParentEntite>() {
#Override
public Predicate toPredicate(Root<MyParentEntite> root,
CriteriaQuery<?> query, CriteriaBuilder cb) {
Predicate predicate = cb.equal(root.get(MyParentEntity_.id), id);
return cb.and(predicate);
}
};
}
public static Specification<MyParentEntite> secondSpecification(final List<Long> ids) {
return new Specification<MyParentEntite>() {
#Override
public Predicate toPredicate(Root<MyParentEntite> root,
CriteriaQuery<?> query, CriteriaBuilder cb) {
Root<MyChildEntity> child = query.from(MyChildEntity.class);
Expression<Long> exp = child.get(MyChildEntity_.id);
Predicate p = exp.in(ids);
return cb.and(p);
}
};
}
In the secondSpecification() method, I also tried to use ListJoin instead of Root directly in the Entity. I searched in other questions here but it seems that this concern is solved with the Hibernate Criteria restrictions or with a LeftJoin, which I tried in my ListJoin in specifing JoinType.LEFT parameter.
Here are links to already tested solutions whitout success :
JPA CriteriaBuilder - How to use "IN" comparison operator
JPA2 Criteria-API: select... in (select from where)
I want to mention that I'm relatively new with Criteria API and Predicate. Maybe I'm missing something that is simple but that is obvious to experienced JPA developpers!
Thanks a lot for your help!
Finally, I found a way to resolved my issue. Requesting only partial collections of sub-entities is something that we found dangerous in terms of data integrity. If a remote service calls to request my parent entity with a partial collection of children's entities within a get, this parent entity object may be return for a modify operation which will result in many "delete" calls on the removed instances of children entities. The persistence API will consider these missing children as relations that were removed, which is something we don't want.
I created a dummy transfert object which contains the partial collections of children's entities requested so this dummy transfert object can't not be use in a future modify operation call. The full version of the parent entity will be used for the "modify" purpose.
Is your JPA provider hibernate?Have you considered the filters in hibernate which can filter the child entities instead of removing them.But the filter usage is somehow very difficult to understand!

JPA not updating ManyToMany relationship in returning result

Here are my entities:
#Entity
public class Actor {
private List<Film> films;
#ManyToMany
#JoinTable(name="film_actor",
joinColumns =#JoinColumn(name="actor_id"),
inverseJoinColumns = #JoinColumn(name="film_id"))
public List<Film> getFilms(){
return films;
}
//... more in here
Moving on:
#Entity
public class Film {
private List actors;
#ManyToMany
#JoinTable(name="film_actor",
joinColumns =#JoinColumn(name="film_id"),
inverseJoinColumns = #JoinColumn(name="actor_id"))
public List<Actor> getActors(){
return actors;
}
//... more in here
And the join table:
#javax.persistence.IdClass(com.tugay.sakkillaa.model.FilmActorPK.class)
#javax.persistence.Table(name = "film_actor", schema = "", catalog = "sakila")
#Entity
public class FilmActor {
private short actorId;
private short filmId;
private Timestamp lastUpdate;
So my problem is:
When I remove a Film from an Actor and merge that Actor, and check the database, I see that everything is fine. Say the actor id is 5 and the film id is 3, I see that these id 's are removed from film_actor table..
The problem is, in my JSF project, altough my beans are request scoped and they are supposed to be fetching the new information, for the Film part, they do not. They still bring me Actor with id = 3 for Film with id = 5. Here is a sample code:
#RequestScoped
#Named
public class FilmTableBackingBean {
#Inject
FilmDao filmDao;
List<Film> allFilms;
public List<Film> getAllFilms(){
if(allFilms == null || allFilms.isEmpty()){
allFilms = filmDao.getAll();
}
return allFilms;
}
}
So as you can see this is a request scoped bean. And everytime I access this bean, allFilms is initially is null. So new data is fetched from the database. However, this fetched data does not match with the data in the database. It still brings the Actor.
So I am guessing this is something like a cache issue.
Any help?
Edit: Only after I restart the Server, the fetched information by JPA is correct.
Edit: This does not help either:
#Entity
public class Film {
private short filmId;
#ManyToMany(mappedBy = "films", fetch = FetchType.EAGER)
public List<Actor> getActors(){
return actors;
}
The mapping is wrong.
The join table is mapped twice: once as the join table of the many-to-many association, and once as an entity. It's one or the other, but not both.
And the many-to-many is wrong as well. One side MUST be the inverse side and use the mappedBy attribute (and thus not define a join table, which is already defined at the other, owning side of the association). See example 7.24, and its preceeding text, in the Hibernate documentation (which also applies to other JPA implementations)
Side note: why use a short for an ID? A Long would be a wiser choice.
JB Nizet is correct, but you also need to maintain both sides of relationships as there is caching in JPA. The EntityManager itself caches managed entities, so make sure your JSF project is closing and re obtaining EntityManagers, clearing them if they are long lived or refreshing entities that might be stale. Providers like EclipseLink also have a second level cache http://wiki.eclipse.org/EclipseLink/Examples/JPA/Caching

Merging an object with FetchType.EAGER relation leads to "FailedObject"

I have an entity VM with a relationship to another entity BP. The relationship is eagerly fetched. First I load a VM. After loading the VM is detached, serialized and changed at the client side. Now I want to update the changed entity so I use the EntityManager.merge() method from JPA. Now I run into the following error from OpenJPA:
"Encountered new object in persistent field "Vm.bp" during attach. However, this field does not allow cascade attach. Set the cascade attribute for this field to CascadeType.MERGE or CascadeType.ALL (JPA annotations) or "merge" or "all" (JPA orm.xml). You cannot attach a reference to a new object without cascading."
Why do I have to add a Cascade.MERGE to a relationship to another entity that will never change? And why does JPA think that BP is a new object ("...cannot attach reference to a new object...")?
When using ManyToOne relationships do I always have to add Cascade.MERGE in order to update the entity or is this because of the EAGER fetch type?
Here's my entity:
#Entity
#Table(name = "VM")
public class Vm extends BaseEntity implements Serializable {
public static final long serialVersionUID = -8495541781540291902L;
#Id
#SequenceGenerator(name = "SeqVm", sequenceName = "SEQ_VM")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SeqVm")
#Column(name = "ID")
private Long id;
// lots of other fields and relations
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "BP_ID")
private Bp bp;
// ...
}
I found the reason why this error message comes up: The #Version annotated database field of the related Bp entity was initialized with "0". Apparently OpenJPA (1.2.3) is not able to cope with entity versions of zero.
Setting the version to 1 solved my issue.