Spring Data JPA persisting one to many referenced entity - java

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.

Related

Migration Hibernate 6

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.

how to avoid AnnotationException: Use of #OneToMany or #ManyToMany targeting an unmapped class?

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.

Hibernate problem - “Use of #OneToMany or #ManyToMany targeting an unmapped class”

I have 2 entities, LCPUserDetails and LCPUserPrivilege. LCPUserDetails has a List class member, so a One to Many relationship. When I run my unit test I am getting this exception:
#Entity
#Table(name = "LCP_USER_DETAILS")
public class LCPUserDetails {
#OneToMany(orphanRemoval = true, cascade = {CascadeType.ALL},
mappedBy = "userDetails")
private List<LCPUserPrivilege> privileges= new ArrayList<>();
}
#Entity
#Table(name = "LCP_USER_PRIVILEGE")
public class LCPUserPrivilege {
#ManyToOne
#JoinColumn(name = "USER_ID")
private LCPUserDetails userDetails;
}
As Sheik Sena Reddy mentioned, you have to update your list of entities. If you don't use an xml file, you can check where you set your EntityManagerFactory and add a list of package that your EMF will scan to list your entities emf.setPackagesToScan(['my.package.to.scan']);.

JPA preRemove listener not called for entity removed on cascade

I am using Eclipselink as a JPA provider and using EntityListener to catch preRemove events from my entities.
I have the following hierarchy:
#Entity
#Table(name = "MY_ENTITY")
#EntityListeners(AuditListener.class)
public class MyEntity {
.....
#OneToMany(mappedBy = "myEntity", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<AnotherEntity> anotherEntities;
.....
}
#Entity
#EntityListeners(AuditListener.class)
#Table(name="ANOTHER_ENTITY")
public class AnotherEntity {
#MapsId("idMyEntity")
#ManyToOne
#JoinColumn(name = "ID_MY_ENTITY")
private MyEntity myEntity;
}
The problem is when I remove one entry from anotherEntities Set and call update for myEntity from JPA, the method public void preUpdate(DescriptorEvent event) gets called for entity 'MyEntity' and thats allright, but as it cascades to 'AnotherEntity' entity and it gets deleted (as it should) there is no call to preRemove(DescriptorEvent event) method for AnotherEntity and I need to know when this occurs.
Is there any annotation missing here ? Do I need to call directly the remove for 'AnotherEntity' to get a callback working ?
Thanks in advance.

How to implement lazy loading using Spring data JPA (JPARepository)?

I am using Spring Data JPA and Hibernate as a provider. I've created several Repository classes which extends to JPARepository<Entity,Serializable> class. I am failing at the moment when I am fetching one entity it brings attached / connected entities along with it ! which are either connected via #OneToOne #OneToMany etc. How can I avoid fetching those connected entities ?
I have tried with #OneToMany(fetch=FetchType.LAZY) etc but still no luck. Following are my java code:
Repository
public interface TicketRepository extends JpaRepository<Ticket, Integer>{
}
Ticket Entity
#Entity
#Table(name = "tbl_tickets")
public class Ticket {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column(name = "customer", nullable = false, length = 256)
private String customer;
#OneToOne(cascade=CascadeType.ALL,fetch=FetchType.LAZY)
#JoinColumn
private User creator;
// ... other properties
}
Service
#Service
public class TicketService {
public Ticket save(Ticket obj,String id) {
User user = userService.findById(Integer.valueOf(id));
obj.setCreator(user);
Ticket savedTicket = ticketRepository.save(obj);
}
}
savedTicket always fetches User entity as well which I do not want to. How could I achieve this ?
Thanks
Get Lazy loading working on nullable one-to-one mapping you need to let hibernate do Compile time instrumentation and add a #LazyToOne(value = LazyToOneOption.NO_PROXY) to the one-to-one relation.
#OneToOne(cascade=CascadeType.ALL,fetch=FetchType.LAZY)
#JoinColumn
#LazyToOne(value = LazyToOneOption.NO_PROXY)
private User creator;
Hope this will work.

Categories