0

I have the following classes

public class User {

    @OneToMany(
            fetch = FetchType.EAGER,
            mappedBy = "user",
            cascade = CascadeType.ALL
    )
    private Set<UserSession> sessions;


    public UserSession login() {
        UserSession session = new UserSession();
        session.setUser(this);
        session.persistAndFlush();
        this.persistAndFlush();
        return session;
    }


....

public class UserSession {

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    protected User user;

as you see, I am adding session from it's side. Sometimes in other times I am getting

caused by: org.hibernate.HibernateException: collection was evicted

How to do correctly?

Dims
  • 47,675
  • 117
  • 331
  • 600

1 Answers1

0

To add the element in Bi-Directional relationship,

  • create a helper method in parent class for adding or removing child entity.
  • create child entity and using helper method add it to parent's collection
  • save child entity

Sample code:

@Getter
@Setter
@Entity
class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;
    private String name;
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<UserSession> userSessions = new HashSet<>();

    public void addNewSession(UserSession userSession) {
        this.userSessions.add(userSession);
        userSession.setUser(this);
    }

    public void removeSession(UserSession userSession) {
        this.userSessions.remove(userSession);
        userSession.setUser(null);
    }
}

Fully working code:

package com.example.demo;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.*;
import org.hibernate.annotations.ResultCheckStyle;
import org.hibernate.annotations.SQLDelete;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.data.repository.CrudRepository;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

@RestController
@RequestMapping("/users")
@EnableTransactionManagement
public class UserAndSessionsController {

    private final UserService userService;

    @Autowired
    public UserAndSessionsController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping
    public Iterable<User> list() {
        return userService.list();
    }

    @PostMapping
    public User create(@RequestBody User user) {
        return userService.save(user);
    }

    @GetMapping("/sessions")
    public Iterable<UserSession> sessions(@RequestParam("userId") Integer userId) {
        return userService.getUserSessions(userId);
    }

    @PostMapping(path = "login")
    public UserSession login(@RequestBody User user) throws Throwable {
        return userService.createNewLoginSession(user);
    }

    @PostMapping(path = "logout")
    public UserSession logout(@RequestBody UserSessionsDto userSessionsDto) throws Throwable {
        return userService.invalidateLoginSession(userSessionsDto.getUser(), userSessionsDto.getUserSession());
    }

    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }

}

@ToString
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
class UserSessionsDto {
    private User user;
    private UserSession userSession;
}

@Getter
@Setter
@Entity
class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;
    private String name;
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<UserSession> userSessions = new HashSet<>();

    public void addNewSession(UserSession userSession) {
        this.userSessions.add(userSession);
        userSession.setUser(this);
    }

    public void removeSession(UserSession userSession) {
        this.userSessions.remove(userSession);
        userSession.setUser(null);
    }
}

@Getter
@Setter
@Entity
@SQLDelete(sql = "update USER_SESSION set valid = false where id = ?", check = ResultCheckStyle.COUNT)
class UserSession {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    private String SessionId;

    private boolean valid;

    @JsonIgnore
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    private User user;

}

@Service
class UserService {
    private final UserRepository userRepository;
    private final UserSessionRepository userSessionRepository;

    @Autowired
    UserService(UserRepository userRepository, UserSessionRepository userSessionRepository) {
        this.userRepository = userRepository;
        this.userSessionRepository = userSessionRepository;
    }

    @Transactional
    public User save(User user) {
        return userRepository.save(user);
    }

    @Transactional(readOnly = true)
    public Iterable<User> list() {
        return userRepository.findAll();
    }

    @Transactional(readOnly = true)
    public Iterable<UserSession> getUserSessions(Integer userId) {
//        return userRepository.findById(userId)
//                .map(User::getUserSessions)
//                .orElse(Collections.emptySet());
        return userSessionRepository.findUserSessionsByUser_Id(userId);
    }

    @Transactional
    public UserSession createNewLoginSession(User user) throws Throwable {
        final User dbUser = userRepository
                .findById(user.getId())
                .orElseThrow(() -> new RuntimeException("User : " + user.getName() + " not found"));
        final UserSession userSession = new UserSession();
        userSession.setSessionId(UUID.randomUUID().toString());
        userSession.setValid(true);
        dbUser.addNewSession(userSession);
        userSessionRepository.save(userSession);
        return userSession;
    }

    @Transactional
    public UserSession invalidateLoginSession(User user, UserSession userSession) throws Throwable {
        final User dbUser = userRepository
                .findById(user.getId())
                .orElseThrow(() -> new RuntimeException("User : " + user.getName() + " not found"));
        final UserSession dbUserSession = userSessionRepository
                .findById(userSession.getId())
                .orElseThrow(() -> new RuntimeException("UserSession : " + userSession.getSessionId() + " not found"));
        dbUser.removeSession(dbUserSession);
        userSessionRepository.delete(dbUserSession);
        return dbUserSession;
    }
}


@Repository
interface UserRepository extends CrudRepository<User, Integer> {

}

@Repository
interface UserSessionRepository extends CrudRepository<UserSession, Integer> {
    Iterable<UserSession> findUserSessionsByUser_Id(Integer userId);
}
silentsudo
  • 6,730
  • 6
  • 39
  • 81