Why does EclipseLink support the #CascadeOnDelete with #ElementCollection? - jpa

I have the following embeddable class that contains an #Lob:
#Embeddable
public class EntityState {
private Integer version;
#Lob
#XmlJavaTypeAdapter(CharArrayAdapter.class)
private char[] xmlState;
...
}
I also have the following embeddable class that contains the above embeddable:
#Embeddable
public class EntityEvent {
#NotNull
private String note;
private EntityState entityState;
...
}
Finally, I have many entity classes that contain a property called history that is a list of EntityEvents. The following is an example:
#Entity
public class Company {
#NotNull
#ElementCollection
private List<EntityEvent> history;
...
}
When I deploy my application in GlassFish 4.1, EclipseLink creates the following tables in my Derby 10.11.1.1 database:
COMPANY
COMPANY_HISTORY
When I create a new Company, my application creates an EntityEvent and adds the EntityEvent to the Company history.
When I modify a Company, my application does the following:
Creates an EntityState object and sets the xmlState property to an XML representation of the unmodified entity.
Creates an EntityEvent object containing the above EntityState.
Adds the EntityEvent to the Company history.
The problem is that when I try to delete an entity that has a history with multiple EntityEvents I receive the following error:
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.DatabaseException Internal Exception: java.sql.SQLSyntaxErrorException: Comparisons between 'CLOB (UCS_BASIC)' and 'CLOB (UCS_BASIC)' are not supported. Types must be comparable. String types must also have matching collation. If collation does not match, a possible solution is to cast operands to force them to the default collation (e.g. SELECT tablename FROM sys.systables WHERE CAST(tablename AS VARCHAR(128)) = 'T1')
Error Code: 20000 Call: DELETE FROM Company_HISTORY WHERE ((((((((((CHANGES = ?) AND (CLIENTTYPE = ?)) AND (CREATED = ?)) AND (IPADDRESS = ?)) AND (NOTE = ?)) AND (TYPE = ?)) AND (VERSION = ?)) AND (XMLSTATE = ?)) AND (CREATER_ID = ?)) AND (Company_ID = ?)) bind => [10 parameters bound]
I found a few references to the issue in the following links:
Hibernate - #ElementCollection - Strange delete/insert behavior
http://eclipse.1072660.n5.nabble.com/Customizing-delete-calls-before-updating-a-ElementCollection-td7312.html
I tried the #OrderColumn technique described in the above referenced stackoverflow article but this did not work in EclipseLink.
The solution that work for me was to add the EclipseLink nonstandard #CascadeOnDelete annotation to my entity as shown below:
#Entity
public class Company {
#NotNull
#ElementCollection
#CascadeOnDelete
private List<EntityEvent> history;
...
}
After performing this change and rebuilding my database, my COMPANY_HISTORY table has a new definition:
Without #CascadeOnDelete
ALTER TABLE COMPANY_HISTORY ADD CONSTRAINT CMPNYHISTORYCMPNYD FOREIGN KEY (COMPANY_ID) REFERENCES COMPANY (ID);
With #CascadeOnDelete
ALTER TABLE COMPANY_HISTORY ADD CONSTRAINT CMPNYHISTORYCMPNYD FOREIGN KEY (COMPANY_ID) REFERENCES COMPANY (ID) ON DELETE CASCADE;
The solution to my problem surprised me because it seems repetitive. My understanding is that JPA should delete all embeddables associated with an entity when the entity is deleted. The fact that EclipseLink has this nonstandard annotation as documented in the following link makes me think that EclipseLink has a bug and instead of fixing the bug created a new #CascadeOnDelete annotation so that the bug would be covered up by the databases cascading delete functionality.
http://www.eclipse.org/eclipselink/documentation/2.5/jpa/extensions/a_cascadeondelete.htm
So my question is why. Why does EclipseLink support the #CascadeOnDelete with #ElementCollection?

CascadeOnDelete is simply a feature that specifies that you have specified the "On Delete Cascade" option in your tables, so that JPA does not need to issue SQL to delete the corresponding references. This SQL can apply to any reference, which is why CascadeOnDelete works on an element collection mapping and any other referene mapping.
Your issue has to do with lob comparison limitation in your database, and since there isn't an ID field to uniquely identify element collection rows, this limitation interferes with the way EclipseLink tries to ensure it is only removing the required rows. If you were willing to add an order column to your table, why not just make the EntityEvent an Entity? Or you can customize EclipseLink as described here so that it uses the foreign key and an orderBy field or any combination of fields as a primary key to uniquely identify rows instead of including the lob field.

Related

"hibernate_sequence" does not exist"

I am getting "ERROR: relation "hibernate_sequence" does not exist" exception while doing insert operation.
Technical Stack
-> Springboot
-> Hibernate
-> PostgreSQL
Approaches tried so far.
-> Verified all entity classes in project, generation strategy is used as "#GeneratedValue(strategy = GenerationType.IDENTITY)".
-> Verified database tables, pk is either Serial or Int with proper sequence generated value.
-> Tried with use-new-id-generator-mappings property as false, didn't worked.
-> Verified sequence with name "hibernate_sequence" is available in Database.
Analysis so far
-> Entities those are annotated with #Audited having this issue as hibernate envers expect global "hibernate_sequence". But not able to find the exact solution.
Note : This was working few days back without any issue, Since last week started getting this issue.
As you said, hibernate-envers is looking for the hibernate_sequence.
Its used to insert records into the REVINFO table
Assuming spring.jpa.hibernate.ddl-auto is not set to create
either
create a hibernate_sequence manually
create a sequence with the name you want. e.g rev_id_seq. Then override the REVINFO definition to change the sequence name by adding your definition of the RevisionEntity
#Entity
#RevisionEntity
public class MyRevision implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "rev_id_generator")
#SequenceGenerator(name = "rev_id_generator", sequenceName = "rev_id_seq", allocationSize = 1)
#RevisionNumber
private int id;
#RevisionTimestamp
private long timestamp;
// Getters, setters, equals, hashCode ...
}
https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#envers-tracking-modified-entities-revchanges
https://thorben-janssen.com/hibernate-envers-extend-standard-revision/
Initially set the spring.jpa.hibernate.ddl-auto to create for the first time and run the application. It will create the hibernate sequence. after that change spring.jpa.hibernate.ddl-auto to none. It will prevent any further data loss from tables. Or you can set to update if necessary.
Because you are using #GeneratedValue()
It will look for how the database that you are using generates ids. For MySql or HSQSL, there are increment fields that automatically increment. In Postgres or Oracle, they use sequence tables. Since you didn't specify a sequence table name, it will look for a sequence table named hibernate_sequence and use it for default. So you probably don't have such a sequence table in your database and now you get that error.
I Also got this working using;
#GeneratedValue(strategy = GenerationType.IDENTITY)
I faced the exact same issue when I migrated from Maria to Postgres. Either/both problems one may have:
Schema name is not in the connection URL
If the schema name isn't passed flyway_schema_history table and sequences were created under the public schema. And application tables were made under the custom schema.
So make sure you have the required schema configured.
spring:
datasource:
url: jdbc:postgresql://localhost:5432/platform?currentSchema=product1
username: admin
password: admin
driver-class-name: org.postgresql.Driver
flyway:
schemas:
- product1
Sequence got created but with another name
This was the problem for me. Sequence got created with the name revinfo_rev_seq. However, while inserting the records it was looking for hibernate_sequence.
I added another revision under flyway migration to rename the already created sequence.
-- This is not required for MySQL/MariaDB. However, while using PostgresSQL getting the error -> ERROR: relation "hibernate_sequence" does not exist
ALTER SEQUENCE revinfo_rev_seq RENAME TO hibernate_sequence;

JPA 2 one to many - how does JPA infer column information?

I have a JPA2 (Hibernate) application which uses a MySQL database with only two tables. One table is called "companies" and the other table is called "employees". Between the two tables there is a one-to-many ralationship (1 company has many employees). The foreign-key column in table "employees" is called "company_id".
In my JPA2 Application I use the following annotations:
In the entity class "Company" I have the following annotation
#OneToMany(cascade = CascadeType.ALL)
private Collection<Employee> employees;
and in class Employee
#ManyToOne
private Company company;
How does JPA know what column it should use to determine all employees of a company. The annotations do not hold this information, but the application works.
Thank you
The ManyToOne side is missing the optional JoinColumn annotation, which in turn has the optional name attribute defaulting to:
The concatenation of the following: the name of the referencing relationship property or field of the referencing entity or embeddable class; "(underscore)"; the name of the referenced primary key column. If there is no such referencing relationship property or field in the entity, or if the join is for an element collection, the join column name is formed as the concatenation of the following: the name of the entity; "(underscore)"; the name of the referenced primary key column.
On the other side of the relationship, the OneToMany side, it's missing the mappedBy attribute (it should be equal to the name of the field that owns the relationship, in your case "company"). Javadoc says that this attribute is required unless the relationship is unidirectional, so there are chances that the JPA implementation you are using is assuming the relationship is unidirectional.

JPA SortedMap mapping - avoid two columns with keys

I have two classes, CalculatedValue and Price. Price has map of CalculatedValue. Each CalculableValue instance has name, value and couple of other fields.
Here is mapping I use to describe a dependency between Price and the CV:
#OneToMany(
cascade = CascadeType.ALL,
fetch = FetchType.EAGER
)
#JoinColumn(name = "priceId")
private Map<String, CalculatedValue> calculatedValues =
new TreeMap<String, CalculatedValue>();
No join table, just mapping by priceId column which refers to Price unique Id.
Here is how generated table looks like:
CREATE TABLE PUBLIC.CALCULATEDVALUE (
UNIQUEID BIGINT NOT NULL,
KEY VARCHAR(2147483647) NOT NULL,
PRICEID BIGINT,
VALUE DOUBLE NOT NULL,
CALCULATEDVALUES_KEY VARCHAR(2147483647),
PRIMARY KEY (UNIQUEID)
);
ALTER TABLE PUBLIC.CALCULATEDVALUE
ADD FOREIGN KEY (PRICEID)
REFERENCES TEST.PUBLIC.PRICE (UNIQUEID);
Everything is working, but I want to know if it possible to to this:
Avoid automatic "CALCULATEDVALUES_KEY" column creation. I already have this value stored in KEY column and it would be nice to avoid duplication and somehow give a hint to JPA.
Trigger cascade delete of calculable value for each removed price (in case I'm running SQL delete statement)
Will such mapping work in case I'll use Date as a key? Not for this particular field, but for a bunch of other ones it will be useful. Assuming the same OneToMany relationship.
Thank you in advance!
PS. I'm using latest version of EclipseLink & H2 as database.
PPS. Didn't want to store the calculable values in array since I need to often find it buy key in Java.
For info on Maps see,
http://en.wikibooks.org/wiki/Java_Persistence/OneToMany
and,
http://en.wikibooks.org/wiki/Java_Persistence/Relationships#Maps
and,
http://www.eclipse.org/eclipselink/documentation/2.4/jpa/extensions/a_cascadeondelete.htm#CIABIIEB
A few issues:
EclipseLink will use Hashtable by default for Map, if you want it to use TreeMap you need to define the field as TreeMap.
Do not give a #JoinColumn on a #OneToMany, this is only supported for advanced unidirectional #OneToMany, a normal #OneToMany should use a mappedBy and have an inverse #ManyToOne in the target entity. (this will fix your issue of the duplicate foreign key).
You need to specify the #MapKey for a map, otherwise it defaults to the id, which seems to be an integer here, not a string.
You can use #CascadeOnDelete in EclipseLink to cascade a delete on the database.

Meaning of #GeneratedValue with strategy of TABLE

The JPA specification gives the following explanation of the annotation #GeneratedValue(strategy=TABLE):
The TABLE generator type value indicates that the persistence provider must assign primary keys for the entity using an underlying database table to ensure uniqueness.
But what does "using an underlying database table" mean in practice? Does it mean using an auxiliary table? Or by scanning the entity-table to find an ID not in use? Or something else?
Check out JavaDoc for TableGenerator, it has a nice example of how it works:
Example 1:
#Entity public class Employee {
...
#TableGenerator(
name="empGen",
table="ID_GEN",
pkColumnName="GEN_KEY",
valueColumnName="GEN_VALUE",
pkColumnValue="EMP_ID",
allocationSize=1)
#Id
#GeneratedValue(strategy=TABLE, generator="empGen")
int id;
...
}
Example 2:
#Entity public class Address {
...
#TableGenerator(
name="addressGen",
table="ID_GEN",
pkColumnName="GEN_KEY",
valueColumnName="GEN_VALUE",
pkColumnValue="ADDR_ID")
#Id
#GeneratedValue(strategy=TABLE, generator="addressGen")
int id;
...
}
Basically ID_GEN is an internal (non-business) table of key-value pairs. Every time JPA wants to generate ID it queries that database:
SELECT GEN_VALUE
FROM ID_GEN
WHERE GEN_KEY = ...
and incremenets the GEN_VALUE column. This mechanism can be used to emulate sequences or to take even further control of generated ids.
In the case of EclipseLink, it uses an auxiliary table. The documentation says
By default, EclipseLink chooses the TABLE strategy using a table named SEQUENCE, with SEQ_NAME and SEQ_COUNT columns

JPA: Give a name to a Foreign Key on DB?

I have a simple questions. How can I give a name to the Foreign Key relations that arise from the # ManyToOne annotation ?
With JPA 2.1 you can just do this with the foreignKey annotation:
import javax.persistence.ForeignKey;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
#ManyToOne
#JoinColumn(name = "company_id", nullable = false, foreignKey = #ForeignKey(name="FK_COMPANY__ROUTE"))
private Company company;
Do not confuse with the deprecated hibernate equivalent
As of JPA 2.1 it is possible to define foreign keys via #ForeignKey annotation.
Unfortunately, it is not very useful if you only need to change the name. If you specify custom name of the FK, you also have to specify SQL definition of the FK. That is at least the way it works in EclipseLink 2.5.0.
If you are interested in naming the column used in the foreign key, one may specify the name of the column used to create the foreign key, using the #JoinColumn annotation along with the #ManyToOne annotation. The value of the name attribute of the #JoinColumn annotation is used by the JPA provider to map the column name in the table to the entity's attribute.
However, the name of the foreign key constraint created itself cannot be configured. At the time of writing this, it is not possible to specify the name of the foreign key constraint using a JPA annotation or configuration parameter in the OR mapping files. If you need to change the name of the foreign key constraint, then you must create the DDL statement yourself, instead of relying on the JPA provider to do this.
I think #ForeignKey doesn't work with #JoinTables or I don't know how to set custom names by this, I have tried it on #JoinTable->foreignKey and #JoinColumn->foreignKey