How can I share a column between two FKs to the same reference table?
I have four entities: Player,Team, TeamPlayer and PlayerScore.
Now here is the use case:
Every batsman in cricket (sorry for a non-global example) playing for a specific team will be scoring when he has a partner-batsman called the runner. Now, the PlayerScore entity needs to capture this information.
So, I must ensure that both the batsman and his partner are playing for the same team. I can use this table to understand which pairs of batsman have been the performing the best. In exact terms, I need two references from PlayerScore Entity to the TeamPlayer entity. Both of them share exactly one column, team. How can I achieve this?
Here are the four classes:
#Entity
#Table(name="team")
public class Team {
#Id
private int id;
#Column(name="name",length=50)
private String name;
}
#Entity
#Table(name="player")
public class Player {
#Id
private int id;
#Column(name="name",length=50)
private String name;
}
#Entity
#Table(name="team_player")
public class TeamPlayer {
#EmbeddedId
private TeamPlayerPK id;
#ManyToOne(targetEntity=Player.class)
#JoinColumn(name="player")
private Player player;
#ManyToOne(targetEntity=Team.class)
#JoinColumn(name="team")
private Team team;
#Column(name="name",length=50)
private String name;
#Embeddable
public static class TeamPlayerPK implements Serializable
{
private static final long serialVersionUID = 1L;
private int team;
private int player;
}
}
#Entity
#Table(name="player_score")
public class PlayerScore {
#Id
private int scoreId;
#ManyToOne(targetEntity=TeamPlayer.class)
#JoinColumns(value={#JoinColumn(name="team",referencedColumnName="team"),#JoinColumn(name="batsmen",referencedColumnName="player")})
private TeamPlayer batsman;
#ManyToOne(targetEntity=TeamPlayer.class)
#JoinColumns(value={#JoinColumn(name="team",referencedColumnName="team"),#JoinColumn(name="runner",referencedColumnName="player")})
private TeamPlayer runner;
private int score;
#Temporal(TemporalType.DATE)
private Date matchDate;
}
EDIT 1: Added the Mysql WB model as suggested in the comment
EDIT 2: First unsuccessful attempt:
The Team, and Player entities remain as above. But the TeamPlayer has been changed as follows:
#ManyToOne(targetEntity=Player.class)
#PrimaryKeyJoinColumn(name="player",referencedColumnName="id")
private Player player;
The #JoinColumn has been changed to #PrimaryKeyJoinColumn
The annotations for runner field in the PlayerScore entity is changed as follows:
#ManyToOne(targetEntity=TeamPlayer.class)
#JoinColumns(value={#JoinColumn(name="team",referencedColumnName="team",insertable=false,updatable=false),#JoinColumn(name="runner",referencedColumnName="player",insertable=true,updatable=true)})
private TeamPlayer runner;
The expectation is that the FK reference for runner is also generated. THe code compiles and Eclipselink goes thru the generation but the foreign key for runner is NOT generated. In search of success yet...
Related
I have a Student model class, and one of the entities is a List of courses offered by the student. when a sudent account is created, it is created with some courses, but this would need to be updated every semester. I want to update that column to add a new course but I don't know how to do that. I tried a SQL update query, but that just changes the values. I'm not very good with SQL and I would like to know if there's any other way to achieve this.Here's my model class.
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Student {
#Id
private int studentId;
#JsonProperty
private String name;
#JsonProperty
private String email;
#JsonProperty
private String department;
#JsonProperty
private int level;
#JsonProperty
private double cgpa;
#JsonProperty
#ManyToMany
private List<Course> coursesOffered;
I want to map exactly two players to one team with a bidirectional association. For that i use two attributes at the team Entity (for the player) and one attribute at the player (for the team).
Team class:
#Entity
public class Team implements Serializable {
#Id
private int id;
#OneToOne(mappedBy = "name")
private Player player1;
#OneToOne(mappedBy = "name")
private Player player2;
...
}
Player class:
#Entity
public class Player implements Serializable {
#Id
private String name;
#OneToOne
#JoinColumn(name = "TEAM_ID")
private Team team;
...
}
When deploying i got following error:
Internal Exception: Exception [EclipseLink-7244] (Eclipse Persistence Services - 2.7.4.v20190115-ad5b7c6b2a): org.eclipse.persistence.exceptions.ValidationException
Exception Description: An incompatible mapping has been encountered between [class [...].data.entities.Team] and [class [...].data.entities.Player]. This usually occurs when the cardinality of a mapping does not correspond with the cardinality of its backpointer.
What is the correct way to map a 1:2 association in JPA?
I think there is problem in database releations:
Player and Team has two OneToMany Realation(one player can b in many team)
You Should changed to this:
Team class:
#Entity
public class Team implements Serializable {
#Id
private int id;
#OneToMany
#JoinColumn(name = "player_id_1")
private Player player1;
#OneToMany
#JoinColumn(name = "player_id_2")
private Player player2;
...
}
Player class:
#Entity
public class Player implements Serializable {
#Id
private String name;
#ManyToOne(mappedBy = "player1")
private Set<Team> teamPlayer1;
#ManyToOne(mappedBy = "player2")
private Set<Team> teamPlayer2;
...
}
You have described a OneToMany/ManyToOne relationship in the database, but without any ordering. Try mapping it as such and using the position within the list to define the relationship - you can limit it to allowing only collections of size 2 in code or at the database yourself if you need.
#Entity
public class Team implements Serializable {
#Id
private int id;
#OneToMany(mappedBy = "team")
#OrderColumn
private List<Player> players;
...
}
#Entity
public class Player implements Serializable {
#Id
private String name;
#OneToOne
#JoinColumn(name = "TEAM_ID")
private Team team;
...
}
The addition of the #OrderColumn will create a column in the Player table to track their position within the team list.
This then allows you to always assume player1 will be in the first position, while player2 is second in the list and so can write get/setPlayer1 and get/setPlayer2 methods appropriately - just take care to handle empty lists and null values.
I'm trying to implement entity auditing in my Java Spring Boot project using spring-data-envers. All the entities are being created as they should, but I've come up against a brick wall when executing the query.
parentRepository.findRevisions(id).stream().map(Parent::getEntity).collect(Collectors.toList());
During this select the repository is supposed to fetch info also from the child entity, instead I get unable to find <child object> with {id}.
According to my experiments categoryId is being searched in the Category_Aud table, instead of the actual table with desired data.
Code snippets:
#Data
#Entity
#Audited
#NoArgsConstructor
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Enumerated(EnumType.STRING)
private Status status;
#Enumerated(EnumType.STRING)
private Type requestType;
private String fullName;
#ManyToOne
#JoinColumn(name = "child_id")
private Child child;
}
#Data
#Entity
#Audited
#NoArgsConstructor
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
}
I've extended Parent with RevisionRepository
#Repository
public interface ParentRepository extends RevisionRepository<Parent, Long, Long>, JpaRepository<Parent, Long>
And annotated my SpringBootApplication entry class with:
#EnableJpaRepositories(repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class)
I couldn't find any explanation for this so far, how can make parentRepository get what I need?
The underlying problem here is that the reference from a versioned entity isn't really properly defined. Which variant of the reference should be returned? The one at the start of the version you use as a basis, the one at the end? The one that exists right now?
There are scenarios for which each variant makes sense.
Therefor you have to query the revisions yourself and can't simply navigate to them.
I have two entities which has separate composite primary keys, Example :-
#Entity
public class Payment
{
#EmbeddedId
private PaymentPK pk1;
private BigInteger amount;
private String countryCode;
}
#Embeddable
public class PaymentPK implements Serializable {
private Long transactionId;
private String type;
}
#Entity
public class tax
{
#EmbeddedId
private TaxPK pk2;
private BigInteger amount;
private String countryCode;
}
#Embeddable
public class TaxPK implements Serializable {
private Long transactionId;
private String taxType;
}
I want to add relationship #ManyToOne, where every payment transaction can have multiple taxes applied to it. The problem here is both entities have different PK, So join on FK is not working(As per my understanding of JPA it allows join on composite key not on part of it). Please help me how to achieve it.I want join based on transactionId attribute.
This is my sample schema and I have generated jpa entities in eclipse.
I am using spring jpa repositories. I want to know if I need to create repository interface for student course table.
I am having doubt over addStudentCourse method of both student and course entity classes. List studentCourses will be always null for new entity, how can I fill student course table while registering student information in system i.e save method on studentRepository.
Student.java
#Entity
#NamedQuery(name="Student.findAll", query="SELECT s FROM Student s")
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private long studentid;
private String studentname;
//bi-directional many-to-one association to StudentCourse
#OneToMany(mappedBy="student")
private List<StudentCourse> studentCourses;
........
public StudentCourse addStudentCourse(StudentCourse studentCourse) {
getStudentCourses().add(studentCourse);
studentCourse.setStudent(this);
return studentCourse;
}
public StudentCourse removeStudentCourse(StudentCourse studentCourse) {
getStudentCourses().remove(studentCourse);
studentCours.setStudent(null);
return studentCourse;
}
Course.java
#Entity
#NamedQuery(name="Course.findAll", query="SELECT c FROM Course c")
public class Course implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private long courseid;
private String coursename;
//bi-directional many-to-one association to StudentCourse
#OneToMany(mappedBy="course")
private List<StudentCourse> studentCourses;
public StudentCourse addStudentCourse(StudentCourse studentCourse) {
getStudentCourses().add(studentCourse);
studentCourse.setCourse(this);
return studentCourse;
}
public StudentCourse removeStudentCourse(StudentCourse studentCourse) {
getStudentCourses().remove(studentCourse);
studentCourse.setCourse(null);
return studentCourse;
}
StudentCourse.java
#Entity
#Table(name="STUDENT_COURSE")
#NamedQuery(name="StudentCourse.findAll", query="SELECT s FROM StudentCourse s")
public class StudentCourse implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private StudentCoursePK id;
private String status;
//bi-directional many-to-one association to Course
#ManyToOne
#JoinColumn(name="COURSEID")
private Course course;
//bi-directional many-to-one association to Student
#ManyToOne
#JoinColumn(name="STUDENTID")
private Student student;
...
}
StudentCoursePK.java
#Embeddable
public class StudentCoursePK implements Serializable {
//default serial version id, required for serializable classes.
private static final long serialVersionUID = 1L;
#Column(insertable=false, updatable=false)
private long studentid;
#Column(insertable=false, updatable=false)
private long courseid;
...
}
If I understood your question correctly what you want to do is to be able to save a student from the save method in StudentRepository, and that this inserts/updates the student and also inserts/updates the join table.
Since the Student entity is not the owning side (it's mapped by "student" in StudentCourse), saving a Student will not trigger a save on StudentCourse. To do so you can add a cascade property the list for insert, update... or just for everything:
#OneToMany(mappedBy="student", cascade = CascadeType.ALL)
private List<StudentCourse> studentCourses = new ArrayList<StudentCourse>();
Then you could a method on your #Service class that looks like this:
#Transactional
public void enrollInCourse(Student student, Course course) {
StudentCourse sc = new StudentCourse();
sc.setStudent(student);
sc.setCourse(course);
sc.setStatus("Enrolled");
student.getStudentCourses().add(sc);
studentRepository.save(student);
}
This will also populate the StudentCourse table.
So there's no need for a repository, although if the cascade doesn't work as expected you could create one and save the StudentCourse entity yourself manually.
If this does not work you could try changing your mappings. For n-ary relationships or join tables with extra columns I always define the #ManytoOne relationships inside the #Embeddable class, and in the entity that represents the join table I define getters as #Transient to allow access to the mapped objects which are inside the embedded composite Id.
You can see an example here, and a blog post about this approach here.