Issue
I ask this question because my OneToMany don't add the objects into my set. I would like to know if I made an error or if it's something that I've missed in the configurations. Here is my code :
application.properties :
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=${SMTP_SPREE_USERNAME}
spring.mail.password=${SMTP_SPREE_PASSWORD}
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
server.servlet.context-path=/api
spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/Workshop
spring.datasource.username=${SPREE_DB_USERNAME}
spring.datasource.password=${SPREE_DB_PASSWORD}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL8Dialect
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
jwt.secret=${SPREE_JWT_SECRET}
ActivityCategory.java :
package com.spreeapi.spree.models;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.util.Set;
@Entity
@Getter
@Setter
@Table(name="activity_category")
public class ActivityCategory {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "title")
private String title;
@OneToMany(targetEntity = Activity.class,
cascade = CascadeType.ALL,
fetch = FetchType.EAGER)
private Set<Activity> activities;
}
Activity.java :
package com.spreeapi.spree.models;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
@Entity
@Table(name = "activity")
@Getter
@Setter
public class Activity {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "title")
private String title;
@Column(name = "description")
private String description;
@Column(name = "min_persons")
private String minimalPersons;
@Column(name = "max_persons")
private String maximumPersons;
@Column(name = "is_remote")
private boolean isRemote;
@Column(name = "working_hours")
private boolean workingHours;
@Column(name = "minimal_duration")
private String minimalDuration;
@ManyToOne(targetEntity = ActivityCategory.class)
@JoinColumn(name = "activity_category_id")
private ActivityCategory activityCategory;
}
ActivityCategoryRepository.java :
package com.spreeapi.spree.repository;
import com.spreeapi.spree.models.ActivityCategory;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ActivityCategoryRepository extends JpaRepository<ActivityCategory, Integer> {
}
ActivityRepository.java :
package com.spreeapi.spree.repository;
import com.spreeapi.spree.models.Activity;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ActivityRepository extends JpaRepository<Activity, Long> {
public Page<Activity> findByActivityCategoryId(int id, Pageable pageable);
}
Test.java :
package com.spreeapi.spree.controllers;
import com.spreeapi.spree.models.Activity;
import com.spreeapi.spree.models.ActivityCategory;
import com.spreeapi.spree.repository.ActivityCategoryRepository;
import com.spreeapi.spree.repository.ActivityRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class Test {
@Autowired
private ActivityRepository activityRepository;
@Autowired
private ActivityCategoryRepository activityCategoryRepository;
@GetMapping("/rest/demand/categories")
public List<ActivityCategory> test3(){
return activityCategoryRepository.findAll();
}
@GetMapping("/rest/demand/activities")
public List<Activity> test(){
return activityRepository.findAll();
}
}
pom.xml :
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.spree-api</groupId>
<artifactId>spree</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spree</name>
<description>spree</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
And the result when I did a GET to the /categories :
[
{
"id": 1,
"title": "Escape game",
"activities": []
},
{
"id": 2,
"title": "Jeux de société",
"activities": []
},
{
"id": 3,
"title": "Sports",
"activities": []
},
{
"id": 4,
"title": "Voyage",
"activities": []
},
{
"id": 5,
"title": "Laser-game",
"activities": []
},
{
"id": 6,
"title": "Activité culturelle",
"activities": []
},
{
"id": 7,
"title": "After Work",
"activities": []
},
{
"id": 8,
"title": "Jeux de cartes",
"activities": []
},
{
"id": 9,
"title": "Jeux Vidéo",
"activities": []
},
{
"id": 10,
"title": "Jeux de Rôle sur Table",
"activities": []
},
{
"id": 11,
"title": "Karaoké",
"activities": []
}
]
As you can see the activities attribute of the ActivityCategory return empty but if I do it by getting the list of all activities of the DB here is the result by accessing the /activities :
[
{
"id": 1,
"title": "Escape Game",
"description": "L'Escape Game est un jeu dont le but est à tenter de s’échapper d’une pièce en un temps limité. En groupe de 2 à 6 participants, les joueurs doivent chercher des indices disséminés dans une ou plusieurs pièces, puis les combiner pour avancer dans la salle.",
"minimalPersons": "2",
"maximumPersons": "50",
"workingHours": true,
"minimalDuration": "Quelques heures",
"activityCategory": {
"id": 1,
"title": "Escape game",
"activities": []
},
"remote": false
},
{
"id": 2,
"title": "7 wonders",
"description": "7 Wonders est un jeu de cartes ayant pour thème le développement de civilisations autour de chacune des sept Merveilles du monde en utilisant un système de draft.",
"minimalPersons": "2",
"maximumPersons": "7",
"workingHours": true,
"minimalDuration": "Quelques heures",
"activityCategory": {
"id": 2,
"title": "Jeux de société",
"activities": []
},
"remote": true
},
{
"id": 3,
"title": "Catan",
"description": "Catan est un classique du jeux de société, un jeu de plateau stratégique mais convivial avec un principe d'échange de ressource.",
"minimalPersons": "2",
"maximumPersons": "7",
"workingHours": true,
"minimalDuration": "Quelques heures",
"activityCategory": {
"id": 2,
"title": "Jeux de société",
"activities": []
},
"remote": true
},
{
"id": 4,
"title": "Risk",
"description": "Risk est un jeux de société inspiré du jeux de guerre, en plus simplifier, où votre but est de conquérique le monde.",
"minimalPersons": "2",
"maximumPersons": "6",
"workingHours": true,
"minimalDuration": "1/2 journée",
"activityCategory": {
"id": 2,
"title": "Jeux de société",
"activities": []
},
"remote": true
},
{
"id": 5,
"title": "Les demeures de l'épouvante",
"description": "Les Demeures de l’Épouvante est un jeu de plateau coopératif d’horreur et d’enquête. Les joueurs y incarnent des investigateurs qui s’aventurent dans les salles sombres des demeures hantées d’Arkham",
"minimalPersons": "2",
"maximumPersons": "5",
"workingHours": true,
"minimalDuration": "1/2 journée",
"activityCategory": {
"id": 2,
"title": "Jeux de société",
"activities": []
},
"remote": true
},
{
"id": 6,
"title": "Foot",
"description": "Le football est un sport collectif qui se joue avec un ballon sphérique, où il faut faire pénétrer un ballon rond dans les buts adverses sans utiliser les mains",
"minimalPersons": "5",
"maximumPersons": "100",
"workingHours": true,
"minimalDuration": "1/2 journée",
"activityCategory": {
"id": 3,
"title": "Sports",
"activities": []
},
"remote": false
},
{
"id": 7,
"title": "Tennis de table",
"description": "Le tennis de table, appelé aussi ping-pong, est un sport de raquette opposant deux ou quatre joueurs autour d'une table",
"minimalPersons": "2",
"maximumPersons": "100",
"workingHours": true,
"minimalDuration": "1/2 journée",
"activityCategory": {
"id": 3,
"title": "Sports",
"activities": []
},
"remote": false
},
{
"id": 8,
"title": "Bowling",
"description": "Le bowling est un jeu qui consiste à renverser des quilles à l'aide d'une boule.",
"minimalPersons": "2",
"maximumPersons": "10",
"workingHours": true,
"minimalDuration": "Quelques heures",
"activityCategory": {
"id": 3,
"title": "Sports",
"activities": []
},
"remote": false
},
{
"id": 9,
"title": "Voyage",
"description": "Un voyage en équipe est un bon moyen de souder ses équipes.",
"minimalPersons": "5",
"maximumPersons": "100",
"workingHours": true,
"minimalDuration": "1 journée",
"activityCategory": {
"id": 4,
"title": "Voyage",
"activities": []
},
"remote": false
},
{
"id": 10,
"title": "Laser-game",
"description": "Un jeu laser aussi connu sous le terme anglophone Laser tag est une activité physique où les participants, revêtus habituellement d'une veste à capteurs, se tirent dessus avec des « pistolets laser »",
"minimalPersons": "2",
"maximumPersons": "10",
"workingHours": true,
"minimalDuration": "1/2 journée",
"activityCategory": {
"id": 5,
"title": "Laser-game",
"activities": []
},
"remote": false
},
{
"id": 11,
"title": "Musée",
"description": "le musée est un lieu d'expériences mémorables et émouvantes quand les sens sont interpellés pour attirer, susciter la curiosité, amuser, émerveiller, faire comprendre.",
"minimalPersons": "2",
"maximumPersons": "100",
"workingHours": true,
"minimalDuration": "1/2 journée",
"activityCategory": {
"id": 6,
"title": "Activité culturelle",
"activities": []
},
"remote": false
},
{
"id": 12,
"title": "After Work",
"description": "Cet instant entre collègues permet de resserrer les liens, de souder les équipes, de créer une sorte d'émulation. C'est aussi un moment où l'on dresse un bilan, où l'on cherche à décompresser ensemble dans un lieu en soirée neutre et agréable, loin de l'ambiance du travail.",
"minimalPersons": "2",
"maximumPersons": "20",
"workingHours": false,
"minimalDuration": "Quelques heures",
"activityCategory": {
"id": 7,
"title": "After Work",
"activities": []
},
"remote": false
},
{
"id": 13,
"title": "Poker",
"description": "Le poker est une famille de jeux de cartes comprenant de nombreuses formules et variantes. Il se pratique à plusieurs joueurs avec un jeu généralement de cinquante-deux cartes et des jetons représentant les sommes misées. Les séquences de jeu alternent distribution de cartes et tours d'enchères.",
"minimalPersons": "5",
"maximumPersons": "50",
"workingHours": true,
"minimalDuration": "1/2 journées",
"activityCategory": {
"id": 8,
"title": "Jeux de cartes",
"activities": []
},
"remote": false
},
{
"id": 14,
"title": "Arcade",
"description": "Une salle d’arcade est un établissement de loisirs regroupant des appareils tels que des jeux d’arcade, des flippers, des baby-foots, des tables d'air hockey, des machines attrape-jouet ainsi que des billards.",
"minimalPersons": "10",
"maximumPersons": "50",
"workingHours": true,
"minimalDuration": "1/2 journée",
"activityCategory": {
"id": 9,
"title": "Jeux Vidéo",
"activities": []
},
"remote": true
},
{
"id": 15,
"title": "Honey Heist",
"description": "Y en a deux : OURS et CRIMINEL. Les deux commencent à 3. OURS sert à courir, escalader, péter des trucs, ignorer les dommages, effrayer les gens et autres trucs d’ours. CRIMINEL sert pour tous les autres trucs, ce que les ours ne font pas en général.",
"minimalPersons": "3",
"maximumPersons": "6",
"workingHours": true,
"minimalDuration": "1/2 journée",
"activityCategory": {
"id": 10,
"title": "Jeux de Rôle sur Table",
"activities": []
},
"remote": true
},
{
"id": 16,
"title": "Karaoké",
"description": "Prends le micro et chante",
"minimalPersons": "2",
"maximumPersons": "110",
"workingHours": false,
"minimalDuration": "1/2 journée",
"activityCategory": {
"id": 11,
"title": "Karaoké",
"activities": []
},
"remote": false
}
]
Please let me know if something is missing or an error has been made.
Solution
You have two separate relationships between ActivityCategory
and Activity
they are completely unrelated.
If you want on to be the inverse of the other you have to add a mappedBy
attribute to the @ManyToOne
or @OneToMany
annotation.
Note that the side without the mappedBy
attribute defines what will be persisted to the database while the other side will mirror that information after it got reloaded from the database, which won't happen within a single transaction due to the 1st level cache.
For more details read up on first level cache and on bidirectional relationship.
Answered By - Jens Schauder
Answer Checked By - Robin (JavaFixing Admin)