One-To-Many relation in JPA DELETE operation is not working - java

The entity model and repository is given below.
Channel.java
public class Channel extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "channel_name")
private String channelName;
#Column(name = "channel_type")
private Integer channelType;
#Column(name = "seq_id")
private Integer seqId;
#Column(name = "channel_device_key")
private String channeldeviceKey;
}
UserRoomChannel.java
public class UserRoomChannel extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
#OneToOne
#JoinColumn(name = "user_house_id")
private UserHouse userHouse;
#OneToOne
#JoinColumn(name = "room_id")
private Room room;
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List<Channel> channels;
}
UserRoomChannelReposirtory.java
public interface UserRoomChannelRepository extends JpaRepository<UserRoomChannel, Long> {
#Query(value = "DELETE FROM user_room_channel_channels WHERE channels_id=?1", nativeQuery = true)
void deleteUserRoomChannelChannels(Long id);
}
I can save the data successfully. When data is saved through this a third table named user_room_channel_channels is created.
EX:
user_room_channel_id channels_id
1 1
1 2
But When I tried to delete with channels_id it give me the error
A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance:.....
The native query what I write it execute from the command line.
But using JPA I can't do that.
Any help or any suggestion for resolving this issue?

A collection with cascade="all-delete-orphan" was no longer referenced
by the owning entity instance
is because before you delete your channels(and its children), you somehow do the following:
you load your UserRoomChannel along with its Channels children in the collection from the Database.
somewhere in your code you change the reference of the children collection : myUserRoomChannel.setChannels(newChannelCollections) or myUserRoomChannel.channels =new ChannelCollections();
and you try to delete the user with your repositorisory.
Hibernate who remembered having set the children collection with reference A to the User can find the collection anymore, because User.channels is now User.channels == B (with B being a new reference to your collection).
How to fix it:
just find the place where you are replacing your children collections and instead of:
myUserRoomChannel.setChannels(newChannelCollections), or
myUserRoomChannel.channels =new ChannelCollections(),
just do
myUserRoomChannel.getChannels().add/delete/clearYourChannels()

I just have done the below step and it works perfectly.
Reomve:
myUserRoomChannel.setChannels(channels)
Add
myUserRoomChannel.getChannels().removeAll(channels) and then
userRoomChannelRepository.save(myUserRoomChannel)

Related

JPA Cascaded Update/Delete of OneToMany

I have two entities "Article" and "Comments". Article has OneToMany relationship with Comment.
#Entity
#Table(name = "article")
public class Article implements Serializable
{
public Article()
{
}
private static final long serialVersionUID = 1L;
#Id
#Column(name = "article_id")
private int articleId;
#Column(name = "title")
private String title;
#Column(name = "category")
private String category;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "articleId", cascade = CascadeType.ALL)
private List<Comment> comments = new ArrayList<>();
}
#Entity
#Table(name = "comment")
public class Comment
{
private static final long serialVersionUID = 1L;
public Comment()
{
}
#Id
#Column(name = "comment_id")
private int commentId;
#Column(name = "author")
private String author;
#Column(name = "text")
private String text;
#JoinColumn(name = "article_id", nullable = false)
private int articleId;
}
I am using JPA EntityManager to perform CRUD operations on Article.
I have an article with the following data in the "article" table.
article_id title category
1 Java 8 in 30 days Java
I have two comments with the following data in the "comment" table.
comment_id author text article_id
1 ABC Java is awesome ! 1
2 XYZ Nice Article !!! 1
Here is my EntityManager code which gets invoked when there is an update to an article including comments.
public Article update(Article article)
{
return entityManager.merge(article);
}
The issue that I am facing here is that whenever I delete a Comment from an existing article using the above method call then the comment really does not get deleted from the table. I understand that "merge" is same as "upsert", but I did not find any other method in EntityManager interface to achieve the comment deletion along with other changes to article.
In your code, #OneToMany(... cascade = CascadeType.ALL) means that whenever a modification is done on the parent (persist, delete, etc.), it is cascaded to the children as well. So if an Article is saved, all its' corresponding Comments are saved. Or if an Article is deleted, all its' corresponding Comments are deleted.
In your case, what you want is just to delete a Comment, unrelated to operations that happen in its' Article.
An easy way to do that is use #OneToMany(... cascade = CascadeType.ALL, orphanRemoval = true) and then when you decide to trigger delete on the first Comment for example, just use Collection.remove() on it:
Article article = ...;
//...
Comment targetComment = article.getComments().iterator().next();
article.getComments().remove(targetComment);
// now Hibernate considers `targetComment` as orphan and it executes an SQL DELETE for it
Make sure your collection is the only one holding a reference to the object referred by targetComment, otherwise you will have inconsistencies in memory.

Spring query distinct on a entire object

I'm using spring data and I made this query:
#Query("SELECT DISTINCT u.pk.fleet.fleetName FROM FleetHasUser u WHERE u.pk.user.username = ?1")
List<FleetName> allFleetNameForUser(String username);
The aim of this query is find all FleetName for a specific user.
This is the part of database schema interested in:
The FleetHasUser class:
#Entity
#Table(name = "fleet_has_user", catalog = "dart")
#AssociationOverrides({
#AssociationOverride(name = "pk.user",
joinColumns = #JoinColumn(name = "id_username")),
#AssociationOverride(name = "pk.fleet",
joinColumns = #JoinColumn(name = "id_fleet")) })
public class FleetHasUser implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private FleetHasUserKeys pk = new FleetHasUserKeys();
#EmbeddedId
public FleetHasUserKeys getPk() {
return pk;
}
the FleetHasUserKeys class:
#Embeddable
public class FleetHasUserKeys implements Serializable {
private static final long serialVersionUID = 1L;
protected User user;
protected Fleet fleet;
Fleet class:
#Entity
#Table(name = "fleet", catalog = "dart")
public class Fleet implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private Integer idFleet;
private Ecu ecu;
private String application;
private Cubic cubic;
private Integer power;
private String euroClass;
private String engineType;
private Traction traction;
private String transmission;
private String note;
private FleetName fleetName;
#JsonIgnore
private Set<Car> cars = new HashSet<Car>(0);
#JsonIgnore
private Set<FleetHasUser> fleetHasUsers = new HashSet<FleetHasUser>(0);
As you can see FleetName is a class. I tried without distinct and the list had duplicated elements, so before create a subquery I inserted distinct only for test and it worked. I didn't find information on google, but is it possible to use distinct on a object? How does it work(check the primary key fields)?
Maybe may I even create a Spring data query like findDistinctPkFleetFleetNameByPkUserUsername?
Thanks
As far as i know, the distinct is being applied on the query not on the mapping phase, as you can see #Query doc the annotation only executes jpql queries so a native distinct.
As you say other option to execute a distinct is
Spring data Distinct
You cannot apply "Distinct" to objects, but you can map then on Set's to avoid duplicated, but the better approach is filter properly the data on the database.

JPA (Hibernate) issue saving ManyToOne relationship

I'm experiencing a problem: I have an Entity Definition, that has a OneToMany relationship with DefinitionInfo Entities and when I try to load an existing Definition, add a DefinitionInfo to it's infoList collection, saving says it can not find the entity of the DefinitionInfo I just added to the Definition. To this I say 'DUH!' I am trying to add a new DefintionInfo to the Definition so of course you won't find it!
Here's the code:
#Entity(name = "CohortDefinition")
#Table(name="cohort_definition")
#NamedEntityGraph(
name = "CohortDefinition.withDetail",
attributeNodes = { #NamedAttributeNode(value = "details", subgraph = "detailsGraph") },
subgraphs = {#NamedSubgraph(name = "detailsGraph", type = CohortDefinitionDetails.class, attributeNodes = { #NamedAttributeNode(value="expression")})}
)
public class CohortDefinition implements Serializable{
private static final long serialVersionUID = 1L;
...
#OneToMany(mappedBy="cohortDefinition", fetch= FetchType.LAZY)
private Set<CohortGenerationInfo> generationInfoList;
...
+ Getters and Setters
And the target/dependent object:
Entity(name = "CohortGenerationInfo")
#Table(name="cohort_generation_info")
public class CohortGenerationInfo implements Serializable {
private static final long serialVersionUID = 1L;
public CohortGenerationInfo()
{
}
public CohortGenerationInfo(Integer cohortDefinitionId, Integer sourceId)
{
this.id = new CohortGenerationInfoPK(cohortDefinitionId, sourceId);
}
#EmbeddedId
private CohortGenerationInfoPK id;
#ManyToOne
#MapsId("cohortDefinitionId")
#JoinColumn(name="id")
private CohortDefinition cohortDefinition;
Here is the EmbeddedID class (The Id for the Info consists of the definition's ID + a ID of another entity that represents differetnt types of info.
#Embeddable
public class CohortGenerationInfoPK implements Serializable {
private static final long serialVersionUID = 1L;
public CohortGenerationInfoPK() {
}
public CohortGenerationInfoPK(Integer cohortDefinitionId, Integer sourceId) {
this.cohortDefinitionId = cohortDefinitionId;
this.sourceId = sourceId;
}
#Column(name = "id")
private Integer cohortDefinitionId;
#Column(name = "source_id")
private Integer sourceId;
+ getter/setter
Here is what I am doing. This should be so simple, but I just don't understand what is going on:
Source source = this.getSourceRepository().findBySourceKey(sourceKey);
CohortDefinition currentDefinition = this.cohortDefinitionRepository.findOne(id);
CohortGenerationInfo info = findBySourceId(currentDefinition.getGenerationInfoList(), source.getSourceId());
if (info == null)
{
info = new CohortGenerationInfo(currentDefinition.getId(), source.getSourceId());
currentDefinition.getGenerationInfoList().add(info);
}
info.setStatus(GenerationStatus.PENDING)
.setStartTime(Calendar.getInstance().getTime());
this.cohortDefinitionRepository.save(currentDefinition);
Here's the Hibernate select just before it blows up:
Hibernate: select cohortgene0_.id as id1_4_0_, cohortgene0_.source_id as source_i2_4_0_, cohortgene0_.execution_duration as executio3_4_0_, cohortgene0_.is_valid as is_valid4_4_0_, cohortgene0_.start_time as start_ti5_4_0_, cohortgene0_.status as status6_4_0_, cohortdefi1_.id as id1_2_1_, cohortdefi1_.created_by as created_2_2_1_, cohortdefi1_.created_date as created_3_2_1_, cohortdefi1_.description as descript4_2_1_, cohortdefi1_.expression_type as expressi5_2_1_, cohortdefi1_.modified_by as modified6_2_1_, cohortdefi1_.modified_date as modified7_2_1_, cohortdefi1_.name as name8_2_1_ from cohort_generation_info cohortgene0_ inner join cohort_definition cohortdefi1_ on cohortgene0_.id=cohortdefi1_.id where cohortgene0_.id=? and cohortgene0_.source_id=?
Here is the stack trace:
javax.persistence.EntityNotFoundException: Unable to find org.ohdsi.webapi.cohortdefinition.CohortGenerationInfo with id org.ohdsi.webapi.cohortdefinition.CohortGenerationInfoPK#3
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$JpaEntityNotFoundDelegate.handleEntityNotFound(EntityManagerFactoryBuilderImpl.java:183)
at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:219)
at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:275)
at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:151)
at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1070)
at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:989)
at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:716)
at org.hibernate.type.EntityType.resolve(EntityType.java:502)
at org.hibernate.type.EntityType.replace(EntityType.java:366)
at org.hibernate.type.CollectionType.replaceElements(CollectionType.java:549)
at org.hibernate.type.CollectionType.replace(CollectionType.java:690)
at org.hibernate.type.TypeHelper.replace(TypeHelper.java:177)
at org.hibernate.event.internal.DefaultMergeEventListener.copyValues(DefaultMergeEventListener.java:407)
at org.hibernate.event.internal.DefaultMergeEventListener.entityIsPersistent(DefaultMergeEventListener.java:219)
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:192)
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:85)
at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:876)
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:858)
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:863)
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:1196)
I stoppped here at the merge part of the stack trace. It appears to be fetchign the existing entity from the DB and mereg it. The Definition DOES EXIST, it's just a new DefinitionInfo being added.
Does anyone have any thoughts? I have some limitations on how to structure the database, but I'm hoping this isn't a database structure problem, rather just somethign annoying with JPA.
Couple of apparent issues with the code.
Since you are expecting to do transitive persistence, i.e., saving CohortDefinition should also save CohortGenerationInfo, you will need to set the cascade property on the OneToMany annotation. For example,
#OneToMany(mappedBy="cohortDefinition", fetch= FetchType.LAZY, cascade= CascadeType.ALL)
You have set a bidirectional relationship between CohortDefinition and CohortGenerationInfo. As a best practice, it is better to set the values on both sides of the relationship explicitly rather than relying on the ORM to do it. So the code should do the following (Better to put it in a helper method).
currentDefinition.getGenerationInfoList().add(info);
info.setCohortDefinition(currentDefinition);

play framework 2 ebean #manytoone Column specified twice

I'm running to some problems with ebean (using play framework 2 version 2.2.1)
I have two classes:
my graph class:
public class Graph extends Model {
#Id
#Column(name="id")
private String id;
#Column(name="type")
private String type;
#OneToMany(mappedBy="valGraph", cascade=CascadeType.ALL)
private List<Val> valItems;
and my value class (with Val.graphId foreign key Graph.id):
public class Val extends Model
#Id
#Column(name="valId")
private String valId;
#Id
#Column(name="graphId")
private String graphId;
#Column(name="Key")
private String Key;
#Column(name="Value")
private String Value;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="graphId")
private Graph valGraph;
but when trying to save a new item i get this error:
javax.persistence.PersistenceException: ERROR executing DML bindLog[] error[Column 'graphId' specified twice]
After numerous searchers around the web I found this answer here - thanks to jtal!
Just to summaries the problem:
Using Ebean i have made a #ManyToOne entity that is not implemented in the database in anyway,
even more the join field, in my case
graphId
is a valid field that has values of its own.
when trying to join the column on that field, it will always fail because it creates this sql query:
SELECT
*
FROM
Val;
select
t0.valId c0,
t0.graphId c1,
t0.Key c2,
t0.Value c3,
t0.graphId c4 <---- notice this duplicate
from
graph_val t0
in order to solve this, i tell ebean not to use the second set of properties.
my new ebean element looks like this:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="graphId", insertable = false, updatable = false)
private Graph valGraph;
and it works! =)

Persist an entity that have associated a list of entities that use #idclass

I have an Evaluation entity that has an associated list of EvaluationEvaluator. I need to explicitly create that entity because it required an extra column "STATUS". Before I continue evaluation. I do: evaluation.setEvaluationEvaluator(listEvaluator) where listEvaluator is a list of EvaluationEvaluator type. Then persist(evaluation). When I run this, it does not throw any kind of exception. But in the database, it inserts in the Evaluation table, and not inserted into the EvaluationEvaluator table.
Below my Evaluation entity.
#Entity
public class Evaluation implements Serializable{
#Id
#GeneratedValue
private Long id;
//MORE FIELDS
#OneToMany(mappedBy="evaluation")
private List<EvaluationEvaluator> evaluators;
//CONSTRUCTORS
//GETTER AND SETTERS
}
This is my EvalutionEvaluator Entity:
#Entity
#Table(name= "EVALUATION_EVALUATOR")
#IdClass(EvaluationEvaluatorId.class)
public class EvaluationEvaluator implements Serializable{
#Id
#Column(name="EMPLOYEE_ID", insertable=false , updatable=false)
private Long EmployeeID;
#Id
#Column(name="EVALUATION_ID", insertable=false, updatable=false)
private Long EvaluationID;
#ManyToOne
#JoinColumn(name"EMPLOYEE_ID")
private Employee employee;
#ManyToOne
#JoinColumn(name"EVALUATION_ID")
private Evaluation evaluation;
#NotNull
private String status;
//CONSTRUCTORS
//GETTER AND SETTERS
}
This is my EvaluationEvaluatorId class
public class EvaluationEvaluatorId implements Serializable{
private Long employeeID;
private Long evaluationID;
//CONSTRUCTOR
//GETTER AND SETTERS
}
And finally, this is my EvaluationBean class
#Stateful
#Named
#LocalBean
#ConversationScoped
public class EvaluationBean {
#PersistentContext(type= PersistenceContextType.EXTENDED)
private EntityManager em;
#Inject
Conversation conversation;
private Evaluation evaluation;
//IN MY WEBPAGE I IMPLEMENT PRIMEFACES PICKLIST AND IT REQUIRE DUALIST TO HANDLE
private DualListModel<Employe> evaluators;
private EvaluationEvaluator evaluationEvaluator;
private List<EvaluationEvaluator> listEvaluators;
#Inject
private EmployeeList employeeList;
//GETTER AND SETTERS
public String begin(){
if (conversation.isTransient()){
converstaion.begin();
}
evaluationEvaluator = new EvaluationEvaluator();
listEvaluators = new ArrayList<EvaluationEvaluator>();
evaluation = new Evaluation();
List<Employee> source = employeeList.findAll();
target = new ArrayList<Employee>();
evaluators = new DualListModel<Employee>(source, target);
return "/evalution/evaluationAsig.xhtml"
}
public String save(){
Iterator<Employee> iterator = evaluators.getTarget().iterator();
while (iterator.hasNext()){
EvaluationEvaluator ev = new EvaluationEvaluator();
ev.setEmployee(iterator.next());
listEvaluators.add(ev);
}
evalution.setEvaluationEvaluators(listEvaluators);
if(evaluation.getId()==null){
em.persist(evalution);
} else{
em.merge(evalution);
}
if(!conversation.isTransient()){
convesation.end();
}
return "/evalution/evaluationsAsig.xhtml"
}
}
When I debug my application,apparently everything is correct, but I mentioned above, doesn't persist in EvaluationEvaluator table.
Your #OneToMany association is missing cascading configuration.
Add cascade = CascadeType.ALL or cascade = {CascadeType.PERSIST, CascadeType.MERGE} to the #OneToMany annotation. JPA assumes no cascading by default so you would need to persist each EvaluationEvaluator by yourself explicitely otherwise.
UPDATE
There is another thing wrong with the code - the Ids of EvaluationEvaluators are never assigned. You have a complex key made of two Long columns. Both are marked not insertable nor updatable which tells to JPA that the id is going to be somehow generated on database level and it should not care about it. There is however no sequence configured explicitely in your entity (although it is not necessarily required) and also from your comment:
I did what you recommended but it throws the following exception. "A different object with same identifier was already associated with the session"
I assume that this is not the case and both id column values default to null or zero and are same for all EvaluationEvaluators you are trying to persist. If you'd like the database to generate the id for you automatically use #GeneratedValue - Configure JPA to let PostgreSQL generate the primary key value - here you can find explanation how to do this (the database part is database dependent, this is for PostgreSQL). The most common use case however, is to configure the sequence but let hibernate pick the next value, instructions here - Hibernate sequence on oracle, #GeneratedValue(strategy = GenerationType.AUTO)

Categories

Resources