Hibernate nested entity Column 'id' cannot be null - java

I'm trying to persist a workout object that has a list of exercises which has a list of exerciseSets. The error i get is when hibernate tries to persist the exerciseSets to the database when i use CascadeType.ALL. When i use MERGE i don't get a error, but the ExerciseSet is not saved to the database. But the Workout and Exercise objects are.
What am i missing here? The only thing i can come up with is that i have to place the exercise object inside the exerciseSet object which kinda ends up in a loop of adding them to each other.
The objects are as follows:
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Workout {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
Long workoutId;
#Column
int day;
#Column
String workoutName;
#OneToMany(cascade = CascadeType.ALL,
orphanRemoval = true)
#JoinColumn
List<Exercise> exercises;
#Column
String createDate;
}
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class Exercise {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private Long exerciseId;
#ManyToOne(fetch = FetchType.LAZY)
private Workout workout;
#OneToMany(mappedBy = "exercise",
cascade = CascadeType.ALL)
private List<ExerciseSet> exerciseSets;
#OneToOne(cascade = CascadeType.MERGE)
#JoinTable(name = "var_exercise",
joinColumns =
{ #JoinColumn(name = "exercise_id")},
inverseJoinColumns =
{ #JoinColumn(name = "variation_id" )})
private ExerciseVariations exerciseVariations;
}
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class ExerciseSet {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private Long setId;
#Column
private int setPosition;
#Column
private int reps;
#Column
private int weight;
#ManyToOne
#JoinColumn(name="exerciseId", nullable = false)
private Exercise exercise;
}
I get a WorkoutDto from the frontend and map it to a entity with a mapper as follows
#PostMapping("/save")
public void saveWorkout(#RequestBody WorkoutDto workoutDto) {
workoutService.saveWorkout(workoutMapper.toWorkout(workoutDto));
}
public Workout toWorkout(WorkoutDto workoutDto) {
Workout workout = modelMapper.map(workoutDto, Workout.class);
if (workoutDto.getWorkoutId() != null) {
workout.setWorkoutId(workout.getWorkoutId());
}
workout.setWorkoutName(workoutDto.getWorkoutName());
workout.setDay(workoutDto.getDay());
List<Exercise> exerciseList = new ArrayList<>();
for (ExerciseDto exerciseDto: workoutDto.getExercisesDto()) {
List<ExerciseSet> exerciseSets = new ArrayList<>();
for (ExerciseSetDto exerciseSetDto : exerciseDto.getExerciseSetDtoList()) {
ExerciseSet exerciseSet = new ExerciseSet();
exerciseSet.setSetPosition(exerciseSetDto.getSetPosition());
exerciseSet.setReps(exerciseSetDto.getReps());
exerciseSet.setWeight(exerciseSetDto.getWeight());
exerciseSets.add(exerciseSet);
}
Exercise exercise = exerciseMapper.toExercise(exerciseDto);
exercise.setExerciseSets(exerciseSets);
exerciseList.add(exercise);
}
workout.setExercises(exerciseList);
workout.setCreateDate(workoutDto.getCreateDate());
return workout;
}

You don't set the Exercise in the ExerciseSet and this is the side that manages the relationship.
You code should look like this:
public Workout toWorkout(WorkoutDto workoutDto) {
Workout workout = modelMapper.map(workoutDto, Workout.class);
if (workoutDto.getWorkoutId() != null) {
workout.setWorkoutId(workout.getWorkoutId());
}
workout.setWorkoutName(workoutDto.getWorkoutName());
workout.setDay(workoutDto.getDay());
List<Exercise> exerciseList = new ArrayList<>();
for (ExerciseDto exerciseDto: workoutDto.getExercisesDto()) {
Exercise exercise = exerciseMapper.toExercise(exerciseDto);
List<ExerciseSet> exerciseSets = new ArrayList<>();
for (ExerciseSetDto exerciseSetDto : exerciseDto.getExerciseSetDtoList()) {
ExerciseSet exerciseSet = new ExerciseSet();
exerciseSet.setSetPosition(exerciseSetDto.getSetPosition());
exerciseSet.setReps(exerciseSetDto.getReps());
exerciseSet.setWeight(exerciseSetDto.getWeight());
exerciseSet.setExercise(excercise);
exerciseSets.add(exerciseSet);
}
exercise.setExerciseSets(exerciseSets);
exerciseList.add(exercise);
}
workout.setExercises(exerciseList);
workout.setCreateDate(workoutDto.getCreateDate());
return workout;
}
And then you can set the CascadeType to ALL.

Related

JPA (Hibernate) - ManyToMany - setter

I am not sure if I am doing something wrong, but when I have these two entities:
#Setter #Getter #NoArgsConstructor #FieldDefaults(level = AccessLevel.PRIVATE)
#Entity
#Table(name = "GROUP_OF_USERS")
public class Group {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
Long id;
String name;
#ManyToOne
User administrator;
#ManyToMany(mappedBy = "groups", cascade = CascadeType.ALL)
Set<User> users = new HashSet<>();
public void setAdministrator(User user){
this.administrator = user;
addUser(user);
}
public void addUser(User user){
users.add(user);
}
}
#Entity
#Setter #Getter #NoArgsConstructor #FieldDefaults(level = AccessLevel.PRIVATE)
#Table(name = "APP_USER")
public class User {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
Long id;
String name;
#Column(unique = true)
String email;
String password;
String bankAccount;
boolean isActive = false;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(
name = "GROUP_USER_RELATIONSHIP",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "group_id"))
Set<Group> groups = new HashSet<>();
public void addGroup(Group group){
groups.add(group);
}
}
and service method like this:
#Override
public Group crateGroup(String userName, String groupName) {
var userInDb = userRepository.findUserByEmailIgnoreCase(userName).orElseThrow(() -> new UserNotFoundException(userName));
userInDb.getGroups().stream()
.filter(group -> group.getName().equals(groupName))
.findAny()
.map(Group::getName)
.ifPresent(name -> {throw new GroupUniqueConstrainException(name);});
var newGroup = new Group();
newGroup.setName(groupName);
newGroup.setAdministrator(userInDb);
return groupRepository.save(newGroup);
}
than I am surprised that administrator is set in DB, but join table doesn't contain any record. Could someone help me here to understand where could be a problem? I found out that I could update the addUser method so it would also add group to the user. But I would need to do this also for addGroup method in User class so when calling that it would create infinite loop.

Deleting an entity with one to one relation

My two entities have one to one relation
#Getter
#Setter
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
#Table(uniqueConstraints = #UniqueConstraint(columnNames = "email"), name = "library_user")
public class AppUser {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
#EqualsAndHashCode.Exclude
private Long id;
// other fields
#OneToOne(mappedBy="user", cascade={CascadeType.REMOVE,CascadeType.PERSIST}, orphanRemoval = true)
private PasswordResetToken token;
// getters/setters and equals/hashcode
}
#Getter
#Setter
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
#Table(name = "password_reset_token")
public class PasswordResetToken {
private static final int EXPIRATION = 60 * 24;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// other fields
#OneToOne(targetEntity = AppUser.class, fetch = FetchType.EAGER, cascade={CascadeType.REMOVE,CascadeType.PERSIST}, orphanRemoval = true)
#JoinColumn(nullable = false, name = "user_id")
private AppUser user;
// getters/setters and equals/hashcode
I tried to delete my user entity by this method
public void deleteUser(Long id) {
resetTokenRepository.deleteAllByUserId(id);
userRepository.deleteById(id);
}
PasswordResetTokenRepository class which method I called in my service method, for deleting user I used regular hibernate method deleteById(Long id)
#Repository
public interface PasswordResetTokenRepository extends JpaRepository<PasswordResetToken, Long> {
void deleteAllByUserId(Long id);
}
But when I try to delete by this method I got this error:
not-null property references a null or transient value : kpi.diploma.ovcharenko.entity.user.PasswordResetToken.user
I read several websites how to delete one to one relation, but their advices didn't help me. For example, I tried a lot of variants of annotation cascade={CascadeType.ALL}, tried all the variants(CascadeType.REMOVE,CascadeType.PERSIST and so on), all time I got the same error. Help me pls, to understand what I do wrong.
try this:
#OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
Here is complete explication .

Accessing #JoinTable in many-to-many relationship in Springboot JPA

I've created an API that has actor, movie and category entities. Actor and movie are connected by many-to-many relationship that maps to a join table called movie_actor and category is connected with movie by one-to-many relationship.
I'm trying to write a native query that returns an integer that would represent the amount of movies from a specific category where specific actor has played so for example query would return 2 if actor played in 2 different sci-fi movies. I have no problem doing that from the database level where I can see the join table movie_actor but that table remains unaccessible in my api because it's not a separate entity. How can I create it that it automatically maps actor and movie ids as the movie_actor table ?
Here is an example code that works for me in the H2 Database:
SELECT COUNT(*) FROM MOVIE M JOIN MOVIE_ACTOR MA on M.MOVIE_ID = MA.MOVIE_ID WHERE ACTOR_ID = 1 AND CATEGORY_ID = 1
Here are my entities:
Actor:
#Entity
#Data
#Table(name = "actor")
#AllArgsConstructor
#NoArgsConstructor
public class Actor {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "actor_id")
private long actorId;
#Column(name = "name")
private String name;
#Column(name = "surname")
private String surname;
#Nullable
#ManyToMany(mappedBy = "actors", fetch = FetchType.EAGER)
#JsonBackReference
private List<Movie> movies = new ArrayList<>();
public Actor(String name, String surname){
this.name = name;
this.surname = surname;
}
}
Movie:
#Entity
#Data
#Table(name = "movie")
#AllArgsConstructor
#NoArgsConstructor
public class Movie {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "movie_id")
private long movieId;
#Column(name = "title")
private String title;
#ManyToMany
#JoinTable(
name = "movie_actor",
joinColumns = #JoinColumn(name = "movie_id"),
inverseJoinColumns = {#JoinColumn(name = "actor_id")}
)
#JsonManagedReference
private List<Actor> actors = new ArrayList<>();
#ManyToOne
#JoinColumn(name = "CATEGORY_ID")
#JsonManagedReference
private Category category;
}
So you have to make it accessible in your API. One option would be to map the intersection table movie_actor to the entity MovieActor and split ManyToMany relationship between Actor and Movie to OneToMany relationship with MovieActor, like that:
#Entity
#Data
#Table(name = "movie_actor")
#AllArgsConstructor
#NoArgsConstructor
public class MovieActor {
#EmbeddedId
private MovieActorId productOrderId = new MovieActorId();
#ManyToOne(cascade = CascadeType.ALL, optional = false)
#MapsId("movieId")
#JoinColumn(name = "product_id", nullable = false)
private Movie movie;
#ManyToOne(cascade = CascadeType.ALL, optional = false)
#MapsId("actorId")
#JoinColumn(name = "order_id", nullable = false)
private Actor actor;
public void addMovieActor(Movie aMovie, Actor aActor) {
movie = aMovie;
actor = aActor;
aMovie.getMovieActors().add(this);
aActor.getMovieActors().add(this);
}
}
#Embeddable
#Getter
#Setter
public class MovieActorId implements Serializable {
#Column(name = "movie_id")
private Long movieId;
#Column(name = "actor_id")
private Long actorId;
}
#Entity
#Data
#Table(name = "actor")
#AllArgsConstructor
#NoArgsConstructor
public class Actor {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "actor_id")
private long actorId;
#OneToMany(mappedBy = "actor")
private List<MovieActor> movieActors = new ArrayList<>();
}
#Entity
#Data
#Table(name = "movie")
#AllArgsConstructor
#NoArgsConstructor
public class Movie {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "movie_id")
private long movieId;
#OneToMany(mappedBy = "movie")
private List<MovieActor> movieActors = new ArrayList<>();
}
Now you can access the intersection table MovieActor inside the query. You can even add more columns to this table if you want.

Not able to delete in #OneToMany relationship spring data jpa

In my spring boot project, I have one LineItem entity below is the code
#Entity
#Table(name = "scenario_lineitem")
#Data
#NoArgsConstructor
public class LineItem implements Cloneable {
private static Logger logger = LoggerFactory.getLogger(GoogleConfigConstant.class);
#Id
#GeneratedValue(strategy = IDENTITY)
private BigInteger lineItemId;
#Column
private String name;
#OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.ALL, CascadeType.PERSIST, CascadeType.MERGE })
#JoinColumn(name = "line_item_meta_id")
private List<QuickPopValue> quickPopValues;
}
Another entity is
#Entity
#Table(name = "quick_pop_value")
#Data
#NoArgsConstructor
public class QuickPopValue implements Cloneable {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "quick_pop_value_id", columnDefinition = "bigint(20)", unique = true, nullable = false)
private BigInteger quickPopValueId;
#Column(name = "column_name")
private String columnName;
#Column(name = "value")
private String value;
#Column(name = "formula", columnDefinition = "longtext")
private String formula;
}
Now I am trying to delete QuickPopValue one by one but it's not getting deleted and not getting any exception as well.
Below is the delete code :
List<QuickPopValue> quickPopValues = sheetRepository.findByColumnName(columnName);
for (QuickPopValue qpValue : quickPopValues) {
quickPopValueRepository.delete(qpValue);
}
Such behavior occurs when deleted object persisted in the current session.
for (QuickPopValue qpValue : quickPopValues) {
// Here you delete qpValue but this object persisted in `quickPopValues` array which is
quickPopValueRepository.delete(qpValue);
}
To solve this you can try delete by id
#Modifying
#Query("delete from QuickPopValue t where t.quickPopValueId = ?1")
void deleteQuickPopValue(Long entityId);
for (QuickPopValue qpValue : quickPopValues) {
quickPopValueRepository.deleteQuickPopValue(qpValue.getQuickPopValueId());
}

How to save data-from-parent-to-child-tables-based-on-entities-in-spring-jpa

I have a table master table user ,topics table and comments table
where in for a single topic there can be multiple comments
user table will be already populated.
I will get a post request to save the topic with structure like below
{
"topicId":"T001",
"title":"stackoverflow",
"commentBeans":[
{
"comment":"developer platform"
},
{
"comment":"developer communtiy"
}
]
}
Frameworks used:
spring boot
JPA
DB : postgressql
I am able to save the data the traditional way (i.e get the request and save topic bean first. get the primarykey from saved entity and loop the list of commentbean where user num will be set dynamically by another get service and save them)
I wanted to know if there is anyway to save the data with single save query.
#Entity
#Table(name ="user")
public class User implements Serializable {
#Id
#Column(name = "user_num")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long userNum;
#Column(name = "user_id")
private String userId;
#Column(name = "username")
private String userName;
}
#Entity
#Table(name = "topics")
public class TopicBean implements Serializable {
#Id
#Column(name = "topic_num")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long topicNum;
#Column(name = "topicId")
private String topicId;
#Column(name = "title")
private String title;
#OneToMany(mappedBy="topicBean")
private List<CommentBean> commentBeans;
}
#Entity
#Table(name = "comments")
public class CommentBean implements Serializable {
#EmbeddedId
private CommentBeanKey key;
#Column(name = "comment")
private string comment;
#ManyToOne
#JoinColumn(name="topic_num")
private TopicBean topicBean;
#ManyToOne
#JoinColumn(name="user_num")
private TopicBean topicBean;
}
#Embeddable
public class CommentBeanKey implements Serializable{
private static final long serialVersionUID = 5889249943605061539L;
#Column(name ="topic_num")
private Long topicNum;
#Column(name ="user_num")
private Long userNum;
}
I saw the below link and am little worried if am doing the wrong way. any help is appreciated.
https://thoughts-on-java.org/hibernate-tips-how-to-map-an-entity-to-multiple-tables/
Parent.java
#Entity
#Table(name = "parent")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#ToString
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int parentId;
private String name;
#OneToMany(mappedBy="parent",fetch=FetchType.LAZY,cascade = CascadeType.PERSIST)
private List<Child> child = new ArrayList<Child>();
}
Child.java
#Entity
#Table(name = "child")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#ToString
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int childId;
private String account;
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Parent.class)
#JoinColumn(name="parentId", referencedColumnName = "parentId", nullable = false)
private Parent parent;
}
Controller.java
//save Child with Parent at same
#PostMapping(value = "/onetomany")
public String OneToMany(#RequestBody Parent parent)
{
System.out.println("Parent: "+parent.toString());
for (Child child : parent.getChild()) {
child.setParent(parent);
}
parent.setChild(parent.getChild());
parentRepository.save(parent);
return "saved";
/*{
"name":"Romil",
"child":[
{"account":"1"},
{"account":"2"}
]
}*/
}
//save Child with Parent's ID
#PostMapping(value = "/onetomanyPID")
public String OneToMany(#RequestBody Child child)
{
child.setParent(child.getParent());
childRepository.save(child);
return "saved";
/*{
"account":"3",
"parent":{
"parentId":"1",
"name":"Romil"
}
}*/
}

Categories