I'm having an issue with a bi-directional mapping between a one-to-many relationship whereby the many is not getting it's foreign key updated - it remains null when performing an update/create. There are two entities, the first (many) is this:
#Entity
#Table(name = "availability")
public class Availability implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PERSON_ID", nullable = false)
private Person person;
The second is the one:
#Entity
#Table(name = "PERSON")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "discriminator", discriminatorType = DiscriminatorType.STRING)
#DiscriminatorValue(value = "P")
public abstract class Person implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "person")
private Collection<Availability> availabilities;
There are a couple of things that might be causing this, but I'm uncertain of what the real reason is. The first is that Person is abstract. However the inheritence type I'm using should not be causing this issue.
The second is the way in which I'm trying to persist the entity in a single transaction. I'm using dropwizard's #UnitOfWork. My DAO is doing the following:
public Staff update(Staff staff) {
return persist(staff);
}
Staff at this point does not have an id. Availability is set on Person (also without an id)
Under the hood, this is simply calling the following:
protected E persist(E entity) throws HibernateException {
currentSession().saveOrUpdate(requireNonNull(entity));
return entity;
}
Should i be using the session manager to open a new transaction, persist person, get the id back, set that on availability and then perform an update with person with the modified availability? It seems to me that would just be wrong.
The problem is that it wasn't really a bi-directional relationship, and Person was never being set on availability - hence why the foreign key was null.
I modified it to unidirectional.
On Person.class
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "person_availability")
private List<Availability> availabilities;
and then on Availability.class I removed the bidirectional reference i.e. removed the person value reference.
The other change required was setting the transactional strategy with the cascade type.
Field annotation strategy is not the issue here.
Related
I have some problems in understanding how to connect one entity with others in one attribute. That is not typical OneToMany relationship, I'm talking about situation when I need to implement complains functionality in my application: User can complain about several different entities (Question, Answer, Comment or another User), so the Complain entity will have schema relations like:
where User connects as One to many to user_id and Many To One to entity_id (1 to * and * to 1 in image).
So, I tried to use parameterized class Complain to implement this (BaseComplain is empty class):
Complain.java
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#ToString
#Entity
#Table(name = "complains")
public class Complain<T extends BaseComplain> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
private User user_id;
#ManyToOne
#JoinColumn(name = "entity_id", nullable = false)
private T entity_id;
#Column
#Temporal(TemporalType.TIMESTAMP)
private Date created_on;
}
User.java
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#ToString
#Entity
#Table(name = "users")
public class User extends BaseComplain {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToMany(mappedBy = "user_id", orphanRemoval = true, fetch = FetchType.LAZY)
#ToString.Exclude
private Set<Complain<BaseComplain>> author_complains;
#OneToMany(mappedBy = "entity_id", orphanRemoval = true, fetch = FetchType.LAZY)
#ToString.Exclude
private Set<Complain<User>> complains;
<...other stuff...>
}
And Question.java (all entities have the same realisation of relationship):
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#ToString
#Entity
#Table(name = "questions")
public class Question extends BaseComplain {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "entity_id", orphanRemoval = true, fetch = FetchType.LAZY)
#ToString.Exclude
#JsonManagedReference
private Set<Complain<Question>> complains;
<...other stuff...>
}
But it caused (formatted):
org.hibernate.AnnotationException:
Property com.*.*.Entities.Complain.entity_id has an unbound type and no explicit target entity.
Resolve this Generic usage issue or set an explicit target attribute (eg #OneToMany(target=)
or use an explicit #Type...
I can add all stack trace, but there are only typical spring app exceptions (Bean creation error, embedded Tomcat exception).
So the question is - Is there any way to implement this logics using only, like, "basic" features of JPA?
Probably, I have some ideas of #MappedSuperclass usage, but still need your help.
How about this - it looks like what you have tried to a certain extent; the idea is that the Complaint is an abstract base entity, so that it can have relations to and from it, but the concrete implementations are questions, answers etc. To have a base table and separate ones per complaint type, use #Inheritance(strategy = InheritanceType.JOINED). And use concrete complaint types that are different from the entities they link to, e.g. QuestionComplaint → Question. So:
#Entity
#Table(name = "complaints")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class Complaint {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
private User user;
#Column
#Temporal(TemporalType.TIMESTAMP)
private Date created_on;
}
The user in turn relates to a set of Complaint objects, but is not a Complaint itself (doesn't make sense, does it?)
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToMany(mappedBy = "user_id", orphanRemoval = true, fetch = FetchType.LAZY)
#ToString.Exclude
private Set<Complaint> complaints;
}
And the concrete Complaint instances:
#Entity
#Table(name = "question_complaints")
#PrimaryKeyJoinColumn(name = "id")
public class QuestionComplaint extends Complaint {
#ManyToOne(fetch = FetchType.LAZY)
private Question question
}
This entity represents the link from Complaint to Question. The Question entity, which represents a "Question", no matter if it has complaints attached, may optionally have a relation back to the QuestionComplaint:
#Entity
#Table(name = "questions")
public class Question {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "question")
private List<QuestionComplaint> complaints;
}
Now usages I expect would be:
What are the complaints filed by a user? - User.getComplaints() will fetch a UNION ALL of the complaints from all known subtypes
What are the complaints attachede to a question (answer, etc)? - question.getComplaints() knows to get records only from the table question_complaints
What complaints has user x filed for question y? - SELECT c FROM QuestionComplaint c WHERE c.user.id = :x AND c.question.id = :y
Fistly, in java Generic Types can't deteminable in runtime, so JPA can't determine to fetch from which table, so it will throw Exception which you send.
Secodly your database design is wrong, Complain table will not connect to Question and Answer, Question and Answer need to connect Complain. Like:
Quesiton -|
v
---> Complain ---> User
^
Answer -|
Or you need to add to two fields to Complain table like questionId and answerId.
Q u e s i t o n A n s w e r
^ ^
| |
(questionID) (answerId)
| |
+-----------Complain ---> User
One field two foreign table not good design specially for JPA.
I have two tables user and student. Student and user has one to one relationship.
I am mapping same column with both userId and UserEntity inside StudentEntity
#Entity
#Table(name = "user")
public class User {
#Id
private UUID id;
#Column(name = "name")
private String name;
...
// getter setter ignored for brevity
}
#Entity
#Table(name = "student")
public class Student {
#Id
private UUID id;
#Column(name = "user_id")
private UUID userId;
#OneToOne
#JoinColumn(name = "user_id", referencedColumnName = "id",
insertable = false, updatable = false)
private UserEntity userEntity;
...
// getter setter ignored for brevity
}
#Repository
public interface StudentRepository extend PagingAndSortingRepository<StudentEntity, UUID> {
Optional<StudentEntity> findByUserId(UUID userId);
}
Now when I call any of below method UserEntity is not populated
StudentEntity student = studentRepository.findByUserId(userId).get()
student.getUserId() --- this is present
student.getUserEntity() -- NULL
Not sure why UserEntity is not getting populated as a part of this call.
The issue is most likely that your persistence context still contains the entity that you previously saved (which has no user set). You would first have to clear the persistence context and the load it again to see a non-null user.
Anyway, like suggested in the comments, I would advise you to just map the user. You don't need the userId field. The way you mapped it now, you should be able to use the following:
Optional<StudentEntity> findByUserEntityId(UUID userId);
Also read the official documentation for more information on the matter: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-property-expressions
You can try reverse mapping mapping like below by using mappedby in the owner of the relationship
jpa hibernate #OneToOne #JoinColumn referencedColumnName ignored
When migrating from standard JPA (EclipseLink) to hibernate, tests are failing due to improper number of child records and/or invalid types for child objects.
I think the issue is the SQL generated by hibernate is not using the discriminator column in the where clause on an InheritanceType.SINGLE_TABLE relationship.
Any suggestions on what to change in the mappings?
Thanks in advance,
Timothy
Environment
postgres 9.6.17
spring boot / JPA 2.4.0
postgres mvn dependency 42.2.18
Domain background
A DcMotor may have up to 2 sets of wires. Represented in the entity each in its own List. These wires are of either ARM or EQ type. Since the attributes on the wires are the same, a single table was chosen by the DBA with a discriminator column named coil_type with values of either ARM or EQ.
Class definitions
Parent Entity
#Entity
#Table(name = "dc_motor")
public class DcMotor implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Version
private Integer version;
//other direct fields
#OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "motor_id", referencedColumnName = "id", nullable = false)
#OrderColumn(name = "idx")
private List<WireArm> armWires;
#OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "motor_id", referencedColumnName = "id", nullable = false)
#OrderColumn(name = "idx")
private List<WireEq> eqWires;
Abstract base class for WireArm and WireEq
#Entity
#Table(name = "dc_wire")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "coil_type", length = 10, discriminatorType = DiscriminatorType.STRING)
public abstract class DcWire implements IDcWire {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// other direct fields
// discriminator column coil_type is not explicitly listed as a field
Concrete child class
#Entity
#DiscriminatorValue(value = "ARM")
public class WireArm extends DcWire implements IDcWire {
// just constructors
}
#Entity
#DiscriminatorValue(value = "EQ")
public class WireEq extends DcWire implements IDcWire {
// just constructors
}
SQL generated
select /*all fields*/ from dc_motor dcmotor0_ where dcmotor0_.id=1;
select /* all fields EXCEPT coil_type*/ from dc_wire armwires0_ where armwires0_.motor_id=1;
select /* all fields EXCEPT coil_type*/ from dc_wire eqwires0_ where eqwires0_.motor_id=1;
I think this can be solved by using org.hibernate.annotations.DiscriminatorOptions(force = true).
I'm new to JPA and I have a case where in my opinion JoinColumn behaves different and I want to know why.
UserEntites should join authorites.
Organziations should join OrganizationSettings.
I have two different approaches and both work.
Case 1
UserEntity :
#Entity
#Table(name = "users")
#Inheritance(strategy = InheritanceType.JOINED)
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "userId")
private List<UserAuthority> authorities;
}
UserAuthoritiesEntity
#Entity(name = "authorities")
#Table(name = "authorities")
public class UserAuthority {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long userId;
private String authority;
}
Here in my opinion the JoinColumn name references to UserAuthority.userId - and it works as expected.
Case 2
See my two other classes:
OrganizationEntity:
#Entity
#Table(name="organization")
public class OrganizationEntity {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
#NotNull
private String url;
#NotNull
private String name;
#OneToOne (cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name="id",updatable = false)
private OrganizationSettingsEntity settings;
}
OrganizationSettings:
#Entity
#Table(name = "organization_settings")
public class OrganizationSettingsEntity {
#Id
private Long organizationId;
}
As you can see here -> Organization is Joining OrganizationSettings with the name id - which works. But in OrganizationSettings there is no id - just organizationId. This works - but makes me wonder.
Why does the second one also work? Shouldn't it be #JoinColumn(name="organizationId") ?
Spring is nothing to do with it. JPA is a standard API.
1-N case : you will create a FK column in the authorities table with name userId (linking back to the users table). You seem to also want to REUSE that same column for this userId field in the element ... this will cause you problems sooner or later since reusing columns without marking the userId field as insertable=false, updatable=false will mean that both may try to update it. Either get rid of the userId field in the element, or convert the field to be of type UserEntity (and have it as a bidirectional relation, using mappedBy on the 1-N owner field), or mark the userId field with those attributes mentioned earlier.
1-1 case : you will create a FK column in the organization table with name id (linking across to the organization_settings table). Sadly this is the same column as the PK of that table is going to use, so again you are reusing the column for 2 distinct purposes, and hell will result. Change the column of the relation FK to something distinct - the FK is in the organization table, not the other side.
I have two entities Business which is composed of a list of Departments
#Entity
#Table(name = "Business")
public class Business implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Id")
private Long id;
#OneToMany(mappedBy = "business",
cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
private List<Department> departments;
#OneToMany(mappedBy = "business", orphanRemoval = true,
cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
private List<Process> processs;
#ManyToMany
private List<Competence> competences;
}
#Entity
#Table(name = "Department")
public class Department implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "father",
cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
private List<Department> departments;
}
When I try to remove a business instance I get a Mysql Exception
Cannot delete or update a parent row: a foreign key constraint fails (evac_java.Department, CONSTRAINT FK_Department_Business FOREIGN KEY (Business) REFERENCES Business (Id)):HY000 - null
Which means I can't delete the business instance because it has departments associated with it, but a department cannot exists by itself so I want to delete all business's departments when it gets removed. I thought I would achieve this by adding cascade = CascadeType.REMOVE to the #OneToMany annotation in the business entity, but it does not work.
I did a search on the net and I found a lot of questions similar to this one on stackoverflow but they all suggest the same: add cascade = CascadeType.REMOVE or CascadeType.ALL
So I'm wondering if I'm missing somethig.
I'm using Glassfish 4.1 and EclipseLink
I tried with
#OneToMany(mappedBy = "business", orphanRemoval = true)
private List<Department> departments;
on the business entity but it does not work either
Here's the method I'm using to remove entities which is declared in an abstract class
public void remove(T entity) {
getEntityManager().remove(getEntityManager().merge(entity));
}
JPA can only remove and cascade the remove over entities it knows about, and if you have not been maintaining both sides of this bidirectional relationship, issues like this will arise. If the collection of departments is empty, try an em.refresh() before the remove, forcing JPA to populate all relationships so that they can be correctly removed, though it is better to maintain both sides of the relationship as changes are made to avoid the database hit.