How do I map an #OneToMany and #ManyToOne relationship properly so that I can save and update the #OneToMany side with or without the #ManyToOne side - spring

I have an app with Angular front end and Spring backend. The two classes in question here are (backend):
#Setter
#Getter
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "tournament_games")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class TournamentGame {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "code", foreignKey = #ForeignKey(name = "code_fk"))
private TournamentCode code;
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "type", foreignKey = #ForeignKey(name = "game_type_fk"))
private GameType type;
#Column(name = "home_score")
private int home_score;
#Column(name = "away_score")
private int away_score;
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "result_type", foreignKey = #ForeignKey(name = "result_type_fk"))
private ResultType result_type;
#Column(name = "status")
private boolean status;
#Column(name = "round")
private int round;
#Column(name = "locked")
private boolean locked;
#OneToMany(mappedBy = "game", fetch = FetchType.EAGER)
private List<TournamentGamesPlayers> players = new ArrayList<>();
}
and
#Setter
#Getter
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "tournament_games_players")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "game")
public class TournamentGamesPlayers implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#ManyToOne
#JoinColumn(name = "tournament_game_id")
private TournamentGame game;
#Id
#ManyToOne
#JoinColumn(name = "playerid")
private Player player;
#Column(name = "home")
private boolean home;
}
I need help figuring out how to persist the List<TournamentGamesPlayers> when I save and/or update a TournamentGame object. I generate 45 games. The first 30 games have known players, and so I set them before saving. The last 15 do not have entries for the TournamentGamesPlayers join table, because I need to add them later.
I am able to get some results with CascadeType.ALL on the #OneToMany side when I initially generate the games, but it fails when I try to update a game with a seemingly infinite recursion/stack overflow.
If I omit any cascade type, the games side get generated, but the join table #ManyToOne side does not get entered.

I ended up just putting the players back into the game table to make my life easier.

try putting CascadeType.MERGE, CascadeType.ALL "delete parent and orphans" (JPA CascadeType.ALL does not delete orphans).
Also, defining the relationship as EAGER and not ignoring the JSON property can have problems. I would add #JsonIgnore to one of the parts of the relationship

Related

#ManyToMany SpringBoot JSON 415 error can`t post to table or can`t get list(n>=1) because of infinite loop caused by relationship

I`ve been trying to create a #ManyToMany relationship between two entities (team&contest) but when i try to post to the contest controller api i get a error 415 saying that
Failed to evaluate Jackson deserialization for type [[simple type, class com.project.Contest.Contest]]: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot handle managed/back reference 'defaultReference': back reference type (`java.util.List<com.project.Contest.Contest>`) not compatible with managed type (com.project.Contest.Contest)
team :
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "team")
public class Team {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#JsonBackReference
//#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class , property = "id")
#ManyToMany(mappedBy = "teams", cascade = CascadeType.PERSIST)
private List<Contest> contests;
private String name;
private int wins, losses;
}
contest :
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "contest")
public class Contest {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
#ManyToMany(cascade = CascadeType.PERSIST)
#JsonManagedReference
//#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class , property = "id")
#JoinTable(
name = "team_contest",
inverseJoinColumns = #JoinColumn(name = "team_id"),
joinColumns = #JoinColumn( name = "contest_id")
)
private List<Team> teams;
#ManyToMany(cascade = CascadeType.PERSIST)
#JsonManagedReference
//#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class , property = "id")
#JoinTable(
name = "contest_user",
joinColumns = #JoinColumn(name = "contest_id"),
inverseJoinColumns = #JoinColumn( name = "user_id")
)
private List<User> users;
}
i found out i can use #JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class , property = "id") instead of #JsonBackReference & #JsonManagedReference which helped me as it let me post to the database but then i refaced the problem that i can`t retrieve contest.teams[1] as because both objects have references to one another it creates some kind of an infinite loop as to get to the reference of the second object(contest.teams[1]) it needs to show the reference the contest.teams[0] has to the contest and soo forth. please help <3
This is the most famous bi-directional issue. To break up the loop while serialization, you can choose:
#JsonIgnore
#JsonIdentityInfo
JPA Projections
#EntityGraph
Or simply make the relationship uni-directional
thanks meridbt i was already using #JsonIdentityInfo but in the wrong place and i read a bit online and fixed my issue by doing this :
team :
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "team")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class , property = "id")
public class Team {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToMany(mappedBy = "teams", cascade = CascadeType.PERSIST)
private List<Contest> contests;
private String name;
private int wins, losses;
}
contest :
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "contest")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Contest {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
#ManyToMany(cascade = CascadeType.PERSIST)
#JoinTable(
name = "team_contest",
inverseJoinColumns = #JoinColumn(name = "team_id"),
joinColumns = #JoinColumn(name = "contest_id")
)
private List<Team> teams;
#ManyToMany(cascade = CascadeType.PERSIST)
#JoinTable(
name = "contest_user",
joinColumns = #JoinColumn(name = "contest_id"),
inverseJoinColumns = #JoinColumn(name = "user_id")
)
private List<User> users;
}

#ManyToOne and #OneToMany ends up in unlimited loop when retrieved through profileRepository.getByProfileId(id);

Class Jobs has Many to One relationship with Profile.
When I retrieve through profileRepository.getByProfileId(id) the response returns recursive data.
Also if you notice Profile has Login object. I don't want to return that as well.
#Entity
#Table(name = "tbl_profile")
#Builder(toBuilder = true)
#NoArgsConstructor
#AllArgsConstructor
#Getter
public class Profile {
#Id
#Column(name = "profile_id")
#GeneratedValue(strategy = GenerationType.AUTO)
long profileId;
#NonNull
#Column(name = "name")
String name;
#Column(name = "description", nullable = false)
String description;
#OneToOne
#JoinColumn(name = "login_id",
referencedColumnName = "login_id")
Login login;
#OneToMany(
mappedBy = "profile"
)
List<Jobs> job;
Class Jobs
#Entity
#Table(name = "tbl_job")
#Builder(toBuilder = true)
#NoArgsConstructor
#AllArgsConstructor
#Getter
public class Jobs {
#Id
#Column(name = "job_id")
#GeneratedValue(strategy = GenerationType.AUTO)
long jobId;
#NonNull
#Column(name = "job_role", nullable = false)
String joRole;
#Column(name = "description", nullable = false)
String description;
#ManyToOne
#JoinColumn(name = "profile_id",
referencedColumnName = "profile_id")
Profile profile;
}
Use #JsonIgnore to the property to ignore the output on JSON. Also according to your business logic, recheck if you need bidirectional association. You could maybe add only unidirectional association.

I seem to need both #OneToOne and #OneToMany relationship

I'm writing an app to operate a collection of hardware that is supposed to be setup and shutdown repeatedly in different locations.
I want to track those installations so I've created separate entities for a physical object itself and the installation.
Station object needs to keep track of up to one (null while not installed) active installation information (so #OneToOne) but also all the previous installations (so #OneToMany)
#Entity
#Table(name = "station")
class Station{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(mappedBy = "stationInstallation", cascade = CascadeType.DETACH,
CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH)
#JoinColumn(name = "station_installation_id", nullable = true)
private StationInstallation activeStationInstallation;
...
#OneToMany(fetch = FetchType.Lazy, mappedBy = "station_id", cascade =
{CascadeType.DETACH, CascadeType.PERSIST,
CascadeType.MERGE, CascadeType.REFRESH })
private List<stationInstallation> stationInstallations;
...
}
The other entity will have the station id, location and date of the setup as well as the shutdownDateTime being null.
#Entity
#Table(name = "stationInstallation")
class StationInstallation{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(mappedBy ="stationInstallation", cascade - CascadeType.ALL)
private Station station;
#Column(name = "location")
private String location;
#Column(name = "setupDateTime")
private LocalDateTime setupDateTime;
#Column(name = "shutdownDateTime")
private LocalDateTime shutdownDateTime
}
Finally the shutdown method is supposed to unattach the entity from the Station entity by setting activeStationInstallation to null and setting shutdownDateTime to LocalDateTime.NOW.
...
stationInstallation.getStation().setActiveStationInstallation(null);
stationInstallation.setShotdownDateTime(LocalDateTime.NOW);
...
But that will obviously result in a growing number of StationInstallation "finished" objects, which would seem to require #ManyToOne relation with the Station all the while I want to keep #OneToOne relation with the activeStationInstallation.
What do?
I figured I could just make another entity calling it FinishedStationInstallation, remove shutdownDateTime from StationInstallation, make both immutable and instead of adding shutdownDateTime with a setter add it in the constructor while deleting the active version. And immutablity is an asset, ut at the same time I'd have to add several new tables to the db and keep track of and query two entities instead of one.
EDIT: I guess I could get rid of List stationInstallations from the 1st entity, since I don't really need to keep the track of it beyond db queries, but I added it for the clarity of the question.
I would model it this way:
#Entity
#Table(name = "station")
class Station{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(cascade = CascadeType.DETACH,
CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH)
#JoinColumn(name = "station_installation_id", nullable = true)
private StationInstallation activeStationInstallation;
...
#OneToMany(fetch = FetchType.Lazy, mappedBy = "station", cascade =
{CascadeType.DETACH, CascadeType.PERSIST,
CascadeType.MERGE, CascadeType.REFRESH })
private List<stationInstallation> stationInstallations;
...
}
#Entity
#Table(name = "stationInstallation")
class StationInstallation{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne(mappedBy ="stationInstallation", cascade = CascadeType.ALL)
private Station station;
#Column(name = "location")
private String location;
#Column(name = "setupDateTime")
private LocalDateTime setupDateTime;
#Column(name = "shutdownDateTime")
private LocalDateTime shutdownDateTime
}
The one-to-one relation from Station to installation has a FK => so no mapped by
The one-to-many relation from Station to installation is mapped by the station association on installation, which obviously has to be a many-to-one association, since the other side is a one-to-many

EntityNotFoundException when using #Cacheable

On my application I have multiple entities like:
#Entity
#Data
#Builder
#ToString(of = {"id", "code", "nameContentType", "observations"})
#EqualsAndHashCode(exclude = "room")
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "desk")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Desk implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#Column(name = "code")
private String code;
#Lob
#Column(name = "name")
private byte[] name;
#Column(name = "name_content_type")
private String nameContentType;
#Column(name = "observations")
private String observations;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(unique = true)
private Coordinates coordinates;
#OneToMany(mappedBy = "desk")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<Reservation> reservations = new HashSet<>();
#ManyToOne
#JoinColumn(name = "room_id", insertable = false, updatable = false)
#JsonIgnoreProperties(value = "desks", allowSetters = true)
private Room room;
}
All relationships represented with a collection are cached with #Cache(usage = CacheConcurrencyStrategy.READ_WRITE).
When I delete some records of related Entities I get:
javax.persistence.EntityNotFoundException: Unable to find com.**.domain.Reservation with id ***
I don't know if I have to make any extra adjustments to my cache settings or how to debug the problem
Are you sure you updated Hibernate to the latest version 5.4.32.Final? If so, and you still have the problem, please create an issue in the issue tracker(https://hibernate.atlassian.net) with a test case(https://github.com/hibernate/hibernate-test-case-templates/blob/master/orm/hibernate-orm-5/src/test/java/org/hibernate/bugs/JPAUnitTestCase.java) that reproduces the issue.

How to link two tables by third?

I have three tables:
1) book: id (primary), name
2) shop: code (unique, not primary), name
3) book_shop: book_id, shop_id (code), price
I want to get shops in book like
book.getShop();
How to link this entities?
I tried:
#Data
#NoArgsConstructor
#Entity
#Table(name = "book", schema = "example")
#EntityListeners(AuditingEntityListener.class)
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
#OneToMany(mappedBy = "book", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<BookShop> bookShop;
}
.
#Data
#NoArgsConstructor
#Entity
#Table(name = "shop", schema = "example")
#EntityListeners(AuditingEntityListener.class)
public class Shop {
#Id
private int code;
private String name;
#OneToMany(mappedBy = "shop", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<BookShop> bookShop;
}
.
#Data
#NoArgsConstructor
#Entity
#Table(name = "book_shop", schema = "example")
public class BookShop implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Id
#ManyToOne
#JoinColumn(name = "book_id")
private Book book;
#Id
#ManyToOne
#JoinColumn(name = "shop_id")
private Shop shop;
#Column(name = "price")
private int fromDate;
}
This code return empty set: Book book = bookRepostiory.getById(1).get().getBookShop()
Try the many to many mapping implement like as below remove your book_shop table,
add this code to shop entity,
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
#JoinTable(name = "book_shop",
joinColumns = {#JoinColumn(name = "book_id", nullable = false)},
inverseJoinColumns = {#JoinColumn(name = "shop_id", nullable = false)})
private Set<Book> bookList = null;
add this code to book entity,
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL,
mappedBy ="bookList")
private Set<Shop> shopList=null;
if any issue inform!!
I would suggest, first - initialize the set in the entity
private Set<BookShop> bookShop = new HashSet<>();
Second, add fetch = FetchType.EAGER to your association, for e.g.
#OneToMany(fetch = FetchType.EAGER, mappedBy = "book", cascade = CascadeType.ALL)

Resources