Can JPA/JaxB populate foreign key by itself? - java

I am using classes which are JPA annotated to map xml data into a database and the other way around via JAXB. Problem is that objects created by JAXB do not include foreign key fields and therefore are null. This conerns ownerId in example below.
Is there a way to fix this without looping through the whole tree again and add foreign keys ?
#Entity
public class Element {
#Id
String id;
#OneToMany(mappedBy = "owner", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Property> properties;
}
#Entity
public class Property {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int propertyId;
#ManyToOne(optional = false, cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn(name = "ownerId", referencedColumnName = "id")
private Element owner;
}

The relationship is owned by the Property so the Property has to know related Element object to have it saved to DB. Adding a property to the list inside the element is not enough.
for (Property p : element.getProperties()) p.setOwner(element);
will solve the problem with the key.
If you read data from XML, typically your Property will not contained the referenced Element, only Element would have the list of Included properties.
You may also want to remove CascateType.ALL from the ManyToONe in Property, as removing a Property will trigere removing its parent Element and all the related Properties, which is probably not what you wanted).
But you may want to add orphanRemoval = true, to the OneToMany in Element, so if a property is removed from the list, it is also deleted in the DB.

What about adding ownerId property into your Property class like this?
#Column("ownerId", insertable=false, updatable="false")
private ownerId;
In this case you will have both ownerId and owner in your class. ownerId is read only and owner is used to assign/update values.
If you want to make it the opposite way around move insertable=false, updatable=false to owner property annotations.

Related

Foreign key assigned to NULL in JPA one-to-many relationship

I have the following pair of entity classes:
#Entity(name="metadata")
public class Metadata {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
// Note: Hibernate CascadeType.ALL is also being used elsewhere in this class, hence
// the fully qualified class name used below
#OneToMany(cascade = javax.persistence.CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "metadata")
private List<Attachment> attachments;
// getters and setters
}
#Entity(name="attachment")
public class Attachment {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "metadata_id", referencedColumnName = "id", nullable = false, updatable = false)
private Metadata metadata;
// getters and setters
}
For completeness, here is how I am building the Metadata object:
Metadata metadata = modelMapper.map(req, Metadata.class);
List<Attachment> attachments = new ArrayList<>();
// the files come as a parameter to a Spring controller endpoint (FYI)
for (MultipartFile file : files) {
Attachment attachment = new Attachment();
attachment.setContents(file.getBytes());
attachment.setFilename(file.getOriginalFilename());
attachments.add(attachment);
}
metadata.setAttachments(attachments);
metadata.setDraft(isDraft);
myJPARepository.save(metadata);
What I observe when creating a Metadata entity and then calling save() from my JPA repository is that all data does get correctly written to my database (Postgres). However, the join column metadata_id is always NULL. At first, I thought this might have been caused due to the referencedColumnName attribute not being set (whose default is ""). However, adding this in as you see above did remedy the problem.
Does anyone know why the join column metadata_id is always appearing as NULL in the database table?
You need to synch both of your object, as of now you are creating metadata object and adding attachment to it and you have cascade so that will save both entities into their respective table.
But, since you have bidirectional relationship, you are only synching one side of relation ship here only, you need to set the same metadata object to each attachment object as well, then hibernate will be able to link the foreign key.
Instead of setter I would suggest use a add function on metadata object something like this
public void addAttachment(Attachment attachment) {
attachments.add(attachment);
attachment.setMetadata(this);
}
This way both the object would be in synch, use that inside in your for loop, you may have to initialise your collection inside metadata object before doing that or you can first check in above add function that if attachments list is null then create one and then add.

Drop not null constraint for joinColumns in JoinTable

I have an entity, leaning on which hibernate able to generate join table for OneToMany-relations.
#Entity
public class RequestType extends EntityObject {
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "MType_MType", joinColumns = #JoinColumn(name = "mtype_id"),
inverseJoinColumns = #JoinColumn(name = "inner_request_types_id"))
private List<LogicalInnerType> innerRequestTypes;
}
#Entity
public class EntityObject {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "EntityID")
private Integer id;
}
But when I run application, I receive the following exception:
PSQLException: EROOR: null value in column "inner_request_types_id" violate constraint NOT NULL
As I know, any JoinColumn has a default true of nullable-parameter.
How could I otherwise drop notnull-constraint?
Yes, the default value of JoinColumn.nullable() is true:
/** (Optional) Whether the foreign key column is nullable. */
boolean nullable() default true;
But this is not relevant here, because nullable() is not inspected for #JoinColumn in a #OneToMany. Hibernate will always add a not null constraint to both columns, because it expects that there is always an owning entity and that the list never contains null values.
If you wanted to add null values to your list in the first place (and this is not a mistake), you should think about an alternative. For example you could add a boolean attribute to RequestType that indicates the state that you wanted to achieve with the null value.
By the way there is another thing, that is apparently not correct in your model. You are using a List, but you add no #OrderColumn. As a result Hibernate has no possibility to ensure any order. If you really want to store a specific order, you should add an #OrderColumn annotation. Or you use a Set instead.

Hibernate-OneToMany mapping for existing DB Tables

I am trying to join to Hibernate Entities in a OneToOne Mapping. I am able to fetch the data for a given primary key from the Main Entity, the joining entity, however, returns null. I am new to hibernate and any help will be appreciated.
I have two Tables,
PT_CORE
Primary Key: ptId - Integer;
Foreign Key: stId(ST_AUX) - Integer;
Columns: ptId, ptName
ST_AUX
Primary Key: stId;
Columns: stId, stName
The two tables get populated by other applications and mine is a read-only operation.
Below is my first Entity class(PtCore.java)
#Entity
#Table(name="PT_CORE")
public class PtCore implements Serializable{
#Id
#Column(name="ptId", nullable = false)
private int id;
#Column(nullable=false)
private int stId; //The Foreign key column
#OneToOne
#JoinTable( name = "core_aux", joinColumns = {#JoinColumn(Name="ptId")},
inverseJoinColumns = {#JoinColumn(Name="stId")}
)
private StAux staux;
//Getters, setters and toString() for above
}
StAux is another Entity, defined as below,
#Entity
#Table(name="ST_AUX")
public class StAux implements Serializable {
#Id
#Column(nullable=false)
private Integer stId;
#OneToOne
private PtCore ptcore;
#Column
private String stName;
//Getters, Setters and toString follow.
}
I do below in the Service method:
PtCore obj = (PtCore) session.get(PtCore.class,1);
System.out.println(obj);
In the Results, I get the value of ptName, but the stAux class variables are null, Indicating that the join does not work as expected.
First of all you have the mapping information existing in your PT_CORE. And I assume it is something like FOREIGN KEY (stid) REFERENCES (stid). If you want to use existing schema and existing data I guess there is no mapping table core_aux really existing. At least you did not mention it. However it is visible as #JoinTable annotation but still there is this above mentioned foreign key which seems to be the real mapping (so again not the join table).
I suggest the following
remove this
#Column(nullable=false)
private int stId; //The Foreign key column
from your PtCore. I think it is not needed. Also in PtCore, remove the #JoinTable (because what I told above) and add mapping informaiion to #OneToOne annotation, like:
#OneToOne
#JoinColumn(name = "stid")
private StAux staux;
from your PT_CORE.
Then in StAux alter also a bit:
#Id
#Column(name = "stid") // this might not be needed but if there is like "st_id"...
private Integer stId; // so just for sure
#OneToOne(mappedBy = "staux")
private PtCore ptcore;
Because you have existing tables and constraints there might raise errors if hibernate tries to auto-generate those again by JPA instructions.
Check this for example for more information.
UPDATE: just realized also that in your title is #OneToMany but in your code is #OneToOne.
So you might want to elaborate your question and/or title a bit.
In your relation, the owning side is PtCore, the inverse side is StAux.
In bidirectional OneToOne relations, the inverse side has to have the mappedBy attribute. Actually, the mappedBy attribute contains the name of the association-field on the owning side.
So, you must change your inverse side code (StAux Entity). You have to add mappedBy attribute to #OneToOne in StAux class:
#OneToOne(mappedBy="staux")
private PtCore ptcore;

How can I cascade delete a collection which is part of a JPA entity?

#Entity
public class Report extends Model {
public Date date;
public double availability;
#ElementCollection
#Cascade(value={CascadeType.ALL})
public Map<FaultCategory, Integer> categories;
}
In one of my jobs I have the following code:
int n = MonthlyReport.delete("date = ?", date);
This always fails to delete the entity with the following error:
The DELETE statement conflicted with the REFERENCE constraint "FK966F0D9A66DB1E54". The conflict occurred in database "TFADB", table "dbo.MonthlyReport_categories", column 'MonthlyReport_id'.
How can I specify the mapping so the elements from the categories collection get deleted when the report is deleted?
Cascading delete (and cascading operations in general) is effective only when operation is done via EntityManager. Not when delete is done as bulk delete via JP QL /HQL query. You cannot specify mapping that would chain removal to the elements in ElementCollection when removal is done via query.
ElementCollection annotation does not have cascade attribute, because operations are always cascaded. When you remove your entity via EntityManager.remove(), operation is cascaded to the ElementCollection.
You have to fetch all MonthlyReport entities you want to delete and call EntityManager.remove for each of them. Looks like instead of this in Play framework you can also call delete-method in entity.
The answer provided by J.T. is correct, but was incomplete for me and for sebge2 as pointed out in his/her comment.
The combination of #ElementCollection and #OnDelete further requires #JoinColumn().
Follow-up example:
#Entity
public class Report extends Model {
#Id
#Column(name = "report_id", columnDefinition = "BINARY(16)")
public UUID id; // Added for the sake of this entity having a primary key
public Date date;
public double availability;
#ElementCollection
#CollectionTable(name = "report_category", joinColumns = #JoinColumn(name = "report_id")) // choose the name of the DB table storing the Map<>
#MapKeyColumn(name = "fault_category_key") // choose the name of the DB column used to store the Map<> key
#Column(name = "fault_category_value") // choose the name of the DB column used to store the Map<> value
#JoinColumn(name = "report_id") // name of the #Id column of this entity
#OnDelete(action = OnDeleteAction.CASCADE)
#Cascade(value={CascadeType.ALL})
public Map<FaultCategory, Integer> categories;
}
This setup will create a table called report and another table report_category with three columns: report_id, fault_category_key, fault_category_value. The foreign key constraint between report_category.report_id and report.report_id will be ON DELETE CASCADE. I tested this setup with Map<String, String>.
We found the magic ticket! Add OnDelete(action= OnDeleteAction.CASCADE) to the ElementCollection. This allows us to remove the item from SQL (outside of the entityManager).
I met the same problem, and here is my code sample.
#ElementCollection
#CollectionTable(name = "table_tag", joinColumns=#JoinColumn(name = "data_id"))
#MapKeyColumn(name = "name")
#Column(name = "content")
private Map<String, String> tags
After a lot of tries, finally, I just add foreign key constraint for the table_tag.data_id to the parent table's primary key. Notice that you should set ON DELETE CASCADE to the constraint.
You can delete the parent entity by any ways, and the child element collection would be deleted too.
As an alternative to the hibernate-specific annotation #org.hibernate.annotations.OnDelete, you can also provide the constraint via #javax.persistence.ForeignKey to customize automatic schema generation:
#CollectionTable(name = "foo_bar", foreignKey = #ForeignKey(
name = "fk_foo_bar",
foreignKeyDefinition = "foreign key (foo_id) references Foo (id) on delete cascade"))
private List<String> bar;

Delete elements from one-to-many relationships when removing them from a JPA Entity

I am using JPA (Hibernate) with the following entity class with one one-to-many relationship.
When I add elements to the list, and then persist the Organization entity, it adds the new elements to the proyects table, but when I remove elements from the list, nothing happens when persist (or merge), and I would like these elements to be removed from the database.
I tried also orphanRemoval=true in the OneToMany annotation, but it doesn't work.
#Entity
public class Organization {
#Id
#GeneratedValue
public long internalId;
#Basic
#Column(nullable = false, length = 100)
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "organization")
private List<Proyect> proyects;
// Getters and Setters
}
You need to set Proyect.organization to null and update that entity, since this property is responsible for the database entry (Proyect is the owning side in this case ).

Categories