#ElementCollection of #Embeddable containing #ManyToOne - jpa

I have the following model
#Entity
#Table(name = "GRAPH")
public class Graph {
[...]
#ElementCollection
#CollectionTable(name = "ROOT", joinColumns = #JoinColumn(name = "GRAPH", nullable = false))
private Set<Root> roots;
}
#Entity
#Table(name = "NODE")
public class Node {
[...]
}
#Embeddable
public class Root {
[...]
#ManyToOne(optional = false)
#JoinColumn(name = "NODE", nullable = false)
private Node node;
}
I use EclipseLink as the JPA Provider. When letting EclipseLink generate the DDL for this structure, the following stuff happens:
The is no primary key on the ROOT table (OK, it is an #Embeddable, and it does not have an identity)
A foreign key is generated from ROOT.GRAPH to GRAPH.ID (As expected)
There is no foreign key from ROOT.NODE to NODE.ID (That's something I can not understand)
Can you help explain me the cause for this behavior? Is there something that can be done about the primary key and the missing foreign key?
Thanks,
M.

Related

JPA - Eclipselink - InheritanceType.JOINED not doing the correct JOIN

I dont know if this is a bug or there is something I am missing, but there is a problem when you try to use Inheritance.JOINED when the superclass has a composite primary key.
I have the following classes:
(Superclass)
#Entity
#Table(name = "tipos_opciones_misiones")
#IdClass(TipoOpcionMisionId.class)
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "clase", discriminatorType = DiscriminatorType.STRING)
#ReadOnly
public abstract class TipoOpcionMision{
#Id
#ManyToOne
#JoinColumn(name="tipo_mision")
private TipoMision tipoMision;
#Id
#Column(name = "numero")
private int numero;
}
And a child class:
#Entity
#Table(name="tipos_misiones_opciones_comercio_compra")
#DiscriminatorValue("COMERCIO_COMPRA")
public class TipoOpcionMisionComercioCompra extends TipoOpcionMision{
#Column(name="valor")
double valor;
}
When I try to get a list on objects "TipoOpcionMision" the generated SQL ignores that there is a composite key [tipo_mision, numero] and it just uses "t1.numero = t0.numero". There should also be a "t1.tipo_mision= t0.tipo_mision".
SELECT **list of fields***, FROM tipos_misiones_opciones t0, tipos_misiones_opciones_comercio_compra t1 WHERE ((t0.tipo_mision = 'MISION_1') AND ((t1.numero = t0.numero) AND (t0.clase = 'COMERCIO_COMPRA')))
There is no errors, but I get false results because I am getting the values of the first row in the cartesian product.
I have tried to add:
#PrimaryKeyJoinColumns({
#PrimaryKeyJoinColumn(name = "tipo_mision", referencedColumnName = "tipo_mision"),
#PrimaryKeyJoinColumn(name = "numero", referencedColumnName = "numero")
})
But program fails when starting with the following error:
Exception Description: A #PrimaryKeyJoinColumns was found on the annotated element [class TipoOpcionMisionComercioCompra]. When the entity uses a single primary key, only a single (or zero) #PrimaryKeyJoinColumn should be specified.
It seems that for some reason Eclipselink is ignoring tthat the superclass has a composite primary key.

ConstraintViolationException could not execute statement; SQL [n/a]; constraint [id]

I have two entities, fileVersion and fileEnvironment, which have a many to many relationship. I'm using a junction table, modeled by fileDeployment entity.
The junction entity:
#Data
#Builder(toBuilder = true)
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(
name = "file_deployment"
)
public class FileDeploymentEntity {
#EmbeddedId
private FileDeploymentKey id;
#ToString.Exclude
#ManyToOne
#MapsId("fileVersionId")
#JoinColumn(name = "fileVersionId")
private FileVersionEntity fileVersion;
#ToString.Exclude
#ManyToOne
#MapsId("fileEnvironmentId")
#JoinColumn(name = "fileEnvironmentId")
private FileEnvironmentEntity fileEnvironment;
}
It's composite key:
#Embeddable
#NoArgsConstructor
#AllArgsConstructor
#Data
#Builder(toBuilder = true)
public class FileDeploymentKey implements Serializable {
#Column
private UUID fileVersionId;
#Column
private UUID fileEnvironmentId;
}
Its JPA repository:
#Repository
public interface FileDeploymentEntityRepository extends
JpaRepository<FileDeploymentEntity, FileDeploymentKey>,
JpaSpecificationExecutor<FileDeploymentEntity> {
}
The two entities for which the junction entity is capturing the many-to-many relationship for:
#Data
#Builder(toBuilder = true)
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(
name = "file_environment"
)
public class FileEnvironmentEntity {
#Id
#GeneratedValue(generator = "UUID")
#GenericGenerator(name = "UUID", strategy = "uuid2")
private UUID id;
#ToString.Exclude
#OneToMany(mappedBy = "fileEnvironment")
private List<FileDeploymentEntity> fileDeployments;
}
FileVersion is the other
#Data
#Builder(toBuilder = true)
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(
name = "file_version"
)
public class FileVersionEntity {
#Id
#GeneratedValue(generator = "UUID")
#GenericGenerator(name = "UUID", strategy = "uuid2")
private UUID id;
#ToString.Exclude
#OneToMany(mappedBy = "fileVersion")
private List<FileDeploymentEntity> fileDeployments;
}
The following code executes fine:
var fileDeploymentEntity = FileDeploymentEntity.builder()
.id(FileDeploymentKey.builder()
.fileVersionId(existingFileVersion.get().getId())
.fileEnvironmentId(existingFileEnvironment.get().getId())
.build())
.deploymentTime(
Instant.now(clock))
.fileEnvironment(existingFileEnvironment.get())
.fileVersion(existingFileVersion.get())
.build();
var result = fileDeploymentEntityRepository.save(fileDeploymentEntity);
But when eventually fileDeploymentEntityRepository.flush() is called I get the following exception:
could not execute statement; SQL [n/a]; constraint [id]
org.hibernate.exception.ConstraintViolationException: could not
execute statement
org.postgresql.util.PSQLException: ERROR: null value in column "id"
violates not-null constraint Detail: Failing row contains
(7670fec3-3766-4c69-9598-d4e89b5d1845,
b9f6819e-af89-4270-a7b9-ccbd47f62c39, 2019-10-15 20:29:10.384987,
null, null, null, null).
If I also call save for the 2 entities it doesn't change the result:
fileVersionEntityRepository
.save(existingFileVersion.get().addFileDeployment(fileDeploymentEntity));
fileEnvironmentEntityRepository
.save(existingFileEnvironment.get().addFileDeployment(fileDeploymentEntity));
Any help would be greatly appreciated!
For me the issue was that I named another entity with the same table name by accident which caused the schema that was generated to be very different from what I thought it was.
Take away lesson:
1) Check the schema that is generated when in doubt.
var con = dataSource.getConnection();
var databaseMetaData = con.getMetaData();
ResultSet resultSet = databaseMetaData.getTables(null, null, null, new String[]{"TABLE"});
System.out.println("Printing TABLE_TYPE \"TABLE\" ");
System.out.println("----------------------------------");
while(resultSet.next())
{
//Print
System.out.println(resultSet.getString("TABLE_NAME"));
}
ResultSet columns = databaseMetaData.getColumns(null,null, "study", null);
while(columns.next())
{
String columnName = columns.getString("COLUMN_NAME");
//Printing results
System.out.println(columnName);
}

How to reference a Composite Secondary Key

The below examples show what I tried to reference an entity by a unique combination of columns that is not its primary key.
I want to do it that way because the referencing table contains all the necessary constituents of the referenced "Composite Secondary Key" but it does not contain the Primary Key.
How can I achieve this? (If possible without being Hibernate specific)
Base example
Suppose an entity has a primary key as well as a composite secondary key:
#Entity
#Table(name = "EXAMPLE_DATA",
uniqueConstraints = {
#UniqueConstraint(columnNames = {
"COMPOSITE_KEY_PART_1",
"COMPOSITE_KEY_PART_2"})
})
public class ExampleData {
#Id
#Column
private Long exampleDataId;
#Column(name = "COMPOSITE_KEY_PART_1")
private String compositeKeyPart1;
#Column(name = "COMPOSITE_KEY_PART_2")
private String compositeKeyPart2;
}
I would like to reference it from another table by its composite secondary key:
#Entity
#Table(name = "EXAMPLE")
public class Example {
#Id
#Column
private Long exampleId;
#Column(name = "COMPOSITE_KEY_PART_1")
private String compositeKeyPart1; // of ExampleData
#Column(name = "COMPOSITE_KEY_PART_2")
private String compositeKeyPart2; // of ExampleData
#MayToOne
#JoinColumn(name = "COMPOSITE_KEY_PART_1", insertable = false, updatable = false)
#JoinColumn(name = "COMPOSITE_KEY_PART_2", insertable = false, updatable = false)
private ExampleData exampleData;
}
However, this leads to
org.hibernate.AnnotationException:
A Foreign key refering com.example.ExampleData from com.example.Example has the wrong number of column. should be 1
Making a separate #Embeddable composite key
I tried making the secondary key embeddable
#Embeddable
public class CompositeKey implements Serializable {
#Column(name = "COMPOSITE_KEY_PART_1")
private String compositeKeyPart1;
#Column(name = "COMPOSITE_KEY_PART_2")
private String compositeKeyPart2;
}
and using it as an embedded object:
#Entity
#Table(name = "EXAMPLE_DATA",
uniqueConstraints = {
#UniqueConstraint(columnNames = {
"COMPOSITE_KEY_PART_1",
"COMPOSITE_KEY_PART_2"})
})
public class ExampleData {
#Id
#Column
private Long exampleDataId;
#Embedded
private CompositeKey compositeKey;
}
but that leads to the same exception:
org.hibernate.AnnotationException:
A Foreign key refering com.example.ExampleData from com.example.Example has the wrong number of column. should be 1
Using #EmbeddedId
Using #EmbeddedId instead of just #Embedded leads to issues with multiple keys
org.hibernate.AnnotationException:
com.example.CompositeKey must not have #Id properties when used as an #EmbeddedId: com.example.ExampleData.compositeKey
Having only a single key works but is undesirable
The only way I can actually make it work is by removing the primary key and making the composite key the primary key (but I don't want that)
#Entity
#Table(name = "EXAMPLE_DATA")
public class ExampleData {
// #Id // removing this primary key is undesirable
#Column
private Long exampleDataId;
#EmbeddedId // This now becomes the primary key
private CompositeKey compositeKey;
}

How to name Foreign Key with #CollectionTable in JPA?

I need help with resolving our problem with naming FK in JPA. We have one embeddable entity e.g. Foo which is used as collection in another one entity Bar.
embeddable entity:
#Embeddable
public class Foo{
#Column(name = "foo1")
private String foo1;
#Column(name = "foo2")
private String foo2;
}
main entity:
#Entity
#Table(name = "Bar")
public class Bar {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
#Id
private Long id;
#ElementCollection
#CollectionTable(name = "foos", joinColumns = #JoinColumn(name = "bar_id", foreignKey = #ForeignKey(name = "foo_bar_fk"), nullable = false))
private List<Bar> bars;
}
When I generated tables in database (postgres) foreign key is called fdk44l345k64 instead foo_bar_fk. Can you tell me how to fix it? Thanks.

using #Embedabble with a foreign key and manyToMany relation

I wrote an example for the code i am trying to implement, i get an error with Constraint "Student_Teacher_FK" already exists.
the #embiddable class has a foreign key that is created twice with current code.
#Entity
public class Teacher {
#Id
#GeneratedValue
private Long id;
#Column(name = "Name")
private String name;
}
#Entity
public class Student{
#Id
#GeneratedValue
private Long id;
#Column(name = "Name")
private String name;
}
#Embeddable
public class StudentList implements Serializable {
#ManyToMany
#JoinTable(name = "Student_Teacher",
joinColumns =
#JoinColumn(name = "Student_ID", referencedColumnName = "ID"),
inverseJoinColumns =
#JoinColumn(name = "Teacher_ID", referencedColumnName = "ID")
)
#ForeignKey(name = "Student_Teacher_FK", inverseName = "Teacher_Student_FK")
public List<Student> studentList = new ArrayList<Student>();
}
#Entity
public class HistoryTeacher extends Teacher {
#Embedded
#NotNull
private StudentList StudentList = new StudentList ();
}
#Entity
public class LangTeacher extends Teacher {
#Embedded
#NotNull
private StudentList StudentList = new StudentList ();
}
#Entity
public class RetiredTeacher extends Teacher {
// has no students
}
#embeddable : Defines a class whose instances are stored as an intrinsic part of an owning entity and share the identity of the entity (http://docs.oracle.com/javaee/6/api/javax/persistence/Embeddable.html)
As you are declaring it in 2 different entity, jpa will create associated association table (student-teacher) 2 times with associated fk, which is explicitely named, and so created 2 times too with the same name. Here is your error.
I don't think using #embeddable is appropriated for what you're intending to do. A student has is own existence and is not part of teacher itself (not an uml composition / black diamond) so it's not an embeddable entity. Student list should be held by teacher entity using a simple manyToMany association.