Hibernate Books

All times are UTC - 5 hours [ DST ]



Post new topic Reply to topic  [ 6 posts ] 
Author Message
 Post subject: JPA Entity with a many-to-many association with extra column
PostPosted: Tue Aug 15, 2017 10:04 pm 
Beginner
Beginner

Joined: Sun May 07, 2017 11:24 pm
Posts: 29
Hello,

I am trying to setup a Tertiary Entity between Episode and Person to record EpisodeRole.

My Episode entity - which I think is setup correct is:

Code:
@Entity
public class Episode {

    public Episode() {}

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    ....
    @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @JoinTable(name = "episode_person", joinColumns = @JoinColumn(name = "episode_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "person_id", referencedColumnName = "id"))
    private List<EpisodePerson> persons;
    @OneToOne(cascade=CascadeType.ALL)
    private Address address;

    // getters setters

        public List<EpisodePerson> getPersons() {
        return persons;
    }


Person entity is

Code:
@Entity
public class Person {

    public Person() {
    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    ....
    @JsonIgnore
    @ManyToMany(mappedBy = "persons", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    private List<EpisodePerson> episodes;

    // getters and setters

    public List<EpisodePerson> getEpisodes() {
        return episodes;
    }


And finally the EpisodePerson class:

Code:
@Entity
public class EpisodePerson {

    @EmbeddedId
    private EpisodePersonId id;

    @ManyToOne
    @JoinColumn(name = "fk_episode", insertable = false, updatable = false)
    private Episode episode;

    @ManyToOne
    @JoinColumn(name = "fk_person", insertable = false, updatable = false)
    private Person person;

    public enum EpisodeRole {
        Goodie,
        Baddie,
        Helper
    }

    @Column
    @Enumerated(EnumType.STRING)
    private EpisodeRole episodeRole;

    public EpisodePerson(Episode e, Person p, EpisodeRole r) {
        // create primary key
        this.id = new EpisodePersonId(e.getId(), p.getId());

        // initialize attributes
        this.episode = e;
        this.person = p;
        this.episodeRole = r;

        // update relationships to assure referential integrity
        p.getEpisodes().add(this);
        e.getPersons().add(this);
    }
}


There is also a EpisodePersonId class.

Now the issue I am having is that since I started to implement this tertiary entity a number of methods in my EpisodeService have broken due to incompatible types being expected. For example:

EpisodeServiceImpl.java

Code:
    @Override
    @Transactional
    public Episode saveEpisode(Episode episode) {

        // CHECK FOR EXISTING PEOPLE ON NID
        List mergedPeople = personService.mergeDetachedWithAttached( episode.getPersons() ); << --- not the input parameter required type
        episode.setPersons( mergedPeople );

    ...
[code]

mergeDetachedWithAttached uses episode.getPersons() - but the getPersons method returns

[code]
        public List<EpisodePerson> getPersons() {
        return persons;
    }


returns a List of EpisodePersons. So somewhere on Episode I need to either implement a getter/setter that returns a list of people or change the the saveEpisode method to use a list of EpisodePersons?

Hope this makes sense. I am relatively new so a detailed answer would be most helpful.

Thanks in advance.

Al


Top
 Profile  
 
 Post subject: Re: Tertiary Entity
PostPosted: Wed Aug 16, 2017 2:45 am 
Hibernate Team
Hibernate Team

Joined: Thu Sep 11, 2014 2:50 am
Posts: 1515
1. The @ManyToMany is only needed if you don't want to map the join table, which you did map anyway.

2. Since you already mapped the join table, it's more efficient to let the @ManyToOne side own the association, as illustrated by this article.

3. The @ManyToMany annotation should be replaced by a mappedBy @OneToMany once you make the @ManyToOne side own the association.

Now, back to your question. The Service methods can stay as-is, and you just need to change the implementation to accommodate the new structure. I'd pay attention to how associations are navigated and fetched because it's more efficient if you JOIN FETCH them with a query instead.

_________________
If you liked my answer, you are going to love my High-Performance Java Persistence book and my blog as well.


Top
 Profile  
 
 Post subject: Re: Tertiary Entity
PostPosted: Wed Aug 16, 2017 3:22 am 
Beginner
Beginner

Joined: Sun May 07, 2017 11:24 pm
Posts: 29
vlad wrote:
1. The @ManyToMany is only needed if you don't want to map the join table, which you did map anyway.

2. Since you already mapped the join table, it's more efficient to let the @ManyToOne side own the association, as illustrated by this article.

3. The @ManyToMany annotation should be replaced by a mappedBy @OneToMany once you make the @ManyToOne side own the association.

Now, back to your question. The Service methods can stay as-is, and you just need to change the implementation to accommodate the new structure. I'd pay attention to how associations are navigated and fetched because it's more efficient if you JOIN FETCH them with a query instead.


Change the implementation to what?

Could you be a little more specific as in new to this. Thank you.


Top
 Profile  
 
 Post subject: Re: JPA Entity with a many-to-many association with extra column
PostPosted: Wed Aug 16, 2017 3:44 am 
Hibernate Team
Hibernate Team

Joined: Thu Sep 11, 2014 2:50 am
Posts: 1515
Quote:
Change the implementation to what?


As you said, you can start with a simple getter that returns the list of Person entities:

Quote:
So somewhere on Episode I need to either implement a getter/setter that returns a list of people or change the the saveEpisode method to use a list of EpisodePersons?


Like every refactoring, it's best if you do it in small steps. This would be the first step. Afterward, you need to check whether the executed SQL queries are optimal as navigating many LAZY associations can lead to many secondary queries.

_________________
If you liked my answer, you are going to love my High-Performance Java Persistence book and my blog as well.


Top
 Profile  
 
 Post subject: Re: JPA Entity with a many-to-many association with extra column
PostPosted: Thu Aug 17, 2017 2:46 pm 
Beginner
Beginner

Joined: Sun May 07, 2017 11:24 pm
Posts: 29
vlad wrote:
Quote:
Change the implementation to what?


As you said, you can start with a simple getter that returns the list of Person entities:

Quote:
So somewhere on Episode I need to either implement a getter/setter that returns a list of people or change the the saveEpisode method to use a list of EpisodePersons?


Like every refactoring, it's best if you do it in small steps. This would be the first step. Afterward, you need to check whether the executed SQL queries are optimal as navigating many LAZY associations can lead to many secondary queries.


A check that I am heading in the right direction:

I have changed my person class to:

Code:
@Entity
public class Person {

    public Person() {
    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    // other stuff
    @JsonIgnore
    @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    private List<EpisodePerson> episodes;

    // getters setters

    public void setEpisodes(List<EpisodePerson> episodes) {
        this.episodes = episodes;
    }

    public List<EpisodePerson> getEpisodes() {
        return episodes;
    }
}


and Episode to:
Code:
package com.example.dao;

//import org.hibernate.search.annotations.Field;
//import org.hibernate.search.annotations.Indexed;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.format.annotation.DateTimeFormat;

import javax.persistence.*;
import javax.validation.constraints.Pattern;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Entity
//@Indexed
public class Episode {

    public Episode() {}

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
     
    // other stuff

    @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    private List<EpisodePerson> persons;
   
    // getters setters

    public List<EpisodePerson> getPersons() {
        return persons;
    }

    public void setPersons(List<EpisodePerson> persons) {
        this.persons = persons;
    }
}


The above changes were to make Private List<Person> person to Private List<EpisodePerson> persons; and change from @ManyToMany to @OneToMany

Correct?

Now the example at https://www.thoughts-on-java.org/many-r ... mment-1654 is not complete so I have been looking at https://www.mkyong.com/hibernate/hibern ... nnotation/ which shows get and set methods for what is my person and episode on both EpisodePerson and EpisodePersonId. Why are the get set methods on both EpisodePerson and EpisodePersonId?

Any how I have implemented the getPerson method and this is what I did :

EpisodePerson
Code:
@Entity
public class EpisodePerson {

    @EmbeddedId
    private EpisodePersonId id;

    @ManyToOne
    @JoinColumn(name = "fk_episode", insertable = false, updatable = false)
    private Episode episode;

    @ManyToOne
    @JoinColumn(name = "fk_person", insertable = false, updatable = false)
    private Person person;

    public enum EpisodeRole {
         Goodie,
         Baddie
    }

    @Column
    @Enumerated(EnumType.STRING)
    private EpisodeRole episodeRole;

    @EmbeddedId
    public EpisodePersonId getPk() {                 <<<<<<----
        return id;
    }

    @Transient
    public Episode getEpisode() {                   <<<<<<<------
        return getPk().getEpisode();
    }

    public EpisodePerson(Episode e, Person p, EpisodeRole r) {
        // create primary key
        this.id = new EpisodePersonId(e.getId(), p.getId());

        // initialize attributes
        this.episode = e;
        this.person = p;
        this.episodeRole = r;

        // update relationships to assure referential integrity
        p.getEpisodes().add(this);
        e.getPersons().add(this);
    }
}


and on EPisodePersonId
Code:
    @Embeddable
    public class EpisodePersonId implements Serializable {

        @Column(name = "fk_episode")
        protected Long episodeId;

        @Column(name = "fk_person")
        protected Long personId;

        public EpisodePersonId() { }

        private Episode episode;           <<<<<<<---
        private Person person;              <<<<<<<---

        @ManyToOne
        public Episode getEpisode() {      <<<<<<----
            return episode;
        }

        public EpisodePersonId(Long episodeId, Long personId) {
            this.episodeId = episodeId;
            this.personId = personId;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result
                    + ((episodeId == null) ? 0 : episodeId.hashCode());
            result = prime * result
                    + ((personId == null) ? 0 : personId.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;

            EpisodePersonId other = (EpisodePersonId) obj;

            if (episodeId == null) {
                if (other.episodeId != null)
                    return false;
            } else if (!episodeId.equals(other.episodeId))
                return false;

            if (personId == null) {
                if (other.personId != null)
                    return false;
            } else if (!personId.equals(other.personId))
                return false;
            return true;
        }
    }


Does this look correct?

My IDE at least is not longer complaining about incompatiable types.

Thanks


Top
 Profile  
 
 Post subject: Re: JPA Entity with a many-to-many association with extra column
PostPosted: Fri Aug 18, 2017 2:19 am 
Hibernate Team
Hibernate Team

Joined: Thu Sep 11, 2014 2:50 am
Posts: 1515
It looks ok to me.

_________________
If you liked my answer, you are going to love my High-Performance Java Persistence book and my blog as well.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 6 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.