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.
Related
I had an existing class like this - a.b.c.Message.java
#Entity
#Table(name = "Message", indexes = { #Index(name = "messageIdIndex", columnList = "messageId"),
#Index(name = "customerIndex", columnList = "customer")})
public class Message extends BaseEntity {
///other fields and columns...
#JsonIgnore
#OneToMany(mappedBy = "msg",cascade = CascadeType.ALL)
private List<Application1> app1;
/// *newly added
#JsonIgnore
#OneToMany(mappedBy = "msg",cascade = CascadeType.ALL)
private List<Application2> app2;
/// setters and getters and toString() and equals() overridden
and second class: a.b.c.Application1.java
#Entity
#Table(name = "Application1", indexes = { #Index(name = "applicationIdIndex", columnList = "app1Id"),
#Index(name = "customerIndex", columnList = "customer")})
public class Application1 extends BaseEntity {
/// other fields and columns
#ManyToOne(cascade = { CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST,
CascadeType.REFRESH }, optional = true)
private Message msg;
///setter and getters and other methods
}
this works fine, however when I add another class - a.b.c.d.Application2.java:(* and the second field app2 in Message class is added )-
#Entity
#Table(name = "Application2", indexes = { #Index(name = "application2IdIndex", columnList = "app2Id"),
#Index(name = "customerIndex", columnList = "customer")})
public class Application2 extends BaseEntity {
/// other fields and columns
#ManyToOne(cascade = { CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST,
CascadeType.REFRESH }, optional = true)
private Message msg;
///setter and getters and other methods
}
now, when I try to create the EntityManagerFactory I get an exception like this -
Caused by: org.hibernate.AnnotationException: Use of #OneToMany or #ManyToMany targeting an unmapped class: a.b.c.Message.app2[a.b.c.d.Application2]
at org.hibernate.cfg.annotations.CollectionBinder.bindManyToManySecondPass(CollectionBinder.java:1196)
at org.hibernate.cfg.annotations.CollectionBinder.bindStarToManySecondPass(CollectionBinder.java:799)
at org.hibernate.cfg.annotations.CollectionBinder$1.secondPass(CollectionBinder.java:724)
at org.hibernate.cfg.CollectionSecondPass.doSecondPass(CollectionSecondPass.java:54)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1621)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1589)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:278)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:848)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:876)
at org.hibernate.jpa.HibernatePersistenceProvider.createEntityManagerFactory(HibernatePersistenceProvider.java:58)
... 46 common frames omitted
here's what I tried
read in some answers to a similar issue to check if the Entity annotation is from javax.persistence package instead of the hibernate package
missing Entity annotation - not the case
i have some doubts that this could be beacuse my new class in inside a sub package ,but the exception was there even after moving the class to the base package (a.b.c)
Got the issue, the new class had to be added in the persistence.xml, didn't face this issue as other newly added entity are using spring data jpa, while the older entities are using hibernate (currently transitioning from hibernate to jpa) , however one of the newly added entity was being used in the older classes which relied on the xml for finding entities.
I am using Spring Data JPA (1.4.0, spring boot) and have following code
ExternalOrder entity:
#Entity
#Table(name = "External_Orders")
public class ExternalOrder {
...
#OneToMany(fetch = FetchType.LAZY, mappedBy = "externalOrder", orphanRemoval = true, cascade = CascadeType.ALL)
private List<ExternalOrderElement> elements;
...
}
ExternalOrderElement entity:
#Entity
#Table(name = "External_Order_Elements")
public class ExternalOrderElement implements Serializable {
#Id
#ManyToOne(optional = false)
#JoinColumn(name = "ExternalOrderID")
private ExternalOrder externalOrder;
#Id
#OneToOne(optional = false)
#JoinColumn(name = "BoardGameID")
private BoardGame boardGame;
...
}
I also have default Crud repository for ExternalOrder
public interface ExternalOrderRepository extends CrudRepository<ExternalOrder, Integer>{
}
And I want to add some element to external order and persist it. I've managed to do this creating my custom repostitory interface and implementing it, but I've now tried to move the logic to the service method like so:
#Override
#Transactional
public void addElementToExternalOrder(int externalOrderId, ExternalOrderElement element) {
ExternalOrder externalOrder = findExternalOrderById(externalOrderId);
element.setExternalOrder(externalOrder);
externalOrder.getElements().add(element);
externalOrderRepository.save(externalOrder);
}
And that results in following Exception upon execution
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'ExternalOrderID' cannot be null
Is there something I am missing about the JPA? It seems like I haven't set the ExternalOrder reference on ExternalOrderElement, but I've done it.
Thank you guys for help in advance.
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 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.
Ok so I get this weird issue that I can't fix.
I have 3 entities ( i will write things that only matters imo)
#Data
#Entity // all # are in javax
#Table(name = "a", schema = "pl")
#SequenceGenerator(...)
public class A extends BaseEntity {
#OneToMany(mappedBy = "pk.a")
private Set<ABRel> Bs = new HashSet<ABRel>();
}
#Getter
#Setter
#Entity
#Table(name = "a_b_rel", schema = "pl")
public class ABRel implements IEntity {
#EmbeddedId
private OfferOrderProjectRelId pk;
public OfferOrderProjectRel(B b, A a) {
if (a == null || b == null) {
throw new IllegalArgumentException("B orA equals null");
}
B.addA(this); // this methods just adds ABRel to sets in A and B
A.addB(this);
pk = new ABRelId(b, a);
}
}
#Getter
#Setter
#Embeddable
#EqualsAndHashCode
public class OfferOrderProjectRelId implements Serializable {
#ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.REFRESH })
#JoinColumn(name = "b_id")
private B b;
#ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.REFRESH })
#JoinColumn(name = "a_id")
private A a;
public ABRelId(B b, A a) {
setB(b);
setA(a);
}
}
#Data
#Entity (it has javax import)
#Table(name = "b", schema = "pl")
#SequenceGenerator(...)
public class B extends BaseEntity {
#OneToMany(mappedBy = "pk.b")
private Set<ABRel> As = new HashSet<ABRel>();
#NotBlank
#Length(max = 10000)
#Column(name = "type", length = 10000, nullable = false)
private String type;
}
ABRel and ABRelId have private contructor (ABRel() and ABRelId()) but not sure if it matters. Entities are working just fine, so I don't think somethink is wrong with them but meaby I am wrong.
So I'm tryin to add criteria by B.type for my filters. Criteria are made "in" (not sure how to say it :) ) A.class. So here's criteria that I'm tryin to add in my dao ( I can add this to criteria not detached one if someone ask):
DetachedCriteria idCriteria = DetachedCriteria.forClass(A.class, "a");
idCriteria.createAlias("Bs", "btype", JoinType.LEFT_OUTER_JOIN, Restrictions.eq("Bs.pk.B.type", "someType"));
Criteria criteria = getSession().createCriteria(A.class, "a");
criteria.add(Subqueries.propertyIn("id", idCriteria));
What I am tryin to achieve is to get all ABRels that have some specified B.type, then I will have to count it somehow, but this is not my issue atm. I have to use criteria, can't use any HQL. I also read that hibernate has some kind of bug with creating alias beetwen entity and its embedded so I can't make it too (probably thats why I am having to much trouble with it). So any ideas? I'm running out of option so any help would be great!
I almost forget, I'm getting this error
org.hibernate.HibernateException: Unknown entity: null at
org.hibernate.loader.criteria.CriteriaQueryTranslator.getPropertyMapping(CriteriaQueryTranslator.java:638)
at
org.hibernate.loader.criteria.CriteriaQueryTranslator.getType(CriteriaQueryTranslator.java:587)
at
org.hibernate.loader.criteria.CriteriaQueryTranslator.getTypeUsingProjection(CriteriaQueryTranslator.java:569)
at
org.hibernate.loader.criteria.CriteriaQueryTranslator.getTypedValue(CriteriaQueryTranslator.java:627)
at
org.hibernate.criterion.SimpleExpression.getTypedValues(SimpleExpression.java:100)
at
org.hibernate.loader.criteria.CriteriaQueryTranslator.getQueryParameters(CriteriaQueryTranslator.java:335)
at
org.hibernate.criterion.SubqueryExpression.createAndSetInnerQuery(SubqueryExpression.java:151)
at
org.hibernate.criterion.SubqueryExpression.toSqlString(SubqueryExpression.java:68)
UPDATE:
I have added sth like this
criteria.createAlias("As", "oorel", JoinType.LEFT_OUTER_JOIN);
criteria.createAlias("oorel.pk.b", "order", JoinType.LEFT_OUTER_JOIN, Restrictions.eq("type", "order"));
And now I'm getting new error(it's in my native language so i will try to translate it) its postgres and hibernate exception :
Column index out of range: 1, number of columns: 0
Sorry for my bad english and thank you in advance.
This is unfortunetly a hibernate bug which havent been fixed yet. This error appears because hibernate is not able to create alias via entity that contains composite key.