I am working on a project using Hibernate 4.3.4 to access a Postgres DB. We have two entities which are linked via a ManyToMany Association.
The code and the associations currently work, in that adding an EntityB to EntityA's collection will automatically add the EntityA to the EntityB's collection once the Session is committed. However, the issue I'm having is that when I try to work on the EntityB's EntityAs, which should include the EntityA I just created, EntityA is not in that collection (It is empty). Example code is here:
#Entity
#Table(name = "entity_a")
public class EntityA {
private Set<EntityB> entityBs = new HashSet<EntityB>(0);
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "entitya_entityb",
joinColumns = { #JoinColumn(name = "entitya_id") },
inverseJoinColumns = { #JoinColumn(name = "entityb_id") })
public Set<EntityB> getEntityBs()
{
return entityBs;
}
public void setEntityBs(Set<EntityB> entityBs)
{
this.entityBs = entityBs;
}
}
#Entity
#Table(name = "entity_b")
public class EntityB {
private Set<EntityA> entityAs = new HashSet<EntityA>(0);
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "entitya_entityb",
joinColumns = { #JoinColumn(name = "entityb_id") },
inverseJoinColumns = { #JoinColumn(name = "entitya_id") })
public Set<EntityA> getEntityAs()
{
return entityAs;
}
public void setEntityAs(Set<EntityA> entityAs)
{
this.entityAs = entityAs;
}
}
/**
* HTTP REST Resource to create Entities and persist them. We do some basic logic when we create them to show the problem.
*/
#Path("/battleRhythm")
#Singleton
public class HttpResource
{
#POST
#Consumes("application/json")
public void createEntityA() {
Session hibernateSession = SessionFactory.getCurrentSession(); // SessionFactory specifics not included
hibernateSession.getTransaction().begin();
// Add an EntityB to the new EntityA
EntityA entityA0 = new EntityA();
EntityB entityB0 = new EntityB0();
entityA.getEntityBs().add(entityB0);
// Persist the new EntityA
EntityADao.getInstance().save(entityA0);
// Try to get this EntityA from EntityB
Set<EntityA> associatedEntityAs = entityB0.getEntityAs(); // Doesn't contain any EntityAs!
hibernateSession.getTransaction().commit();
}
}
Here's the question:
Can I make Hibernate automatically add the EntityA0 to EntityB0's collection when I save EntityA0, without committing the transaction? Is this possible? How?
Caveat : The example above does not fully reflect this, but we perform similar operations on both Entities, so having an "owner" in the traditional Hibernate sense (using the mappedBy = "" Attribute configuration) is not an ideal option. I don't want to try to convince everyone to only ever use EntityB.getEntityAs().add(EntityB0) in CreateEntityA(). It's too confusing.
You don't have the choice. There MUST be an owner side, and there MUST be an inverse side. And it's YOUR responsibility to maintain both sides of the association: don't expect to have B inside A's collection of Bs when you only add A to B (and vice-versa)
Now, nothing forbids you to have a methods addB(B b) inside A that adds b to A's collection of Bs, and which adds this to B's collection of As. And you can of course also have a method addA(A a) in B that does the same thing.
Related
I try to migrate a SpringBoot application to SpringBoot 3. Sprinboot 3 use Hibernate 6.
My application refuse to start because of the following error
Caused by: java.lang.NullPointerException: Cannot invoke "java.util.Map.get(Object)" because the return value of "java.util.Map.get(Object)" is null
at org.hibernate.envers.configuration.internal.metadata.AuditMetadataGenerator.addJoins(AuditMetadataGenerator.java:206)
at org.hibernate.envers.configuration.internal.metadata.AuditMetadataGenerator.generateSecondPass(AuditMetadataGenerator.java:409)
at org.hibernate.envers.configuration.internal.EntitiesConfigurator.configure(EntitiesConfigurator.java:86)
at org.hibernate.envers.boot.internal.EnversServiceImpl.initialize(EnversServiceImpl.java:129)
at org.hibernate.envers.boot.internal.AdditionalJaxbMappingProducerImpl.produceAdditionalMappings(AdditionalJaxbMappingProducerImpl.java:92)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:329)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:1350)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1421)
at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:66)
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:376)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:409)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:396)
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:352)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1797)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1747)
... 110 common frames omitted
After digging in the Envers code it appear that the problem is located in the org.hibernate.envers.configuration.internal.metadata.AuditMetadaGenerator class.
At line 337, there is a condition that let a audited class be referenced during the first pass of envers .
if ( entity.isJoinAware() ) {
final JoinAwarePersistentEntity joinAwareEntity = (JoinAwarePersistentEntity) entity;
createJoins( persistentClass, joinAwareEntity, auditingData );
addJoins( persistentClass, propertyMapper, auditingData, persistentClass.getEntityName(), mappingData, true );
}
private void createJoins(PersistentClass persistentClass, JoinAwarePersistentEntity entity, ClassAuditingData auditingData) {
final Iterator<org.hibernate.mapping.Join> joins = persistentClass.getJoinIterator();
final Map<org.hibernate.mapping.Join, Join> joinElements = new HashMap<>();
entityJoins.put( persistentClass.getEntityName(), joinElements );
....
This is this list that is called during the second pass line 206.
while ( joins.hasNext() ) {
final org.hibernate.mapping.Join join = joins.next();
final Join entityJoin = entityJoins.get( entityName ).get( join );
Here entityJoins.get(entityName) return null for one of my entity.
This entity is correctly annotated with #Audited and extend from another Entity like this:
#Entity
#Table(name = "a")
#Audited
#DiscriminatorValue("DISCRIMINATOR")
public class A extends B {
//...
}
#Entity
#Table(name = "b")
#Inheritance(strategy = InheritanceType.JOINED)
#Audited
public abstract class B {
//...
}
In my comprehension, specifying the Inheritance with a value of InheritanceType.JOINED make envers create a JoinedSubclassPersistentEntity that itself inherit from PersistentEntity.
This PersistentEntity has a method :
public boolean isJoinAware() {
return false;
}
that is overriden by it's children like RootPersistentEntity by :
#Override
public boolean isJoinAware() {
return true;
}
This override is not made by JoinedSubclassPersistentEntity which is the class generated when using Joined inheritence strategy.
This cause my entity to not be add to the first pass but still processed by the second pass.
So the question is ? Is it a bug in Envers ? Can i used Joined inheritence strategy with a #Audited class ?
It was working well in hibernate 5.6.
[Edit]:
I managed to reproduce the error with a simple test class :
#Entity
#Audited
#DiscriminatorValue("OPTION")
public class Child extends Parent{
private String propA;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "recursive_children_child", joinColumns = {#JoinColumn(name = "child_id", nullable = false, updatable = false)}, inverseJoinColumns = {#JoinColumn(name = "recursive_id", nullable = false, updatable = false)})
#NotAudited
#OrderBy("propA DESC")
private List<Child> children = new ArrayList<>();
#ManyToOne(fetch = FetchType.LAZY)
#JoinTable(name = "recursive_children_child", joinColumns = {#JoinColumn(name = "recursive_id")}, inverseJoinColumns = {#JoinColumn(name = "child_id")})
#NotAudited
private Child recursiveChild;
public Child(String propA) {
this.propA = propA;
}
public Child() {
}
public String getPropA() {
return propA;
}
public void setPropA(String propA) {
this.propA = propA;
}
}
The problem seems to be with the recursive relationship in the Child class. Ember try to audit the relationship despite the presence of #Audited annotation.
Link to a project that reproduce the bug => https://github.com/scandinave/envers6-migration-bug
I finally found the problem by reading the Jakarta Persistence Spec.
The JoinTable annotation is used in the mapping of entity associations. A JoinTable annotation is
specified on the owning side of the association
In my project there is a #JoinTable annotation on both side of the association. This was working before Hibernate 6, but not now. I just had to remove the #JoinTable not needed to solve the error.
I got an issue when trying to persist an item of a collection and one of its properties is an already persistent entity in a database.
Trying to clarify by this simplified example:
Class A has a List of class B (One to Many, mappedBy, CascadeType.PERSIST, CascadeType.MERGE)
Class B is related back to A (Many to One, JoinColumn)
Class B inherits from BParent (Single Table)
BParent is related to class C (Many to One, CascadeType.MERGE, CascadeType.PERSIST)
When i run ARepository.save(entity) passing a complete object to it (with a List of B including one B entity with a new C associated) it persists all entities normally.
When i run ARepository.save(entity) passing an object related with existing (with List of B including a B entity realted with a EXISTING C associated, i.e. id only) it triggers the dettached entity passed to persist error.
org.hibernate.PersistentObjectException: detached entity passed to persist: C
What im trying to do is to save a new related object (when it has no id) or associate to an existing object (when it has an id).
My respositories are all Spring Data JpaRepository interfaces. My code goes like this (getters and setters ommited):
#Entity
public class A {
#OneToMany(mappedBy = "bEntity", cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
private List<B> bList = new ArrayList<>();
}
#Entity
#DiscriminatorValue("B")
public class B extends BParent {
#ManyToOne
#JoinColumn(name = "CVLE_CVL_UUID")
private A bEntity;
}
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "CVLE_TIPO")
#Table( name = "TAB_CONTA_VINCULADA_LANC_EMPREGADO")
public abstract class BParent {
#Id
private UUID id;
#ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST})
#JoinColumn(name = "CVLE_EMP_UUID")
private C cEntity;
}
#Entity
#Table( name = "TAB_EMPREGADO")
public class C {
#Id
#Column(name = "EMP_UUID")
private UUID id;
}
I hope i could explain it right (english is not my first language). I would appreciate if someone could give me a hand on this, thank you.
I have a few entities linked as follows:
#Entity
#Table(name = "distribution_activity")
public class DistributionActivity extends AbstractActivity {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "activity", orphanRemoval = true)
protected Set<DistributionTask> tasks = new TreeSet<>();
...
}
and
#Entity
#Table(name = "distribution_task")
public class DistributionTask extends AbstractTask {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "activity_id")
protected DistributionActivity activity;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "store_id")
protected Store store;
...
}
and
#Entity
#Table(name = "store")
public class Store extends AbstractAuditableEntity {
#OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER, mappedBy = "store", orphanRemoval = true)
protected Set<DistributionTask> distributionTasks = new TreeSet<>();
...
}
The repositories are as follows:
#Repository
public interface DistributionActivityRepository extends PagingAndSortingRepository<DistributionActivity, Long> {
}
and
#Repository
public interface StoreRepository extends PagingAndSortingRepository<Store, Long> {
}
I'm using MySQL and the tables are generated WITHOUT any cascade options on the foreign keys. When I delete a DistributionActivity everything works fine and Hibernate actually issues delete statements for each of the linked tasks.
hibernate.SQL:109 - delete from distribution_task where id=? and version=?
hibernate.SQL:109 - delete from distribution_activity where id=? and version=?
When I delete a Store however, no delete statements are generated for the linked tasks and a MySQLIntegrityConstraintViolationException exception is thrown referring to a foreign key violation.
hibernate.SQL:109 - delete from store where id=? and version=?
Any clues?
Can you post the snippet of code around session.delete() where the deletion happens?
I cannot say it for sure but I have ran in similar issues before related to bidirectional relationships. Briefly speaking, I needed to make sure the objects are in sync when using bidirectional relationships.
It looks to me that you want to delete a Store that still have a reference in DistributionTask.
Example, if you have something like this:
session.delete(store);
Then, try changing for something like this:
distributionTask.setStore(null);
session.save(distributionTask);
session.delete(store);
So; you need control the consistency of your objects manually when dealing with bidirectional relationship.
I hope it helps. Cheers,
I've a weird problem loading some objects. I'm using JPA 1, hibernate-core version 3.3.0.SP1 and hibernate-entitymanager version 3.4.0.GA
Let's say I've these JPA entities:
#Entity
#Table(name = "SLC_ELE")
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(discriminatorType = DiscriminatorType.INTEGER, name = ElementoPrograma.C_ID_CTG_ELE)
public class Element {
...
}
#Entity
#Table(name = "SLC_ELE_ONE")
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorValue(Categories.ID_CTG_ONE)
public class ElementTypeOne extends Element {
...
}
#Entity
#Table(name = "SLC_ELE_TWO")
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorValue(Categories.ID_CTG_TWO)
public class ElementTypeTwo extends Element {
...
}
#Entity
#Table(name = ThreeElementExample.TABLENAME)
#AssociationOverrides({
#AssociationOverride(name = JpaMany3ManyEntity.ASOCIATION_OVERRIDE_ONE,
joinColumns =
#JoinColumn(name = Element.C_ID_ELE)),
#AssociationOverride(name = JpaMany3ManyEntity.ASOCIATION_OVERRIDE_TWO,
joinColumns =
#JoinColumn(name = OneEntity.C_ID_TWO)),
#AssociationOverride(name = JpaMany3ManyEntity.ASOCIATION_OVERRIDE_THREE,
joinColumns =
#JoinColumn(name = AnotherEntity.C_ID_THREE))})
public class ThreeElementExample extends JpaMany3ManyEntity<Element, OneEntity, AnotherEntity> {
...
}
The thing is, I'd like to obtain always the subclasses (meaning the ElementTypeOne, ElementTypeTwo instead the elements) when I load a collection of these entities. The problem is that the many to many relation always get the Element (the father instead the children)
Let's say I've an entity A containing a colection of Elements:
#Fetch(FetchMode.JOIN)
#OneToMany(cascade = CascadeType.ALL, mappedBy = "idEle")
private Collection<Element> elementCollection;
And if I get the collection, everything works fine (I get the subclasses as expected).
The problem comes when I've another entity B with a collection of the JpaMany3ManyEntity (notice that the same entity element is involved)
#OneToMany(cascade = CascadeType.ALL, mappedBy = JpaMany3ManyEntity.ASOCIATION_OVERRIDE_ONE, fetch = FetchType.LAZY)
private Collection<ThreeElementExample> threeElementExampleCollection;
If I loop the threeElementExampleCollection from class B before I try to obtain the elementCollection from class A, when I load the objects from the elementCollection
I obtain just the superclass (Element) objects instead the children.
I guess that, for any reason, the many to many relationship obtains always the Element objects (father) and saves them in the hibernate cache, but I need to avoid this behaviour.
Any ideas or workarround? Any kind of help would be really appreciated.
Thanks in advance.
EDIT: the many to many class:
#SuppressWarnings("serial")
#MappedSuperclass
#AssociationOverrides({
#AssociationOverride(name = JpaMany3ManyEntity.ASOCIATION_OVERRIDE_ONE,
joinColumns =
#JoinColumn(name = "changeMeWhenExtends")),
#AssociationOverride(name = JpaMany3ManyEntity.ASOCIATION_OVERRIDE_TWO,
joinColumns =
#JoinColumn(name = "changeMeWhenExtends")),
#AssociationOverride(name = JpaMany3ManyEntity.ASOCIATION_OVERRIDE_THREE,
joinColumns =
#JoinColumn(name = "changeMeWhenExtends"))})
public abstract class JpaMany3ManyEntity<A extends JpaBaseEntity, B extends JpaBaseEntity, C extends JpaBaseEntity> extends JpaBaseEntity {
public static final String ID_ATTNAME = "id";
public static final String ASOCIATION_OVERRIDE_ONE = JpaMany3ManyEntity.ID_ATTNAME + "." + JpaMany3ManyId.ID_ONE_ATTNAME;
public static final String ASOCIATION_OVERRIDE_TWO = JpaMany3ManyEntity.ID_ATTNAME + "." + JpaMany3ManyId.ID_TWO_ATTNAME;
public static final String ASOCIATION_OVERRIDE_THREE = JpaMany3ManyEntity.ID_ATTNAME + "." + JpaMany3ManyId.ID_THREE_ATTNAME;
...
}
Here's a workarround that works to me: Deproxy the entities.
Even having a parent proxy of the entity (jpa.inheritance.issue.Element_$$_javassist_1) if you deproxy it, you'll obtain the real entities (children).
Let's say you want to loop your (children) elements collection from the entity A and do something with them.
Something like:
public void loopDeproxyElements(List<Element> yourElementsCollection){
for(Element p : yourElementsCollection){
if(p instanceof HibernateProxy){
Element child = (Element) ((HibernateProxy) p).getHibernateLazyInitializer()
.getImplementation();
if (child instanceof ElementTypeOne){
//You can cast your object or do whatever you want, knowing for sure that's a child element)
ElementTypeOne myRealElement = (ElementTypeOne) child;
...
} else {
//It should be ElementTypeTwo (if u never create parent entities)
...
}
}
}
)
It will always get the children elements as I was expecting.
Try experiment with hibernate.default_batch_fetch_size property. By default it's set to 1. This will load only first entity from your collection. Increasing it to ~size of the collection might help.
I have a the two following classes:
#Entity
class A {
#Id
private aId;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "AB", joinColumns = #JoinColumn(name = "aId", referencedColumnName = "aId"), inverseJoinColumns = #JoinColumn(name = "bId", referencedColumnName = "bId"))
private Set<B> bSet;
}
#Entity
class B {
#Id
private bId;
}
I load the complete object structure from one database and then enters a new transaction on the second database to persist the structure again. However the "AB" table is left empty. This is very strange as "B" is persisted though I only explicitly persist "A". I have check that A-objects contains non empty sets of B, so that is not a problem.
This leaves me with the conclusion that Hibernate believes that "AB"-table should exist as both "A" and "B" already have their primary keys. Is there a way around this so I can get Hibernate to persist the join-table in the second database?
I guess this is happening because you are using proxy object.That is if you create instances of A and B with new operator and then call persist ,Join table record will be created .But you are using object from obtained from entitymanager(these are proxy objects) so you have to merge object that way entitymanager will create new proxies of this objects.