Spring Boot with hibernate: Creating Composite Key and Many-to-many relations - java

I am trying to create a very simple Spring Boot application for storing sports data.
It has two entities: player and tournament. However, I want to store how each player placed in each tournament.
For this, I created the following ER Diagram. The junction table PlayerPlacement_map contains a relationship-attribute placement for storing how the players place in a tournament:
I have followed this guide on how to map the relationship between Players and Tournament, with Id from those two tables as a composite key in the junction table called PlayerPlacementMap: https://www.baeldung.com/jpa-many-to-many
That has given me the following classes:
Player.java (Tournament.java follows a similar pattern - left out for brevity)
#Entity
#Table(name = "players")
public class Player {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Id", updatable = false, nullable = false)
private long id;
#Column(name = "Name")
private String name;
#ManyToMany
#JoinTable(
name = "PlayerTournament_map",
joinColumns = #JoinColumn(name = "Player_Id"),
inverseJoinColumns = #JoinColumn(name = "Tournament_Id"))
Set<Tournament> attendedTournaments;
#OneToMany(mappedBy = "player")
Set<PlayerPlacement> placements;
//getters, constructors - left out for brevity
}
PlayerPlacementKey.java (making an embeddable composite key-class)
#Embeddable
public class PlayerPlacementKey implements Serializable {
#Column(name = "Player_Id")
long playerId;
#Column(name = "Tournament_Id")
long tournamentId;
//getters, setters, constructors, equals, hashcode - left out for brevity
}
PlayerPlacement.java (junction table)
#Entity
#Table(name = "PlayerPlacement_map")
public class PlayerPlacement {
#EmbeddedId
PlayerPlacementKey id;
#ManyToOne
#MapsId("PlayerId")
#JoinColumn(name = "Player_Id")
Player player;
#ManyToOne
#MapsId("TournamentId")
#JoinColumn(name = "Tournament_Id")
Tournament tournament;
int placement;
//constructors - left out for brevity
}
I have repositories for player, tournament and playerplacement. Player and tournament repositories are working fine on their own, and I am able to perform CRUD operations through a #RestController against a MSSQL database.
This is the repository for playerplacement:
#Repository
public interface PlayerPlacementRepository extends JpaRepository<PlayerPlacement, PlayerPlacementKey>
{}
For the junction table, I have made a PlayerPlacementController:
#RestController
public class PlayerPlacementController {
#Autowired
private PlayerPlacementRepository playerPlacementRepository;
#PostMapping("/playerplacement")
public PlayerPlacement addPlayerPlacement(#RequestBody PlayerPlacement playerPlacement) {
return playerPlacementRepository.save(playerPlacement);
}
}
Finally, here comes the problem: When I call the /"playerplacement" endpoint, I receive the following error:
org.hibernate.id.IdentifierGenerationException: null id generated for:class com.testproject.learning.model.PlayerPlacement
I think I have migrated the database just fine, but just to be safe, here is my migration for the junction table:
CREATE TABLE PlayerPlacement_map (
PlayerId Bigint NOT NULL,
TournamentId Bigint NOT NULL,
Placement int
CONSTRAINT PK_PlayerPlacement NOT NULL IDENTITY(1,1) PRIMARY KEY
(
PlayerId,
TournamentId
)
FOREIGN KEY (PlayerId) REFERENCES Players (Id),
FOREIGN KEY (TournamentId) REFERENCES Tournaments (Id)
);
I haven't been able to figure out what I am doing wrong. I appreciate all help and pointers that I receive - thanks in advance.
EDIT:
Progress has been made with help of user Alex V., but I hit a new problem as of right now.
I make the following JSON call:
{
"player":
{
"name":"John Winther"
},
"tournament":
{
"name":"Spring Boot Cup 2020"
},
"placement":3
}
I don't set the id (composite key) myself - I guess it should be handled by something of the framework? Anyways, when I make this call in debug mode, I get the following debug variables set in the endpoint:
However, it results in the following error:
java.lang.NullPointerException: Cannot invoke "Object.getClass()" because "o" is null, which probably refers to my equals-method in PlayerPlacementKey.java (?):
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PlayerPlacementKey)) return false;
PlayerPlacementKey that = (PlayerPlacementKey) o;
return playerId == that.playerId &&
tournamentId == that.tournamentId;
}
Hope anybody can push me in the right direction one more time.

Your entity has playerId field annotated as #Column(name = "Player_Id"), but database table contains PlayerId column instead of Player_Id. The same situation with tournamentId, player, tournament fields.
#MapsId("TournamentId") has to contain #EmbededId class field name tournamentId instead of TournamentId. It means the field is mapped by embeded id field tournamentId.
#ManyToOne
#MapsId("tournamentId")
#JoinColumn(name = "tournamentId")
Tournament tournament;
The same problem here #MapsId("PlayerId") .

Related

Hibernate sometimes doesn't extract uuid referenced entity

I have two entities, let's name them University and Student, Student is unidirectional ManyToOne with University they both extends base class BasicUUIDEntity:
#MappedSuperclass
public class BasicUUIDEntity implements Serializable {
protected UUID id;
#Id
#Column(name = "id", columnDefinition = "binary(16)")
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BasicUUIDEntity that = (BasicUUIDEntity) o;
return id == null || id.equals(that.id);
}
#Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
}
The structure of Student and University doesn't really matters, the important things is:
Student.class:
#Entity
#Table(name = "students")
public static class Student extends BasicUUIDEntity {
private University univ;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "univ_id", referencedColumnName = "id")
public University getUniversity() {
return univ;
}
public void setUniversity(University univ) {
this.univ = univ;
}
}
University.class:
#Entity
#Table(name = "universities")
public static class University extends BasicUUIDEntity {
//of course it does contain some fields, but they are strings, etc.
}
The tables are created as follows:
CREATE TABLE students
(
id BINARY(16) NOT NULL,
univ_id BINARY(16),
PRIMARY KEY (id),
CONSTRAINT FK_STUDENT_UNIV
FOREIGN KEY (univ_id) REFERENCES memory_type (id)
ON DELETE SET NULL
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8;
CREATE TABLE universities
(
id BINARY(16) NOT NULL,
PRIMARY KEY (id)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8;
The Issue is sometimes, on really rare occasions Hibernate doesn't extract that University entity along with Student, meaning that when I do student.getUniversity() it returns null, and in debugger it is also null. BUT the students table contains exactly the same univ_id as expected University of this student, and so does university contains that id. I am completely sure that hibernate session is not closed, and I've tried to execute exactly the same query as hibernate does, and it does return expected id of the university, joins them, etc. The thing is that most of the time It does work well, and such issue happens very rarely due to unknown reasons, what is more strange is that the table does contains that id, so it seems like it is not an MySQL issue.
Also worth mention, I am using Spring Data JPA.
Have anyone encountered such behavior? Could it be due to Inheritance/Data JPA/Java 9/Anything other?
PS.
Please ignore any typo in the code, it is just an example
Versions:
Hibernate: 5.2.12.Final,
Spring Data JPA: 2.0.5.RELEASE,
HikariCP: 2.7.8,
MySQL connector: 6.0.6,
Java: 9
MySQL: Ver 14.14 Distrib 5.7.21
Solved, finally
tl;dr
Put fetch = FetchType.LAZY to ManyToOne relation, so in my example it becomes:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "univ_id", referencedColumnName = "id")
public University getUniversity() {
return univ;
}
Description
I am not still sure why it behaves like this, but it seems that Hibernate does not extract many-to-one relations at all, if they're eager. It have some sense as this way there will be circular dependencies, but I thought that PersistenceSet will deal with such issues. What is even more strange - just the same structure actually works without eager extraction if I use Long instead of UUID.

Saving to child table with springboot+jpa

I have trouble in saving... fetch from child table is working
Here is my parent table pojo
#Entity
#Table(name = "Dei_Resources")
public class DeiResources {
private int id;
private String employeeId;
private Set<DeiResourceType> deiResourceType;
//other setters getters not included
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public int getId() {
return id;
}
#OneToMany(fetch = FetchType.EAGER, mappedBy = "deiResource", cascade = CascadeType.ALL)
public Set<DeiResourceType> getDeiResourceType() {
return deiResourceType;
}
Child Table pojo
#Entity
public class DeiResourceType implements Serializable{
private int id;
private int resourceId;
private String typeValue;
#JsonBackReference
private DeiResources deiResource;
//other setters getters not included
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public int getId() {
return id;
}
#ManyToOne(fetch=FetchType.LAZY,optional=false)
#JoinColumn(name = "resourceId", referencedColumnName="id",insertable = false, updatable = false)
public DeiResources getDeiResource() {
return deiResource;
}
I have DeiResourcesRepository in place, in my service Im trying this
DeiResources dei = new DeiResources();
DeiResourceType deii = new DeiResourceType();
Set<DeiResourceType> deiResourceType = new HashSet<DeiResourceType>();
deii.setTypeValue("Driver");
deiResourceType.add(deii);
dei.setEmployeeId("unique1");
dei.setDeiResourceType(deiResourceType);
deiResourcesRepository.save(dei);
Getting this error
[ERROR] org.hibernate.engine.jdbc.spi.SqlExceptionHelper - ORA-02291:
integrity constraint (DEI_ADMIN.DEI_RESOURCE_TYPE_R01) violated -
parent key not found
In DeiResourceType table I have added foreign key constrain with Parent table ID. How can I get rid of this error, any suggestion/help ?
First, in SQL there is no Child/Parent pattern. Thats why the imagination of a structure like a real family is wrong. In this example a parent can not be a child, in the real world everyone who is parent is a child too!
We only have two Tables who have a 1-n relation respecting the constraint that every DeiResource must have a DeiResourceType! As long as this constraint keeps it integrity, there is no exception.
This in mind you call the constructor of two entitys but you save only one (you save the DeiResource without DeiResourceType). So you save a Resource without a type. But the constraint that every DeiResource must have a DeiResourceType is broken and the database has no integrity anymore and the Exception is thrown!
You have two options:
You save the DeiResourceType first and then the DeiResource
You load a existing DeiResourceType first, wire it to the DeiResource and save the DeiResource then.
I would prefer the second method to avoid duplicates in the Table "DeiResourceType".
(By the way, theese constraints can be deferrable, a old, forgotten and mighty magic)

JPA - Check existence of value in another table as Boolean value

I have a (abbreviated) class that looks like this:
#Entity
#Table
#SecondaryTable(
name = "SUPER_ADMIN",
pkJoinColumns = #PrimaryKeyJoinColumn(
name = "PERSON_ID",
referencedColumnName = "PERSON_ID"))
public class Person {
#Id
#Column(name = "PERSON_ID")
private Long personId;
// getters/setters omitted for brevity
}
The SUPER_ADMIN table has only one column: PERSON_ID. What I would like to do is add private Boolean superAdmin to Person where it would be true if the PERSON_ID is present in that table.
Is this even possible? I am using Hibernate as my JPA provider, so I'm open to proprietary solutions as well.
UPDATE
It seems like I should have done more homework. After poking around, I see that #SecondaryTable does inner joins and not outer joins. Therefore, my idea here will not work at all. Thanks to #Elbek for the answer -- it led me to this revelation.
You can use JPA callback methods.
public class Person {
#Id
#Column(name = "PERSON_ID")
private Long personId;
#Transient
private transient Boolean superAdmin = false;
// This method will be called automatically when object is loaded
#PostLoad
void onPostLoad() {
// BTW, personId has to be present in the table since it is id column. Do you want to check if it is 1?
superAdmin = personId == 1;
}
}
or you can create easy getter method.
public class Person {
#Id
#Column(name = "PERSON_ID")
private Long personId;
boolean isSuperAdmin() {
return personId == 1;
}
}
You can't have an optional relationship with a #SecondaryTable. You do not have any other choice than using a #OneToOne optional relationship in that case.

Persisting set of Enums in a many-to-many unidirectional mapping

I'm using Hibernate 3.5.2-FINAL with annotations to specify my persistence mappings. I'm struggling with modelling a relationship between an Application and a set of Platforms. Each application is available for a set of platforms.
From all the reading and searching I've done, I think I need to have the platform enum class be persisted as an Entity, and to have a join table to represent the many-to-many relationship. I want the relationship to be unidirectional at the object level, that is, I want to be able to get the list of platforms for a given application, but I don't need to find out the list of applications for a given platform.
Here are my simplified model classes:
#Entity
#Table(name = "TBL_PLATFORM")
public enum Platform {
Windows,
Mac,
Linux,
Other;
#Id
#GeneratedValue
#Column(name = "ID")
private Long id = null;
#Column(name = "NAME")
private String name;
private DevicePlatform() {
this.name = toString();
}
// Setters and getters for id and name...
}
#Entity
#Table(name = "TBL_APP")
public class Application extends AbstractEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "NAME")
protected String _name;
#ManyToMany(cascade = javax.persistence.CascadeType.ALL)
#Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})
#JoinTable(name = "TBL_APP_PLATFORM",
joinColumns = #JoinColumn(name = "APP_ID"),
inverseJoinColumns = #JoinColumn(name = "PLATFORM_ID"))
#ElementCollection(targetClass=Platform.class)
protected Set<Platform> _platforms;
// Setters and getters...
}
When I run the Hibernate hbm2ddl tool, I see the following (I'm using MySQL):
create table TBL_APP_PLATFORM (
APP_ID bigint not null,
PLATFORM_ID bigint not null,
primary key (APP_ID, PLATFORM_ID)
);
The appropriate foreign keys are also created from this table to the application table and platform table. So far so good.
One problem I'm running into is when I try to persist an application object:
Application newApp = new Application();
newApp.setName("The Test Application");
Set<DevicePlatform> platforms = EnumSet.of(Platform.Windows, Platform.Linux);
newApp.setPlatforms(platforms);
applicationDao.addApplication(newApp);
What I would like to happen is for the appropriate rows in the Platform table to created, i.e. create a row for Windows and Linux, if they don't already exist. Then, a row for the new application should be created, and then the mapping between the new application and the two platforms in the join table.
One issue I'm running into is getting the following runtime exception:
2010-06-30 13:18:09,382 6613126-0 ERROR FlushingEventListener Could not synchronize database state with session org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.example.model.Platform
Somehow, the platform set is not being persisted when I try to persist the application. The cascade annotations are supposed to take care of that, but I don't know what's wrong.
So my questions are:
Is there a better way to model what I want to do, e.g. is using an Enum appropriate?
If my model is alright, how do I properly persist all of the objects?
I've been struggling with this for hours, and I've tried to recreate all of the code above, but it might not be complete and/or accurate. I'm hoping someone will point out something obvious!
You should decide whether your Platform is an entity or not.
If it's an entity, it can't be an enum, because list of possible platforms is stored in the database, not in the application. It should be a regular class with #Entity annotation and you will have a normal many-to-many relation.
If it isn't an entity, then you don't need TBL_PLATFORM table, and you don't have a many-to-many relation. In this case you can represent a set of Platforms either as an integer field with bit flags, or as a simple one-to-many relation. JPA 2.0 makes the latter case simple with #ElementCollection:
#ElementCollection(targetClass = Platform.class)
#CollectionTable(name = "TBL_APP_PLATFORM",
joinColumns = #JoinColumn(name = "APP_ID"))
#Column(name = "PLATFORM_ID")
protected Set<Platform> _platforms;
-
create table TBL_APP_PLATFORM (
APP_ID bigint not null,
PLATFORM_ID bigint not null, -- the ordinal number of enum value
primary key (APP_ID, PLATFORM_ID)
);
and enum Platform without annotations.
Simple use below mapping on your entity. Suppose that we have:
public enum TestEnum { A, B }
Then in your Entity class:
#ElementCollection(targetClass = TestEnum.class)
#CollectionTable(
name = "yourJoinTable",
joinColumns = #JoinColumn(name = "YourEntityId")
)
#Column(name = "EnumId")
private final Set<TestEnum> enumSet= new HashSet<>();
The following example shows what the situation is when Module is an entity and Langue is an enum.
#Entity
public class Module {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String libelle;
#ElementCollection(targetClass = Langue.class, fetch = FetchType.EAGER)
#CollectionTable(name = "link_module_langue",
joinColumns = #JoinColumn(name = "module_id", referencedColumnName = "id"))
#Enumerated(EnumType.STRING)
#Column(name = "langue")
private Set<Langue> langues;
}
public enum Langue {
FRANCAIS, ANGLAIS, ESPAGNOLE
}
You should create link_module_langue table, please see the following sql code :
CREATE TABLE `link_module_langue` (
`module_id` BIGINT(20) NOT NULL,
`langue` VARCHAR(45) NOT NULL,
PRIMARY KEY (`module_id`, `langue`),
CONSTRAINT `module_fk`
FOREIGN KEY (`module_id`)
REFERENCES `module` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE);
NB: Langue is not an entity and would not have its own table.

Does Hibernate support one-to-one associations as pkeys?

Can anyone tell me whether Hibernate supports associations as the pkey of an entity? I thought that this would be supported but I am having a lot of trouble getting any kind of mapping that represents this to work. In particular, with the straight mapping below:
#Entity
public class EntityBar
{
#Id
#OneToOne(optional = false, mappedBy = "bar")
EntityFoo foo
// other stuff
}
I get an org.hibernate.MappingException: "Could not determine type for: EntityFoo, at table: ENTITY_BAR, for columns: [org.hibernate.mapping.Column(foo)]"
Diving into the code it seems the ID is always considered a Value type; i.e. "anything that is persisted by value, instead of by reference. It is essentially a Hibernate Type, together with zero or more columns." I could make my EntityFoo a value type by declaring it serializable, but I wouldn't expect this would lead to the right outcome either.
I would have thought that Hibernate would consider the type of the column to be integer (or whatever the actual type of the parent's ID is), just like it would with a normal one-to-one link, but this doesn't appear to kick in when I also declare it an ID. Am I going beyond what is possible by trying to combine #OneToOne with #Id? And if so, how could one model this relationship sensibly?
If the goal is to have a shared primary key, what about this (inspired by the sample of Java Persistence With Hibernate and tested on a pet database):
#Entity
public class User {
#Id
#GeneratedValue
private Long id;
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private Address shippingAddress;
//...
}
This is the "parent" class that get inserted first and gets a generated id. The Address looks like this:
#Entity
public class Address implements Serializable {
#Id #GeneratedValue(generator = "myForeignGenerator")
#org.hibernate.annotations.GenericGenerator(
name = "myForeignGenerator",
strategy = "foreign",
parameters = #Parameter(name = "property", value = "user")
)
#Column(name = "ADDRESS_ID")
private Long id;
#OneToOne(mappedBy="shippingAddress")
#PrimaryKeyJoinColumn
User user;
//...
}
With the above entities, the following seems to behave as expected:
User newUser = new User();
Address shippingAddress = new Address();
newUser.setShippingAddress(shippingAddress);
shippingAddress.setUser(newUser); // Bidirectional
session.save(newUser);
When an Address is saved, the primary key value that gets inserted is the same as the primary key value of the User instance referenced by the user property.
Loading a User or an Address also just works.
Let me know if I missed something.
PS: To strictly answer the question, according to Primary Keys through OneToOne Relationships:
JPA 1.0 does not allow #Id on a OneToOne or ManyToOne, but JPA 2.0 does.
But, the JPA 1.0 compliant version of Hibernate
allows the #Id annotation to be used on a OneToOne or ManyToOne mapping*.
I couldn't get this to work with Hibernate EM 3.4 though (it worked with Hibernate EM 3.5.1, i.e. the JPA 2.0 implementation). Maybe I did something wrong.
Anyway, using a shared primary key seems to provide a valid solution.
Yes that is possible.
Look at the following example using Driver and DriverId class as id for Driver.
#Entity
public class Drivers {
private DriversId id; //The ID which is located in another class
public Drivers() {
}
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "personId", column = #Column(name = "person_id", nullable = false))})
#NotNull
public DriversId getId() {
return this.id;
}
//rest of class
}
Here we are using personId as the id for Driver
And the DriversId class:
//composite-id class must implement Serializable
#Embeddable
public class DriversId implements java.io.Serializable {
private static final long serialVersionUID = 462977040679573718L;
private int personId;
public DriversId() {
}
public DriversId(int personId) {
this.personId = personId;
}
#Column(name = "person_id", nullable = false)
public int getPersonId() {
return this.personId;
}
public void setPersonId(int personId) {
this.personId = personId;
}
public boolean equals(Object other) {
if ((this == other))
return true;
if ((other == null))
return false;
if (!(other instanceof DriversId))
return false;
DriversId castOther = (DriversId) other;
return (this.getPersonId() == castOther.getPersonId());
}
public int hashCode() {
int result = 17;
result = 37 * result + this.getPersonId();
return result;
}
}
You can do this by sharing a primary key between EntityFoo and EntityBar:
#Entity
public class EntityBar
{
#Id #OneToOne
#JoinColumn(name = "foo_id")
EntityFoo foo;
// other stuff
}
#Entity
public class EntityFoo
{
#Id #GeneratedValue
Integer id;
// other stuff
}
You have to use #EmbeddedId instead of #Id here.
And EntityFoo should be Embeddable.
Another way is to put an integer, and a OneToOne with updateble and instertable set to false.

Categories