#UniqueConstraint does not affect DELETE statement on #CollectionTable Hibernate - java

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)

Related

Spring data - Deletion is suppressed with relation to view

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

Migration issue with Single Table Inheritance from JPA to hibernate

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).

Hibernate annotations: Many-to-many with shared composite key attribute

I'm trying to map up an existing database schema using Hibernate+JPA annotations.
One of my entities are mapped like this:
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
private int department;
#Id
private int userId;
...
And another entity, Group:
#Entity
#Table(name = "groups")
public class Group implements Serializable {
#Id
private int department;
#Id
private int groupId;
...
Group and User should have a many-to-many relationship between them, but the issue is that the join table ("user_group") only has columns "DEPARTMENT, USERID, GROUPID" - i.e. the DEPARTMENT column needs to be used in both joinColumns and inverseJoinColumns:
#ManyToMany(cascade = { CascadeType.ALL })
#JoinTable(
name = "user_groups",
joinColumns = { #JoinColumn(name = "department"), #JoinColumn(name = "groupid") },
inverseJoinColumns = {#JoinColumn(name = "department"), #JoinColumn(name = "userid") }
)
private List<User> groupUsers = new ArrayList<>();
which gives a mapping error - "Repeated column in mapping for entity".
However, it looks like this was/is possible using XML, because this exact example exists in the old Hibernate documentation. But I cannot find any evidence that this ever worked using annotations? I tried with #JoinFormula instead of #JoinColumn, but that does not compile. Is it possible?
Okay, I'm pretty sure it's not possible.
I found a promising workaround:
Create an #Embeddable for the "user_group" table:
#Embeddable
public class UserGroupMembership implements Serializable {
#ManyToOne
#JoinColumnsOrFormulas(
value = {
#JoinColumnOrFormula(column = #JoinColumn(referencedColumnName = "userid", name = "userid")),
#JoinColumnOrFormula(formula = #JoinFormula(referencedColumnName = "department", value = "department"))
})
private User user;
public UserGroupMembership(User user) {
this.user = user;
}
public UserGroupMembership() {
}
public User getUser() {
return user;
}
}
The trick is that #ManyToOne allows you to use #JoinColumnsOrFormulas, so one of the join conditions can be a formula, which I doesn't seem to work for #ManyToMany (the #JoinColumnsOrFormulas annotation is ignored as it expects the join columns to be part of the #JoinTable annotation).
The UserGroupMemberships are then mapped as a ElementCollection:
#ElementCollection
#CollectionTable(name = "user_group", joinColumns = {
#JoinColumn(name = "department", referencedColumnName = "department"),
#JoinColumn(name = "groupid", referencedColumnName = "groupid")
})
#OrderColumn(name = "seq", nullable = false)
private List<UserGroupMemberships> groupUsers = new ArrayList<>();
This only works right now for a unidirectional many-to-many relationship.

Hibernate/JPA unidirectional OneToMany with join condition on constant value in source table

I want to use Hibernate annotations to represent a unidirectional one-to-many relationship using a join. I want an added condition on the join so it only happens when a column in the source table (the "one") is equal to a constant value. For example.
SELECT *
FROM buildings b
LEFT JOIN building_floors bf on bf.building_id = b.id AND b.type = 'OFFICE'
I want to represent the b.type = 'OFFICE' part of that query.
My question is quite similar to this one, except I have a condition on the source table. JPA/Hibernate Join On Constant Value
The Java entities look like this:
#Entity
#Table(name = "buildings")
public class Building {
#Id
#Column(name = "id")
private int id;
#Column(name = "type")
private String type;
#OneToMany(mappedBy = "buildingId",
fetch = FetchType.EAGER,
cascade = {CascadeType.ALL},
orphanRemoval = true)
#Fetch(FetchMode.JOIN)
// buildings.type = 'OFFICE' ????
private Set<BuildingFloors> buildingFloors;
// getters/setters
}
#Entity
#Table(name = "building_floors")
public class BuildingFloor {
#Id
#Column(name = "building_id")
private int buildingId;
#Id
#Column(name = "floor_id")
private int floorId;
#Column(name = "description")
private String description;
// getters/setters
}
I've tried a few things where I have that placeholder comment:
#Where annotation
This doesn't work since that applies to the target entity.
#JoinColumns annotation
#JoinColumns({
#JoinColumn(name = "building_id", referencedColumnName = "id"),
#JoinColumn(name = "'OFFICE'", referencedColumnName = "type")
})
This doesn't work because I get the following error (simplified for clarity): Syntax error in SQL statement "SELECT * FROM buildings b JOIN building_floors bf on bf.building_id = b.id AND bf.'OFFICE' = b.type"
A different #JoinColumns annotation
#JoinColumns({
#JoinColumn(name = "building_id", referencedColumnName = "id"),
#JoinColumn(name = "buildings.type", referencedColumnName = "'OFFICE'")
})
This doesn't work because when using a unidirectional OneToMany relationship, the referencedColumnName is from the source table. So I get the error: org.hibernate.MappingException: Unable to find column with logical name: 'OFFICE' in buildings
Thanks in advance!
Why not use inheritance ? (I use it with JPA, I never use hibernate directly)
#Entity
#Inheritance
#Table(name = "buildings")
#DiscriminatorColumn(name="type")
public class Building {
#Id
#Column(name = "id")
private int id;
#Column(name = "type")
private String type;
}
And :
#Entity
#DiscriminatorValue("OFFICE")
public class Office extends Building {
#OneToMany(mappedBy = "buildingId",
fetch = FetchType.EAGER,
cascade = {CascadeType.ALL},
orphanRemoval = true)
private Set<BuildingFloors> buildingFloors;
}
Create database View with the following select:
SELECT bf.* FROM building_floors bf JOIN buildings b on bf.building_id = b.id AND b.type = 'OFFICE'
Map it to a class OfficeBuildingFloors as an ordinary entity and then use #OneToMany for it in Building class.
Of course, you won't be able to modify such collection and to avoid any exception you can use #Immutable on OfficeBuildingFloors.
In my opinion you should create a specific query to achieve your goals, rather than put specific annotations with constant parameter. I'm not see you mention another frameworks besides Hibernate so I would give some example with Hibernate. In your Building class your unidirectional mappings look like this:
#OneToMany(fetch = FetchType.Lazy, cascade = {CascadeType.ALL}, orphanRemoval = true)
#JoinTable(name = "building_floors", joinColumns = #JoinColumn(name = "id"), inverseJoinColumns = #JoinColumn(name = "building_id")
private Set<BuildingFloor> buildingFloors;
Then you can fetch your data using TypedQuery like this.
TypedQuery<Customer> query = getEntityManager().createNamedQuery("select b from building b inner join fetch b.buildingFloors where b.type = 'OFFICE'", Building.class);
List<Building> result = query.getResultList();
My solutions is not Hibernate specific, actually you could perform this with simple JPA. Hope this can help you to achieve your goals.
As you want filter source table you could use #Loader annotation
#Entity
#Table(name = "buildings")
#Loader(namedQuery = "building")
#NamedNativeQuery(name="building",
query="SELECT * FROM buildings b"
+ " LEFT JOIN building_floors bf on bf.building_id = b.id"
+ " WHERE b.type = 'OFFICE' AND b.id = ?",
resultClass = Building.class)
class Building
Approach with view in DB would be better and more clearly, if it could be used inside DB also. Otherwise rename Building to something which explicitly represent filtering.
Another approaches to mention: #Filter, #FilterDef.

Hibernate - ManyToOne & Inheritance / JOINED / mappedBy

I have some problems with inheritance mapping. Here my database structure:
And associated entities:
AbstractEntity:
#MappedSuperclass
public abstract class AbstractEntity<ID extends Serializable> implements Serializable {
#Id #GeneratedValue(strategy = IDENTITY)
#Column(unique = true, updatable = false, nullable = false)
private ID id;
public ID getId() {
return id;
}
#SuppressWarnings("unused")
public void setId(ID id) {
this.id = id;
}
UserAcitvity entity:
#Entity #Table(name = "user_activity")
#Inheritance(strategy = JOINED)
#AttributeOverride(name = "id", column = #Column(name = "ua_id"))
public abstract class UserActivity extends AbstractEntity<Long> {
#ManyToOne(cascade = { MERGE, PERSIST }, fetch = LAZY)
#JoinColumn(name = "ua_user_id")
private User user;
...
}
Comment entity:
#Entity #Table(name = "comment")
#PrimaryKeyJoinColumn(name = "cm_id")
public class Comment extends UserActivity {
#ManyToOne(cascade = { MERGE, PERSIST }, fetch = LAZY)
#JoinColumn(name = "cm_question_id")
private Question question;
...
}
Question entity:
#Entity #Table(name = "question")
#PrimaryKeyJoinColumn(name = "qs_id")
public class Question extends UserActivity {
...
#OneToMany(fetch = LAZY, cascade = ALL, mappedBy = "question")
private List<Answer> answers = new ArrayList<>();
#OneToMany(fetch = LAZY, cascade = ALL, mappedBy = "question")
private List<Comment> comments = new ArrayList<>();
...
}
Answer entity:
#Entity #Table(name = "answer")
#PrimaryKeyJoinColumn(name = "asw_id")
public class Answer extends UserActivity {
#ManyToOne(cascade = { MERGE, PERSIST }, fetch = LAZY)
#JoinColumn(name = "asw_question_id")
private Question question;
...
}
and User entity:
#Entity #Table(name = "user")
#AttributeOverride(name = "id", column = #Column(name = "user_id"))
public class User extends AbstractEntity<Long> {
...
#OneToMany(cascade = REMOVE)
private List<Question> questions = new ArrayList<>();
#OneToMany(cascade = REMOVE)
private List<Answer> answers = new ArrayList<>();
#OneToMany(cascade = REMOVE)
private List<Comment> comments = new ArrayList<>();
...
}
Problem:
When I try to save or delete a User I get an exceptions:
org.springframework.dao.InvalidDataAccessResourceUsageException: could not prepare statement; SQL [insert into user_question (user_user_id, questions_qs_id) values (?, ?)]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement
and:
org.hibernate.engine.jdbc.spi.SqlExceptionHelper : 147 = user lacks privilege or object not found: USER_ANSWER
Hibernate is trying to create a table: user_question and user_answer which me do not need.
What I should doing for fixes ?
I don't think you can achieve this by mapping the ManyToOne association to User generically in the UserActivity entity. That's probably too confusing for the JPA provider (Hibernate).
Instead, I think you need to map the association to User in each of the Question, Answer and Comment entities. Yes, I know that would be duplicated code, but it looks like the only way you will then be able to qualify the OneToMany mappings in User using the mappedBy reference.
For instance, your Question entity would have an association defined as:
#ManyToOne(cascade = { MERGE, PERSIST }, fetch = LAZY)
#JoinColumn(name = "ua_user_id")
private User questionUser;
Depending on how clever (or not) Hibernate is about the above association, you may need to specify the table="USER_ACTIVITY" in the JoinColumn annotation.
Then the User would have the OneToMany as:
#OneToMany(mappedBy="questionUser", cascade = REMOVE)
private List<Question> questions = new ArrayList<>();
Similarly for each of Answer and Comment.
Of course, I haven't tried this, so I could be wrong.
It's probably happening because when you set the #OneToMany mapping then the hibernate will create an auxiliary table that will store the id from the entities on the relationship.
In this case you should try the following:
#OneToMany(cascade = REMOVE)
#JoinColumn(name = "answer_id")
private List<Answer> answers = new ArrayList<>();
The #JoinColumn annotation will map the relationship without the creation of the auxiliary table, so it's pretty likely this solution will help you in this situation.
Try this mapping, this should work as you expect according to section 2.2.5.3.1.1 of the documentation:
#Entity
public class User {
#OneToMany(cascade = REMOVE)
#JoinColumn(name="user_fk") //we need to duplicate the physical information
private List<Question> questions = new ArrayList<>();
...
}
#Entity
public class Question {
#ManyToOne
#JoinColumn(name="user_fk", insertable=false, updatable=false)
private User user;
...
}
The reason why the auxiliary association is created, is that there is no way for Hibernate to know that the Many side of the relation (for example Question) has a foreign key back to User that corresponds to the exact same relation as User.questions.
The association Question.user could be a completely different association, for example User.questionCreator or User.previousSuccessfulAnswerer.
Just by looking at Question.user, there is no way for Hibernate to know that it's the same association as User.questions.
So without the mappedBy indicating that the relation is the same, or #JoinColumn to indicate that there is no join table (but only a join column), Hibernate will trigger the generic one-to-many association mapping solution that consists in creating an auxiliary mapping table.
The schema misses such association tables, which causes the error that can be solved with the mapping above.
If you want unidirectional one-to-many usage in your entity relationship.
Try with..JoinTable
#OneToMany(cascade = REMOVE)
#JoinTable(name = "user_question", joinColumns = {
#JoinColumn(name = "user_id")}, inverseJoinColumns = {
#JoinColumn(name = "qs_id")})
private List<Question> questions = new ArrayList<>();

Categories

Resources