mappedBy reference an unknown target entity property with inheritance - java

I am trying to store a entity inside a Database with hibernate. I have got the following classes:
#Entity
public class UsableRemoteExperiment extends RemoteExperiment {
private List<ExperimentNodeGroup> nodeGroups = new ArrayList<>();
#OneToMany(mappedBy = "experiment", cascade = CascadeType.ALL, orphanRemoval = true)
public List<ExperimentNodeGroup> getNodeGroups() {
return nodeGroups;
}
public void setNodeGroups(final List<ExperimentNodeGroup> nodeGroups) {
this.nodeGroups = nodeGroups;
}
/* More getters and setters for other attributes */
The Experiment Node Group looks like this:
#Entity
public class ExperimentNodeGroup extends NodeGroup {
private List<Node> nodes = new ArrayList<>();
/* More getters and setters for other attributes */
And the NodeGroup Class looks like this:
#Entity
public abstract class NodeGroup extends GeneratedIdEntity {
protected Experiment experiment;
#ManyToOne(optional = false)
#JsonIgnore
public Experiment getExperiment() {
return experiment;
}
/* More getters and setters for other attributes */
Now when i try to compile the Code, I get this error:
Caused by: org.hibernate.AnnotationException: mappedBy reference an
unknown target entity property:
[...].ExperimentNodeGroup.experiment
in
[...].UsableRemoteExperiment.nodeGroups

It's one of the quirks of the hibernate where it does not work as expected with mappedBy and inheritance. Could you try specifying targetEntity as well? Here's the documentation and this is what it says:
The entity class that is the target of the association. Optional only
if the collection property is defined using Java generics. Must be
specified otherwise.
You can try specifying targetEntity = ExperimentNodeGroup.class or targetEntity = Transaction.class and see if that makes any difference.

I think the problem here is that you need to also put on a setter, hibernate assumes that if you have a getter to get from a database you will need a setter to read from it, you can either put a setter, or use
#Entity(access = AccessType.FIELD)
and put the annotations on your attributes.

Related

Quarkus/Hibernate merge with OneToMany(orphanRemoval=true) gives HibernateException

I am migrating from weblogic+eclipselink to quarkus+hibernate and get an error when trying to update a class through an endpoint.
Error is javax.persistence.PersistenceException: org.hibernate.HibernateException: A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance:.
Imagine classes
#Data
#Builder
#Entity
class Parent {
...
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "parent", orphanRemoval = true)
#Builder.Default
private List<Child> children= new ArrayList<>();
}
#Data
#Builder
#Entity
class Child {
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PARENT_ID")
#EqualsAndHashCode.Exclude
#ToString.Exclude
private Parent parent;
}
#Transactional
#Applicationscoped
class ParentService {
public void updateParent(Parent updatedParent) {
Optional<Parent> optionalExistingParent =
parentRepository.getParentByID(updatedParent.getId());
if (optionalExistingParent.isEmpty()) {
throw new IllegalArgumentException("Tried updating non-existing parent " + updatedParent.getGuid().toString());
}
// set back references for possible new children
setBackReferences(updatedParent);
parentRepository.merge(updatedParent);
}
}
Situation: I retrieve a Parent object through a rest endpoint. Then I update a value from the parent (e.g. name) and send the updated parent object to a PUT endpoint. It then gets to the service layer, where I check if the parent is known, and if so, I call repository.merge().
It is at the repository.merge() call where the exception is thrown.
A lot of other stackoverflow questions regarding this error say that I need to clear() and addAll() the List, however (ignoring that I didn't even change anything in the list) all that code is hibernate code, there is no code written by me adding/removing to that list.
I fixed this by doing parent.getChildren.size() and thus retrieving the data from the database before merging. It's silly, but fetch=EAGER does not work because there are multiple lists of children (including grandchildren) and then you get a HibernateException that you cannot fetch multiple bags.

MapStruct cannot find members from generic Set property

I started using MapStruct 1.4.0.CR1. I'm also using Gradle:
dependencies {
annotationProcessor("org.mapstruct:mapstruct-processor:${project.property("mapstruct.version")}")
implementation("org.mapstruct:mapstruct:${project.property("mapstruct.version")}")
}
I have some JPA entities I'm trying to map:
public class Exam implements Serializable {
// More class members here
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "exam", orphanRemoval = true)
private Set<Scan> scans;
public Exam() { } // ...no-argument constructor required by JPA
public Exam(final Builder builder) {
// ...set the rest also
scans = builder.scans;
}
// getters (no setters), hashCode, equals, and builder here
}
public class Scan implements Serializable {
// More class members here
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "scan", orphanRemoval = true)
private Set<Alarm> alarms;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "scan", orphanRemoval = true)
private Set<Isotope> isotopes;
protected Scan() { } // ...no-argument constructor required by JPA
public Scan(final Builder builder) {
// ...set the rest also
alarms = builder.alarms;
isotopes = builder.isotopes;
}
// getters (no setters), hashCode, equals, and builder here
}
I have similar classes for mapping, but they don't have as many fields/members as the JPA entities, moreover, they are on a completely different sub-system (hence the mapping). The problem is that MapStruct is telling me there are no isotopes within Scans: java: No property named "scans.isotopes" exists in source parameter(s). Did you mean "scans.empty"?.
Basically, isotopes and alarms are not contained within a Set of scans in the (new) mapped Exam class. This is my ExamMapper:
#FunctionalInterface
#Mapper(componentModel = "spring", injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface ExamMapper {
// #Mapping(source = "scans.alarms", target = "alarms")
#Mapping(source = "scans.isotopes", target = "isotopes")
Exam valueFrom(tld.domain.Exam entity);
}
Is there a way to accomplish this? I think this may be trivial, but I'm fairly new to MapStruct ;)
The source and target attributes of the #Mapping can only reference bean properties.
This means that when using scans.isotopes, it will look for a property isotopes in Set<Scan> and thus the compile error.
In order to solve this you'll need to provide some custom mappings. From what I can understand you will need to do flat mapping here as well. The reason for that is that you have multiple scans, and each scan has multiple isotopes. You need to gather all of that and map it into a single collection.
One way to achieve this is in the following way:
#Mapper(componentModel = "spring", injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface ExamMapper {
#Mapping(source = "scans", target = "isotopes")
Exam valueFrom(tld.domain.Exam entity);
Isotope valueFrom(tld.domain.Isotope isotope);
default Set<Isotope> flatMapIsotopes(Set<Scan> scans) {
return scans.stream()
.flatMap(scan -> scan.getIsotopes().stream())
.map(this::valueFrom)
.collect(Collectors.toSet());
}
}

Hibernate inheritance mapping unknown property

I am struggling with my inheritance structure where I have a mapped superclass which contains a common field in the concrete classes. This superclass has a one-to-one mapping with a "wrapper" object.
The objects look like this;
#Entity
public class Wrapper {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "wrapper_id", nullable = false)
private Long wrapperId;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "wrapper")
#Cascade(CascadeType.SAVE_UPDATE)
private Base base;
public Long getWrapperId() {
return wrapperId;
}
public void setWrapperId(Long wrapperId) {
this.wrapperId = wrapperId;
}
public Base getBase() {
return base;
}
public void setBase(Base base) {
this.base = base;
}
}
#MappedSuperclass
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Base {
#OneToOne(fetch = FetchType.LAZY)
#Cascade(CascadeType.SAVE_UPDATE)
#JoinColumn(name = "wrapper_id")
protected Wrapper wrapper;
public Wrapper getWrapper() {
return wrapper;
}
public void setWrapper(Wrapper wrapper) {
this.wrapper = wrapper;
}
}
#Entity
public class SubA extends Base {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "sub_a_id", nullable = false)
private Long subAId;
public Long getSubAId() {
return subAId;
}
public void setSubAId(Long subAId) {
this.subAId = subAId;
}
}
For simplicity I have only included a single concrete class but I have several.
This mapping works great when I do not have a reference to "Base" in the wrapper object. As soon as I try to add the bi-directional relationship between the wrapper and base I start to get this error....which doesn't make sense as the field is there.
Caused by: org.hibernate.AnnotationException: Unknown mappedBy in: com.xxx.Wrapper.base, referenced property unknown: com.xxx.Base.wrapper
at org.hibernate.cfg.OneToOneSecondPass.doSecondPass(OneToOneSecondPass.java:153)
at org.hibernate.cfg.Configuration.originalSecondPassCompile(Configuration.java:1697)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1426)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1846)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1930)
at org.springframework.orm.hibernate4.LocalSessionFactoryBuilder.buildSessionFactory(LocalSessionFactoryBuilder.java:372)
at org.springframework.orm.hibernate4.LocalSessionFactoryBean.buildSessionFactory(LocalSessionFactoryBean.java:453)
at org.springframework.orm.hibernate4.LocalSessionFactoryBean.afterPropertiesSet(LocalSessionFactoryBean.java:438)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1627)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1564)
... 50 more
What am I missing?
Thanks,
Quote from Java Platform, Enterprise Edition: The Java EE Tutorial:
37.2.2 Mapped Superclasses
Entities may inherit from superclasses that contain persistent state
and mapping information but are not entities. That is, the superclass
is not decorated with the #Entity annotation and is not mapped as an
entity by the Java Persistence provider. These superclasses are most
often used when you have state and mapping information common to
multiple entity classes. Mapped superclasses are specified by
decorating the class with the annotation
javax.persistence.MappedSuperclass:
...
Mapped superclasses cannot be queried and cannot be used in
EntityManager or Query operations. You must use entity subclasses of
the mapped superclass in EntityManager or Query operations. Mapped
superclasses can't be targets of entity relationships.
So it looks like you can`t use this Base class in entity relationships:
#OneToOne(fetch = FetchType.LAZY, mappedBy = "wrapper")
#Cascade(CascadeType.SAVE_UPDATE)
private Base base;
Looks like this hibernate bug: Hibernate complains about an unknown mappedBy property when mapping a bidirectional OneToOne relation with a derived identifier, which was fixed only in late hibernate 4.2.2, 4.3.0.Beta3 versions.
We ended up doing a lot of prototyping with the different options (mapped super class, hierarchical etc) and weighed the options.
In the end we decided to create object hierarchies coupled with the #Entity annotation, Inheritance strategy of SINGLE_TABLE and using Discriminator values to give us exactly what we needed without sacrificing too much.
Thank you all for your suggestions.

Jackson bi-directional nested object setting

I'm having a few issues with deserializing the following json:
{
children:[{name:"1c"},{name:"2c"},{name:"3c"}]
}
My classes would look like this:
#JsonIdentityInfo(scope=ParentObject.class,generator = ObjectIdGenerators.PropertyGenerator.class, property="id")
public class ParentObject {
int id;
#OneToMany(cascade = CascadeType.ALL,mappedBy="parent",orphanRemoval=true)
Set<Child> children;
}
#JsonIdentityInfo(scope=Child.class,generator = ObjectIdGenerators.PropertyGenerator.class, property="id")
public class Child {
int id;
String name;
#ManyToOne
ParentObject parent;
}
With this in mind, I'd like Jackson to automatically set the ParentObject.
The structure I'm given is almost perfect with the exception of the ParentObject not being set. This is imperative as Hibernate will then set the ids of the parent and then set the ids of the children based on this which is currenly null.
I originally had it set with a JSONManagedReference and BackReference, but that it uni-directional and when requesting a single object, the parent object would be ignored.
How can I get this to work?
Thanks!
Tom
Try using #jsonignore on the
#ManyToOne
ParentObject parent;
I think while deserializing it will not consider it and give you a Parent Object..
Regards,
Prasad

Set JPA Annotations at the member variable or at get methods of an entity?

i havenĀ“t some big experiences with the topic JPA and Hibernate. For me it is not clear, when it is necessary to write the JPA annotation at the member variable of my entity class ans when i can use the get methods for my annotations. Is it right, that annotations should not being set at the set method of an entity?. Here is a small example:
public class MessageEntity implements Persistable{
#ManyToOne
StatusEntity state;
#Column(nullable = false)
private Boolean freitext = false;
private Collection<Variables> variables;
#OneToMany(cascade = { CascadeType.MERGE, CascadeType.PERSIST }, targetEntity = Variables.class)
public Collection<Variables> getVariables() {
return this.variables;
}
}
Also it is not clear for me, when i must use the attribute targetEntity. Can somebody explain that for me?
Maik
The annotations can go either on the properties or the getters, but not on the setters.
In a one to many relationship, if the Set(collection) is specified without the generics, the targetEntity is required. If the Set<Generic> is used then the targetEntity is not required.
Ref: Java api
I have not used any JPA annotations on getter methods, I'd assume its the same as annotating the variable at the top. I'd keep all annotations on the variables at the top, just to be cleaner.
For that "targetEntity" attribute on your #OneToMany I have not used, however I do use mappedBy when using #OneToMany. I think targetEntity is not needed in your case.
#Entity
#Table(name="message_entity")
public class MessageEntity implements Persistable{
#ManyToOne
StatusEntity state;
#Column(nullable = false)
private Boolean freitext = false;
#OneToMany(mappedBy="message_entity"), cascade = { CascadeType.MERGE, CascadeType.PERSIS
private Collection<Variables> variables;
public Collection<Variables> getVariables() {
return this.variables;
}
}

Categories

Resources