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).
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.
So I have two entities:
#Getter
#Setter
#Entity
#Table(name = "MY_TABLE")
public class MyTable {
#Id
#Column(nullable = false, length = 18)
#SequenceGenerator(name = "MY_TABLE_seq", sequenceName = "MY_TABLE_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "MY_TABLE_seq")
private long id;
#OneToOne(mappedBy = "myTable")
private MyTableView myTableView;
}
And an Immutable entity (the reason for this is that it is a database view):
#Entity
#Getter
#Immutable
#Table(name = "MY_TABLE_VIEW")
public class MyTableView {
#Id
#Column(name = "ID", nullable = false, length = 18)
private Long id;
#OneToOne
#MapsId
#JoinColumn(name = "id")
private MyTable myTable;
}
Updating and creating the MyTable works without a problem. The problem start when I try to remove the MyTable. I am using the repository for that:
public interface MyTableRepository extends CrudRepository<MyTable,Long> {
}
In the service I am using:
public void deleteMyTable(Long id){
/*fetch the my table enity*/
myTableRepository.delete(myTable);
}
Nothing happens. No exception nothing at all. What I have tried is changing the #OneToOne mapping. With different cascade:
#OneToOne(mappedBy = "myTable",cascade = CascadeType.ALL)
#OneToOne(mappedBy = "myTable",cascade = CascadeType.DETACH)
#OneToOne(mappedBy = "myTable",cascade = CascadeType.MERGE)
#OneToOne(mappedBy = "myTable",cascade = CascadeType.REFRESH)
#OneToOne(mappedBy = "myTable",cascade = CascadeType.REMOVE)
#OneToOne(mappedBy = "myTable",cascade = CascadeType.PERSIST)
ALL,MERGE and REMOVE throws and exception as I can not delete from a
view
DETACH,REFRESH and PERSIST does nothing but the MyTable entity is not
removed
Does anyone have an idea how to resolve this problem?
As indicated in the question comments, I suggested you to try creating some kind of #PreRemove hook in order to avoid the problem.
Because it makes perfect sense according to how the relationship between your entities is defined.
But if you think about that, you are dealing with a view, with a read-only entity: in my opinion, it makes more sense to invert the relationship and make MyTable the owner. In addition probably it will solve the issue.
Please, consider define you relationship with MyTableView in MyTable like this:
#OneToOne
#JoinColumn(name = "id", referencedColumnName = "id")
private MyTableView myTableView;
In MyTableView, simplify your relationship with MyTable in the following way:
#OneToOne(mappedBy = "myTableView")
private MyTable myTable;
This is probably caused by the constraint on the mapping.
An alternative way to remove "MyTableView" instance when deleting "MyTable" is to set "orphanRemoval = true". I think this is better for a double OneToOne mapping.
there are some links that (i hope) may helps you :
Hibernate one to one mapping. Delete row from dependent table
How does JPA orphanRemoval=true differ from the ON DELETE CASCADE DML clause
I am using Hibernate in a Java project to connect to the database. I have the following classes:
#Entity
#Table(name = "a")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class A {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
}
#Entity
#Table(name = "b")
public class B extends A {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "c_id", nullable = false)
private C c;
}
#Entity
#Table(name = "c")
public class C {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
}
I am trying to write a query over A that will eagerly fetch C inside B. I tried to do:
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<A> criteria = builder.createQuery(A.class);
Root<Pin> root = criteria.from(A.class);
criteria.select(root);
builder.treat(root, B.class).fetch("c", JoinType.LEFT);
But it won't perform the fetch. Oddly enough, I was able to do the following successfully to do a left join:
Join c = builder.treat(root, B.class).join("c", JoinType.LEFT);
Any ideas what I am doing wrong?
If you have a field set FetchType.LAZY and you want it to be eager fetched in one spefic query you have two options:
1.
The simplest way is just query normally and iterate over the result to force hibernate to fetch the nested fields.
List<Class> result = session.createCriteria(persistentClass).list();
for(Class c : result)
c.getLazyField();
2. The hard way, if you have some special requirement where you must run just one query on DB level you will need to get a cursos and fecth every field MANUALLY.
As you set the field to be lazy, doesn't matter if hibernate sees it on the resultset, it will simple ignore since it was annotated to be.
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.
We have an Entity with embedded collection.
1) Initial configuration
#Entity #Table
public class A implements Serializable {
#ElementCollection
#CollectionTable(name = "A_B", joinColumns = #JoinColumn(name = "A"))
private Set<B> embedded = new HashSet<B>();
}
#Embeddable
public class B implements Serializable {
private String unique;
private String nullable;
}
When removing an object with this configuration Hibernate generates a Query similar to
DELETE FROM A_B WHERE A = ? AND unique = ? AND nullable = ?
This does not work, when nullable field is actually NULL, due to SQL NULL equals.
2) Adding #UniqueConstraint
I tried to mitigate this using #UniqueConstraint, which suppose to handle this case
#Entity #Table
public class A implements Serializable {
#ElementCollection
#CollectionTable(name = "A_B", joinColumns = #JoinColumn(name = "A"), uniqueConstraints = #UniqueConstraint(columnNames = {"unique"}))
private Set<B> embedded = new HashSet<B>();
}
This did not change a Query at all.
Finally I solved it by using propriatery Hibernate #SQLDelete annotation
3) Adding #SQLDelete
#Entity #Table
public class A implements Serializable {
#ElementCollection
#CollectionTable(name = "A_B", joinColumns = #JoinColumn(name = "A"), uniqueConstraints = #UniqueConstraint(columnNames = {"unique"}))
#SQLDelete(sql = "delete from A_B where A=? and unique=? and (1 = 1 OR nullable = ?)")
private Set<B> embedded = new HashSet<B>();
}
I have a strict requirements to use only JPA 2.0 annotations, so I might need to remove my solution - 3.
How can I make a 2 configuration working, using standard JPA 2.0 ?
Do I need some additional annotations except #UniqueConstraint in #CollectionTable?
If you're using an Embeddable type, can't/shouldn't you use #AttributeOverrides on the Set and then set the Column in the embeddable type to Nullable?
#AttributeOverrides({
#AttributeOverride(name="UNIQUE_COLUMN", column=#Column(name="UNIQUE"))
#Column(name = "UNIQUE", nullable = true)