I have a many-to-many relationship with three tables and entities adn the join table contains additional column. On both sides of the relationship I have set cascadeType.All
When I add new objects to owner side the merge method works fine but when I remove a child object from the owner and merge it, the corresponding rows in the join table will not be removed and I will have duplicate rows in there.
owner entity
#OneToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "definitionType")
private List<DefinitionProperty> definitionProperties = new ArrayList<DefinitionProperty>();
#OneToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "property")
private List<DefinitionProperty> definitionProperties= new ArrayList<DefinitionProperty>();
mapping entity
#Id
#JoinColumn(name = "dtid", referencedColumnName = "id")
#ManyToOne(optional = false)
private DefinitionType definitionType;
#Id
#JoinColumn(name = "prid", referencedColumnName = "id")
#ManyToOne(optional = false)
private Property property;
I am not calling remove method of my entity manager at all and I am expecting the cascading to remove the unwanted rows automatically. Is that possible? what should I do to in order to remove those rows?
I can add my code here if it help
It just needed orphanRemoval=true on the owner side.
Related
I have yet another #OneToMany question. In this case, I'm trying to model a person having a list of excluded people they shouldn't be able to send items to. This is a Spring Boot app using JPA.
In the code below, the exclusions list populates properly but the excludedBy List does not. Because of this, I believe that is causing the deletion of a Person that is excluded by another person to fail because the Exclusion in excludedBy is not mapped on the object properly.
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue
#Column(nullable = false)
Long id;
...
#OneToMany(mappedBy = "sender", cascade = { CascadeType.ALL })
List<Exclusion> exclusions = new ArrayList<>();
//This is not getting populated
#JsonIgnore
#OneToMany(mappedBy = "receiver", cascade = { CascadeType.ALL })
List<Exclusion> excludedBy = new ArrayList<>();
...
}
#Entity
#Table(name = "exclusions")
public class Exclusion {
#Id
#GeneratedValue
#Column(nullable = false)
Long id;
#ManyToOne
#JsonIgnore
Person sender;
#ManyToOne
Person receiver;
...
}
I would expect that this would have mapped the bidirectional relationship properly and as such the excludedBy List would be populated as well.
Any wisdom on this matter would be great!
1 - An #Id is by default not nullable, not required:
#Column(nullable = false)
2 - There is no need for an #Id in this class. Both sides of the exclusion are together unique. Not needed:
#Id
#GeneratedValue
Long id;
3 - An "Exclusion" requires both an excludedBy and an excluded, give them names that match and they are your #Id. It is a 2 way ManyToMany relationship.
#Entity
#Table(name = "exclusions")
public class Exclusion {
#Id
#ManyToMany // An ID so not optional, so no need for (optional = false)
Person excludedBy;
#Id
#ManyToMany // An ID so not optional, so no need for (optional = false)
Person excluded;
}
Entity Exclusion always knows both sides of the story.
#ManyToMany(mappedBy = "excludedBy", cascade = { CascadeType.ALL })
List<Exclusion> excluded = new ArrayList<>();
#ManyToMany(mappedBy = "excluded", cascade = { CascadeType.ALL })
List<Exclusion> excludedBy = new ArrayList<>();
Tip: JSON DTOs shouldn't be defined in your JPA DTOs, otherwise you can't change your internal data model independently of your external API model.
I had this problem in the past. Your key problem ist that your ORM Mapper hibernate does not know which of your database entries need to be assinged to exclusions and which are assiged to excludedBy. You need a discriminator and add the constraint in your select. I would propose a solution that looks something like this:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "PRIMARY_KEX_IN_EXCLUSION_TABLE", referencedColumnName = "id")
#Where(clause = "is_excluded_by = 0")
private Set<Exclusion> exclusions;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "PRIMARY_KEX_IN_EXCLUSION_TABLE", referencedColumnName = "id")
#Where(clause = "is_excluded_by = 1")
private Set<Exclusion> excludedBy;
the value isExcludedBy needs to be a database column, part of your Entity and set in your code manually.
I think you also need to use Set instead of List when having multiple collections in one Entity. https://vladmihalcea.com/spring-data-jpa-multiplebagfetchexception/
I've got the following code:
#OneToOne(cascade = ALL)
private SquadMember commander;
#OneToOne(cascade = ALL)
private SquadMember lieutenant;
#OneToMany(orphanRemoval = true, cascade = REMOVE, mappedBy = "squad")
private Set<SquadMember> memberList = new LinkedHashSet<>();
When I add a commander or lieutenant, they end up in the SquadMember table and when I retrieve the memberList from the database the commander and lieutenant are included in the memberList(basically duplicated as they're in the Squad as commander/lieutenant but also as member).
How can I fix this so that the commander / lieutenant never appear in the memberList when retrieved from the database?
Posts I've already looked at but did not find my answer:
JPA OneToOne and OneToMany on the same entity
I suppose the piece of code you have put is from the entity "Squad".
In the SquadMember entity you must have three Squad type parameters for the bidirectional mapping to be correct, since if you are going to register a lieutenant and fill in his squad when you only have one parameter, JPA and Hibernate will associate it as being part of the ONE-TO-MANY relationship.
Verify that in the SquadMember entity you have the following:
//Relation members with squad
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "SQUAD_ID")
private Squad squad;
//Relation bidirectional for lieutenant
#OneToOne(fetch = FetchType.LAZY, mappedBy = "lieutenant")
private Squad squadLt;
//Relation bidirectional for commander
#OneToOne(fetch = FetchType.LAZY, mappedBy = "commander")
private Squad squadCm;
For example if you need to update commander, use squadCM, etc..
I have a relation between Accommodation and Booking classes, and also I set a foreign key in booking table.
ALTER TABLE `project`.`booking`
ADD INDEX `fk_accId_fk_idx` (`accommodation` ASC);
ALTER TABLE `project`.`booking`
ADD CONSTRAINT `fk_accId_fk`
FOREIGN KEY (`accommodation`)
REFERENCES `project`.`accommodation` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION;
Accommodation class:
#Entity
....
public class Accommodation implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private BigInteger id;
#OneToMany(mappedBy = "accommodation", fetch = FetchType.EAGER, cascade = CascadeType.REMOVE)
#JsonManagedReference
private List < Booking > bookings;
......getters setters
}
Booking class:
#Entity
public class Booking implements Serializable {
......
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "bookings", nullable = true)
#JsonBackReference
private Accommodation accommodation;
....getters setters
}
When I execute a query for listing accommodations, I get unknown column in field list error.
javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: could not extract ResultSet] with root cause
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'bookings7_.bookings' in 'field list'
Even I set the relation and define the foreign key in table, what is the reason that I get this error?
Try to define your join-table mapping manually in JPA. Drop your schema and let JPA create your tables:
Accommodation class
#OneToMany(mappedBy = "accommodation", fetch = FetchType.EAGER, cascade = CascadeType.REMOVE)
#JsonManagedReference
private List < Booking > bookings;
Booking class
#ManyToOne(fetch = FetchType.EAGER)
#JoinTable(name = "accommodation_booking_join_table",
joinColumns = {#JoinColumn(name="booking_id")},
inverseJoinColumns = #JoinColumn(name = "accommodation_id"))
#JsonBackReference
private Accommodation accommodation;
Try changing your column names to lower case in your db and entity class.
I had a situation like that, and I solved it by changing the field's position on the query. Looks like it's a MySQL bug, like (or the same as) the one mentioned on this post:
https://bugs.mysql.com/bug.php?id=1689
The description of this MySQL bug mentioned a similar workaround solution: "I found that by swapping that field's position in the table with another field that it worked OK."
I have a table called Order which has many Items, so I need a map to link the Items to the Order.
At the moment I have this:
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#Fetch(value = FetchMode.SUBSELECT)
#JoinTable(name = "order_map_item", joinColumns = #JoinColumn(name = "orderId"), inverseJoinColumns = #JoinColumn(name = "itemId"))
List<Item> items = new ArrayList<Item>();
but it doesn't allow duplicates if I want to add an Item twice.
When I edit the MySql database by hand and remove the index, I can add duplicates. But they are getting more if I call em.refresh(order);
Please tell me, what is best practice for my case? Can't find anything...
Looks like your problem is that the join table has obviously a composite primary key with each column as foregn key to both the tables.
However your use case dictates that there may be repetition. In this case, an alternate strategy could be a mapping entity
#Entity
public class OrderItemMapping {
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn(name="order")
private Order order;
#ManyToOne
#JoinColumn(name="item")
private Item item;
Order Class:
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Item> items;
Items Class:
#ManyToOne(cascade = {CascadeType.ALL} )
#JoinColumn(name = "orderId")
private Order order;
i would like to create an application in this context : Zk 6, Spring v3.1.1, JPA 2.0, Hibernate 4.1.4, all with annotations but i have some pb with JPA concept.
Here are a type of case study :
3 tables, all linked via a join table ; we are dealing with cardinality 0, n.
So we have T_E_USER, T_E_TYPE and T_E_AIR.
Each table has a numeric ID, and a simple VARCHAR field.
A join table is created with T_J_USR_TPE_AIR with the 3 ID referenced by foreign keys forming a composed primary key.
I'm using Hibernate Tools for generate my entities (version JPA).
And that's where the problems start ....
I have, in each entity class, an attribute of type set with annotation # OneToMany.
I have a class representing the join that has an id attribute of complex type (another class) with an annotation EmbeddedId for a composite key.
And attributes representing the three entities with annotations # ManyToOne.
Here are my questions, because that's where I'm confused:
which should i set into the "mappedBy" attribute in the annotation # OneToMany of my entities?
Am I forced to do a class entity representing the join?
How does the CASCADE? Is it possible to use it in this context to enrich the join table "automatically"? Or should I manually instantiate the class representative of the join in order to persist the information myself?
A big thank you in advance for any kind soul who could give me a helping hand.
Thank you for your answers but one said "yes" when the other says "no" lol
Here's what I did during the day but I have not yet been tested.
In each entity table, i added a #OneToMany relation with mappedBy setted to the attribute defined in "join" entity :
#OneToMany(fetch = FetchType.LAZY,
mappedBy = "aircraft",
cascade = { CascadeType.REMOVE })
private Set<UserConfig> userConfigs = new HashSet<UserConfig>(0);
...
#OneToMany(fetch = FetchType.LAZY,
mappedBy = "userAccount",
cascade = { CascadeType.REMOVE })
private Set<UserConfig> userConfigs = new HashSet<UserConfig>(0);
...
#OneToMany(fetch = FetchType.LAZY,
mappedBy = "referenceType",
cascade = { CascadeType.REMOVE })
private Set<UserConfig> userConfigs = new HashSet<UserConfig>(0);
And i created a new Entity for the join table.
#Entity
#Table(name = "T_J_USR_RFT_AIR_URA")
public class UserConfig implements java.io.Serializable {
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "airId",
column = #Column(name = "URA_AIR_ID", nullable = false)),
#AttributeOverride(name = "usrId",
column = #Column(name = "URA_USR_ID", nullable = false)),
#AttributeOverride(name = "rftId",
column = #Column(name = "URA_RFT_ID", nullable = false))
})
private UserConfigId id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "URA_RFT_ID", nullable = false, insertable = false, updatable = false)
private ReferenceType referenceType;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "URA_USR_ID", nullable = false, insertable = false, updatable = false)
private UserAccount userAccount;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "URA_AIR_ID", nullable = false, insertable = false, updatable = false)
private Aircraft aircraft;
...
getter & setter
}
Where UserConfigId is :
#Embeddable
public class UserConfigId implements java.io.Serializable {
#Column(name = "URA_AIR_ID", nullable = false)
private Integer airId;
#Column(name = "URA_USR_ID", nullable = false)
private Integer usrId;
#Column(name = "URA_RFT_ID", nullable = false)
private Integer rftId;
...
getter & setter
}
What do you think about this practice ?
I just used "cascade" if an object of the join table is deleted in order to delete all element associated in the join.
Is it all right ?
Anyway thank you Tom, i will analyzed your link.
Thank you JMelnyk too.
You are welcome if you want to demonstrate what are the best practices for this case.
Three-way joins are tricky. I think what you've done, using an entity for the join table, is probably the right thing to do. To answer your questions:
Your #OneToMany attributes refer to the entity mapping the join table; they should be mappedBy the appropriate #ManyToOne attribute in that entity.
Yes, unfortunately, an entity for the join table is the best way to do this.
Cascades can be used to automatically add objects to the database, but not to create objects. You will need to create instances of the join entity in code.
which should i set into the "mappedBy" attribute in the annotation #
OneToMany of my entities?
mappedBy attribute represents a property name you are joining on. Read more...
e.g. AnyEntity holds List<Employee> which is joined on (mappedBy) department property in Employee entity, and that department property holds the association.
Am I forced to do a class entity representing the join?
No, you do not provide an entity class for join tables.
How does the CASCADE? Is it possible to use it in this context to
enrich the join table "automatically"? Or should I manually
instantiate the class representative of the join in order to persist
the information myself?
Yes it is possible to enrich associations of the entity and itself by marking associations with desired cascade type.
e.g. We have a Department which holds List<Employee> and I put CascadeType.PERSIST on employees. Now we populate department objects with its properties and employees. When we are finished, we persist only the department, and it will cascade operation to employees.