Search code examples
javamysqlhibernatejpaorm

Hibernate error: object references an unsaved transient instance - save the transient instance before flushing: model.Room


Solved - Updated

I deleted merge user method and adding room to room list from the user.

public void save(Room room, User user) {
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    try {
        session.beginTransaction();
        room.getUsers().add(user);
        session.merge(room);
        session.getTransaction().commit();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        session.close();
    }
}

And updating the user instance out the RoomRepository to get the updated User instance:

public void updateUser() {
    this.user = userRepository.updateUser(user);
}

On UserRepository:

public User updateUser(User user) {
    User result = null;
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    try {
        session.beginTransaction();
        result = session.get(User.class, user.getId());
        session.getTransaction().commit();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        session.close();
    }
    return result;
}

Probably this is a bad practise, so I'll have to think how to refactor this.


Problem

I have class Room and class User with a ManyToMany association.

User

@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "users")
public Set<Room> rooms = new HashSet<>();

Room

@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinTable(name = "sala_usuario",
      joinColumns = @JoinColumn(name = "id_salas"),
      inverseJoinColumns = @JoinColumn(name = "cod_user"))
private Set<User> users = new HashSet<>();

This is the exception's trace:

java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: model.Room
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:161)
at org.hibernate.query.spi.AbstractSelectionQuery.list(AbstractSelectionQuery.java:374)
at org.hibernate.query.sqm.internal.QuerySqmImpl.list(QuerySqmImpl.java:1073)
at org.hibernate.query.Query.getResultList(Query.java:94)
at repository.MessageRepository.findRoomMessages(MessageRepository.java:41)
at app.ChatController.loadMessages(ChatController.java:123)
at app.ChatController.lambda$initialize$0(ChatController.java:45)

This is the save method in RoomRepository:

public void save(Room room, User user) {
        Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        try {
            session.beginTransaction();
            user.getRooms().add(room);
            room.getUsers().add(user);
            session.merge(room);
            session.merge(user);
            session.getTransaction().commit();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            session.close();
        }
}

The error happens when a Room is opened by the user and try to get the messages with OneToMany association because the instance of Room is not persisted on Hibernate, but it is:

public List<Message> findRoomMessages(Room room) {
        List<Message> result = null;
        Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        try {
            session.beginTransaction();
            CriteriaBuilder cb = session.getCriteriaBuilder();
            CriteriaQuery<Message> query = cb.createQuery(Message.class);
            Root<Message> root = query.from(Message.class);
            query.select(root).where(cb.equal(root.get("room"), room));
            result = session.createQuery(query).getResultList();
            session.getTransaction().commit();
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            session.close();
        }
        return result;
    }

I've solved making this, but I want to use ORM:

public List<Message> findRoomMessages(Room room) {
        List<Message> result = null;
        Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        try {
            session.beginTransaction();
            result = session.createNativeQuery("SELECT * FROM mensajes WHERE id_sala = :pid_sala", Message.class)
                            .setParameter("pid_sala", room.getId()).list();
            session.getTransaction().commit();
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            session.close();
        }
        return result;
}

Solution

  • I'm not sure about it and I'm absolutely no hibernate expert but I think you could give it a try.

    public void save(Room room, User user) {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            try {
                Transaction tx = session.beginTransaction();
                room.getUsers().add(user); //it shouldn't be needed to add the room to the users rooms
                session.merge(room);//the save operation should be cascaded, so the user should be automatically saved as well
                tx.commit();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                session.close();
            }
    }