Map One to two association - jpa

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.

Related

JPA entity inheritance: which instance to create for lazy initialization?

JPA entity inheritance: which instance to create for lazy initialization? For example,
Single table mapping strategy:
Teacher(abstract)
/ \
FullTimeTeacher PartTimeTeacher
Entity School referencing Teacher:
#Entity
public class School {
#ManyToOne(fetch=FetchType.LAZY)
private Teacher manager;
}
When retrieving a School entity from database, the school's manager is lazy, not initialized. Which type of proxy will be instantiated? Teacher is abstract.
The proxy may not match the actual referenced type (Full Time or Part Time Teacher).
I was curious myself, and tested it with the following setup:
#Entity
public class Garage {
#Id
#GeneratedValue
private Long id;
#OneToOne(fetch = FetchType.LAZY, optional = false, cascade = CascadeType.ALL)
private Car car;
...
}
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class Car {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
...
}
#Entity
public class SportsCar extends Car {
private int hp;
...
#Override
public String toString() {
return "SportsCar [hp=" + hp + ", getId()=" + getId() + "]";
}
}
The test:
Garage garage = new Garage();
SportsCar car = new SportsCar();
car.setHp(350);
garage.setCar(car);
em.persist(garage);
...
Garage garage = em.find(Garage.class, garage.getId());
System.out.println(garage.getCar().getClass());
System.out.println(garage.getCar());
System.out.println(garage.getCar() instanceof SportsCar);
The above prints:
class com.example.Car_$$_jvstd71_f
SportsCar [hp=350, getId()=1]
false
Conclusion: Hibernate will create a proxy of the superclass. That proxy will, however, delegate method calls to the subclass instance.

JPA: Share one column among multiple FK mappings

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...

Load data from two OneToMany relationships

I want to display data from a database with JPA on the frontend, i.e. display the Person's details. Details can be 0..n Adresses and 0..m Phones. The entities are shown below:
#Entity
public class Person implements Serializable {
#Id #GeneratedValue
private int id;
private String name;
#OneToMany(mappedBy="person")
private List<Address> addresses = new ArrayList<Address>();
#OneToMany(mappedBy="person")
private List<Phone> phones = new ArrayList<Phone>();
// plus getter and setter
}
#Entity
public class Address implements Serializable {
#Id #GeneratedValue
private int id;
#ManyToOne
private Person person;
private String onestring; // plus getter and setter
}
#Entity
public class Phone implements Serializable {
#Id #GeneratedValue
private int id;
#ManyToOne
private Person person;
private String anotherstring; // plus getter and setter
}
As lazy loading is activated, ...
#PersistenceContext
private EntityManager em;
public Person getPerson(int id) {
return em.find(Person .class, id);
}
... would only provide proxies on adresses and phones.
Questions:
What is good way displaying the all data on the frontend, i.e. the person and all its addesses and phones? (Except for setting FetchType to EAGER).
Is there a way to fetch both addresses and phones into the same instance of Person or do I have to fetch the Person twice (one time with addresses, one time with phones) to omit a cartesian product?

Join Table and Spring Data Repository

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.

How to map existing JPA entities to PicketLink

I am trying to migrate a Seam 2 app to CDI and use PicketLink for security. After all the reading and researching, it seems like all the examples are having one to one mapping between PicketLink model and the backend entity. e.g. Account to AccountEntity, Partition to PartitionEntity. Since I already have entities in place representing identity model, I am stuck on trying to map them to PicketLink. Here is what I have:
#MappedSuperClass
public class ModelEntityBase implement Serializable {
#Id #Generated
Long id;
Date creationDate;
}
#Entity
public Account extends ModelEntityBase {
String username;
String passwordHash;
#OneToOne(mappedBy = "account")
Person person;
}
#Entity
public Person extends ModelEntityBase {
String name;
String email;
#OneToOne
#JoinColumn(name = "account_id")
Account account;
}
Two entities (plus a super class) representing a single identity model in PicketLink, e.g. stereo type User.
Based on this why IdentityType id is String not Long, I tried to add a new Entity in:
#Entity
#IdentityManaged(BaseIdentityType.class);
public class IdentityTypeEntity implement Serializble {
#Id #Identifier
private String id;
#OneToOne(optional = false, mappedBy = "identityType")
#OwnerReference
private Account account;
#IdentityClass
private String typeName;
#ManyToOne #OwnerReference
private PartitionEntity partition;
}
I've tried a few different ways with the annotation and model classes. But when using IdentityManager.add(myUserModel), I just can't get it to populate all the entities. Is this even possible?
Got help from Pedro (PicketLink Dev). Post the answer here to help others.
This is the model class I ended up using.
#IdentityStereotype(USER)
public class User extends AbstractAttributedType implements Account {
#AttributeProperty
private Account accountEntity;
#AttributeProperty
#StereotypeProperty(IDENTITY_USER_NAME)
#Unique
private String username;
#AttributeProperty
private boolean enabled;
#AttributeProperty
private Date createdDate;
#AttributeProperty
private Date expiryDate;
#AttributeProperty
private Partition partition;
// getter and setter omitted
}
And created a new entity to map to this model:
public class IdentityTypeEntity implements Serializable {
#Id
#Identifier
private String id;
#OneToOne(optional = false, mappedBy = "identityType",
cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#AttributeValue
// #NotNull
private HAccount accountEntity;
#IdentityClass
private String typeName;
#ManyToOne
#OwnerReference
private PartitionEntity partition;
#AttributeValue
private String username;
#AttributeValue
// #Transient
private boolean enabled;
#AttributeValue
private Date createdDate;
#AttributeValue
private Date expiryDate;
}
PL can map property with #AttributeProperty to entity property with #AttributeValue. But it can only map to one entity. Therefore there is no way to map, say User and its properties over to Account and Person. But you can have the entity (in my case accountEntity) in the model. I also have to duplicate a few fields in the new IdentityTypeEntity and my existing Account entity (username, eanbled, createdDate) because PL requires these. Use a #PrePersist and similar to sync them.