Interestingly, I can't find any solution for a seemingly common scenario! So I'm asking here to learn from experienced professionals in Spring Data JPA. I'll consider using Lombok to make the sample codes more concise.
Consider a simple IMDB example web application. I've defined two simple entities as below:
#Data
#Entity
public class Movie {
#Id
#GeneratedValue
private long id;
private String title;
private int year;
private int rating;
}
#Data
#Entity
public class Actor {
#Id
#GeneratedValue
private long id;
private String firstName;
private String lastName;
private Date birthday;
private String gender;
}
Now we need a join-table to link these two entities; but this is not just a simple join-table. Other than the actor and movie columns, this table has some additional attributes. We didn't want to waste storage by adding an ID column here, instead we used a composite-key consisting of actor and movie:
#Data
#Embeddable
public class MovieActorId implements Serializable {
private Actor actor;
private Movie movie;
}
#Data
#Entity
public class MovieActor {
#EmbeddedId
private MovieActorId id;
private int salary;
private String characterName;
}
There are two Many-to-One relations here: MovieActor >-- Actor and MovieActor >-- Movie.
Now my main question is: "Assuming the above design, how should I define the #ManyToOne relationships in this design?"
NOTE: I believe if we add an additional ID column to the MovieActor join-table instead of the composite/embedded MovieActorId, the JPA code will become fairly straight-forward. But suppose we have some sort of limitation, and we need to stick to this design as much as possible.
You need to use #MapsId which provides the mapping for an EmbeddedId primary key in #ManyToOne relation
#Data
#Embeddable
public class MovieActorId implements Serializable {
private long actorId;
private long movieId;
// constructor, setter, etc
}
#Data
#Entity
public class MovieActor {
#EmbeddedId
private MovieActorId id;
#ManyToOne(cascade = CascadeType.ALL)
#MapsId("actorId")
private Actor actor;
#ManyToOne(cascade = CascadeType.ALL)
#MapsId("movieId")
private Movie movie;
...
}
Related
Given two entities Employee and EmployeeAddress, I am trying to implement the DTO pattern - mainly because my IDE shows a warning when using an entity as parameter in my REST controller.
In this context, I have a question regarding how to deal with the OneToOne relationship between these two entities:
The parent entity:
#Entity
#Table
public class Employee{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "employee_address_id")
private EmployeeAddress employeeAddress;
}
The child entity:
#Entity
#Table
public class EmployeeAddress{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String street;
private String postalCode;
#OneToOne(mappedBy="employeeAddress")
private Employee employee;
}
My first idea was to introduce the following two DTOs:
#Getter
#Setter
public class EmployeeDTO {
private Long id;
private String firstName;
private String lastName;
private EmployeeAddressDTO employeeAddress;
}
#Getter
#Setter
public class EmployeeAddressDTO {
private Long id;
private String street;
private String postalCode;
}
This doesn't seem to work, however: I have to replace the EmployeeAddressDTO inside my EmployeeDTO with the actual entity EmployeeAddress in order for this to work. This, however, seems a bit contradicting to me - why would I create an EmployeeDTO only for it to contain an entity?
So, I wonder, how do I deal with this OneToOne relationship in my DTO?
Do I have to create an EmployeeDTO as:
#Getter
#Setter
public class EmployeeDTO {
private Long id;
private String firstName;
private String lastName;
private String street;
private String postalCode;
}
Would this be the right approach?
why would I create an EmployeeDTO only for it to contain an entity?, that is the whole question about DTOs.
In small or toy apps there may even no point for DTOs anyway. When your app is growing, things are different. It is good practice to separate the persistence (entities) from the presentation (or API, or interface to the outside world).
Entities defining the app data model (the data that needs your app to do the work), DTOs are what you want to export (in a controlled way) to the world.
Of course, at the beginning it is mostly the same data.
I am developing a SpringBoot API that has 2 domain JPA entities. One of these entities is 'Player' which stores information about the player of the game and includes their 'id' which is autogenerated and autoincrementing.
The 'Game' entity stores information about previous games that have been played, and has an autogenerated and autoincremeneting id also 'gameId'.
However I also want to store all of the 'Players' 'id' who participated in that game within the 'Game' entity.
Therefore, when a Game object is created, it will require the 'id' of 2 seperate Player entities.
Would a OneToMany relationship work in this case? Where the Player is annotated with #OneToMany above the 'id' field, and the Game is annotated with #ManyToOne above the 'playerId' field?
Kind of like this:
public class Game {
#Id
#GeneratedValue
private Long gameId;
#ManyToOne()
private Long playerId;
//Should this be multiple different fields like below?
#ManyToOne()
private Long player1Id;
#ManyToOne()
private Long player2Id;
#ManyToOne()
private Long player3Id;
#ManyToOne()
private Long player4Id;
}
public class Player {
#Id
#GeneratedValue
#OneToMany()
private Long id;
private String firstName;
private String lastName;
private String email;
}
Does this answer your question?
This make you able to get id of player who joined.
#Entity
public class Game {
#Id
#GeneratedValue
private Long gameId;
#OneToMany(cascade = CascadeType.ALL)
private List<Player> players = new ArrayList<>();
public Game(Long gameId, List<Player> players){
...
}
}
#Entity
public class Player {
#Id
#GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
}
But I also recommend you reading a tutorial on this topic.
This could be easy way to solve, but unidirectional one-to-many mapping might not be a best practice. It should be used carefully.
https://thorben-janssen.com/best-practices-many-one-one-many-associations-mappings/
For that reason I recommend you to use many-to-one relation instead of one-to-many like bellow.
#Entity
public class Game {
#Id
#GeneratedValue
private Long gameId;
}
#Entity
public class Player {
#Id
#GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
#ManyToOne
private Game joinedGame;
}
Or if player can be joined in multiple games, consider middle-entity to loosen many-to-many relation, like Game - JoinPlayers - Player.
I have a situation with repeatable class fields which I want to mark as #embeddable, however the question is - does JPA allow re-utilizing a class multiple times as embeddable in other different classes?
E.g. my embeddable class looks as follows:
#Embeddable
#Data
public class Audit{
private String name;
private Audit auditor;
private LocalDateTime creationDate;
}
Is it possible to embed the Audit into multiple different classes as for ex.:
#Entity
#Table(name = "BANK")
public class Bank{
#Id
private Long id;
#Column(name = "BANK_NAME")
private String bankName;
#Embedded
private Audit audit;
}
AND
#Entity
#Table(name = "CORPORATION")
public class Corporation{
#Id
private Long id;
#Column(name = "CORPORATION_NAME")
private String corporationName;
#Embedded
private Audit audit;
}
Historically Hibernate called these components. JPA calls them embeddables. Either way, the concept is the same: a composition of values.
Most often, embeddable types are used to group multiple basic type mappings and reuse them across several entities.
Java Code Example:
#Data
#Entity(name = "Book")
public class Book {
#Id
#GeneratedValue
private Long id;
private String title;
private String author;
private Publisher publisher;
}
#Data
#Embeddable
public static class Publisher {
#Column(name = "publisher_name")
private String name;
#Column(name = "publisher_country")
private String country;
}
And this is SQL to show how your table should look like:
create table Book (
id bigint not null,
author varchar(255),
publisher_country varchar(255),
publisher_name varchar(255),
title varchar(255),
primary key (id)
)
More details can be found in the documentation :)
First of all hello everybody, it's my first post here.
I'm very new at spring and have bit trouble to grasp mapping object to database. To be more specific, there is an article:
OneToMany
In given example there are 2 clases:
#Entity
public class Employee {
#Id
#Column(name="EMP_ID")
private long id;
...
#OneToMany(mappedBy="owner")
private List<Phone> phones;
...
}
#Entity
public class Phone {
#Id
private long id;
...
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="OWNER_ID")
private Employee owner;
...
}
Can I place some annotation to make the table Employee have a column with list of phones?
I have been scratching my head over this for weeks now and I have no idea how to work my way around it.
I have two tables:
Author (author_code, author_number, author_name) author_code and author_number are primary keys
Title (author_code, title_code, sequence, title_desc) title_code and sequence are primary keys
Code:
#Entity
#IdClass(AuthorPK.class)
#Table(name="Author")
public class Author implements Serializable {
#Id
#Column(name="author_code")
private long authorCode;
#Id
#Column(name="author_number")
private String authorNumber;
#Column(name="author_name")
private String authorName;
//bi-directional many-to-one association to Title
#OneToMany(mappedBy="author")
private List<Title> titles;
//getter setters
}
public class AuthorPK implements Serializable {
private long authorCode;
private String authorNumber;
//getter equals() hashCode()
}
#Entity
#IdClass(TitlePK.class)
#Table(name="Title")
public class Title implements Serializable {
#Id
#Column(name="title_code")
private long titleCode;
#Id
#Column(name="sequence")
private long sequence;
#Column(name="title_desc")
private String titleDesc;
//bi-directional many-to-one association to Author
#ManyToOne
#JoinColumns({
#JoinColumn(name="author_code", referencedColumnName="author_code"),
#JoinColumn(name="author_number", referencedColumnName="author_number")
})
private Author author;
//getters setters
}
public class TitlePK implements Serializable {
private long titleCode;
private long sequence;
//getter equals() hashcode()
}
I need to link the two entities via author_code but JPA requires me to include both IDs in the #JoinColumn... it's throwing errors on my application since the source table doesn't have the other column. Is there another way for me to join these entities?