4

I have a some problem.

I have 3 tables:

users (id, name, ..)
roles (id, name)
user-role (user_id, role_id)

When I do many-to-many relationship and do save() I have 3 inserts.

User:

@Entity
@Table(name = "user")
public class User implements Serializable {

    public static final String UK_EMAIL = "uk_email";

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @JoinTable(
        name = "system_user_role",
        joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
        inverseJoinColumns = {@JoinColumn(name = "role_id", referencedColumnName = id")}
    )
    private List<SystemRole> userRole;

    public List<SystemRole> getUserRole() {
        return userRole;
    }

SystemRole;

@Entity
@Table(name = "system_role")
public class SystemRole implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @Column
    private String name;

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(
        name = "system_user_role",
        joinColumns = {@JoinColumn(name = "role_id", referencedColumnName = "id")},
        inverseJoinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")}
    )
    private List<User> users;

    public List<User> getUsers() {
        return users;
    }

    public void setUsers(List<User> users) {
        this.users = users;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

PLEASE, tell me, can I insert data into 2 tables only, only into (User and user_role? I have roles list and I need not add a new role when I create a new user.

So, when I do:

SystemRole role1 = systemRoleService.findOne("ROLE_ADMIN");
userForm.setUserRole(Lists.newArrayList(role1));
....
final User saved = userRepository.save(user);
....

I get an error:

org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to  persist:...

If i do:

@Service("userService")
@Transactional
public class UserServiceImpl implements UserDetailsService, ResourceService<User> {
private final static Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class);

@Autowired
private UserDAO userRepository;
@Autowired
private SystemRoleDAO systemRoleRepository;

@Override
@Transactional(rollbackFor = ResourceException.class)
public User create(User user) throws ResourceException {
    try {
        SystemRole role1 = systemRoleRepository.findOne(6l);
        user.setUserRole(Lists.newArrayList(role1));

        user.setId(62l); // !!! if set user ID - it works CORRECT

        final User saved = userRepository.save(user);
        return saved;
    } catch (DataIntegrityViolationException ex) {
...

UserDAO:

@Repository
public interface UserDAO extends JpaRepository<User, Long> {
...

SystemRoleDAO:

@Repository
public interface SystemRoleDAO extends JpaRepository<SystemRole, Long> {

It works, but I have 3 inserts.

When I create a new user, I need to select a role from list, add it to the user and save the new user.

Many thanks.

user1647166
  • 81
  • 1
  • 8
  • And how do you assign the roles? It looks like you're creating new roles instead of using the existing ones. And please, avoid using rawtypes like `List`. Use `List` instead. – Tom Oct 10 '14 at 08:54
  • I would guess there is an insert because to roles because you create a new role rather than assign an existing one but you would need to post the code which creates and saves. – Alan Hay Oct 10 '14 at 08:54
  • @user1647166 I edited your question, there was a problem with the code formatting. Can you please check this question: http://stackoverflow.com/questions/17887055/org-hibernate-persistentobjectexception-detached-entity-passed-to-persist-with ? Looks related. – Tom Oct 10 '14 at 09:31
  • @Tom, thanks, but I saw this post before, and I have a different situation. I need to use exists roles (I will select them from list), and not create a new role every time, when I create a new user. – user1647166 Oct 10 '14 at 09:37
  • It looks like `systemRoleService.findOne` is calling `detach` on your `role`object. If so, don't do that. `detach` is meant for advanced cross-session operations, not regular object manipulation. – João Mendes Oct 10 '14 at 09:44
  • @João Mendes, I use `org.springframework.data.jpa.repository.JpaRepository` interface and call method `findOne()` from it. – user1647166 Oct 10 '14 at 09:51
  • Is the method there you assing the role and save the user annotated with `@Transactional` (`org.springframework.transaction.annotation.Transactional`)? – Tom Oct 10 '14 at 09:56
  • You need to perform your code within a single transaction. Right now each call to hibernate runs in a separate transaction and your entities are detached after every call. – Erwin Bolwidt Oct 10 '14 at 10:38
  • @Tom, yes, I have changed my `create()` method. – user1647166 Oct 10 '14 at 12:21
  • @ErwinBolwidt, I have changed it, but .... :( – user1647166 Oct 10 '14 at 12:22
  • @Tom @ErwinBolwidt, if I call `user.setId()` - it works correct, but I did that for tests only. I don't know User ID before saving... – user1647166 Oct 10 '14 at 12:42

2 Answers2

1

@Entity @Table(name = "user")

public class User implements Serializable {

public static final String UK_EMAIL = "uk_email";

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;

@ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})  //Make CascadeType.Detached this should solver your problem
@JoinTable(
    name = "system_user_role",
    joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
    inverseJoinColumns = {@JoinColumn(name = "role_id", referencedColumnName = id")}
)
private List<SystemRole> userRole;

public List<SystemRole> getUserRole() {
    return userRole;
}
0

I guess that your code inside the userRepository.save() method is calling

entityManager.persist(user);

Instead you should be calling

entityManager.merge(user);

BY doing so, instead of inserting a new Role, Hibernate will check if a Role with the same ID already exists and if this is case, that Role will be attached to the User entity (provided that the cascade type includes Merge operation).

The reason why you need to call merge is that you are using a Role entity which has been loaded (via systemRoleService.findOne("ROLE_ADMIN")) and then detached from persistence context, so this entity is detached when you try to save the User. If Role had not been detached, you could call persist() instead of merge() on the User entity.

Giovanni
  • 543
  • 2
  • 11
  • Hi, thanks, but I use `org.springframework.data.jpa.repository.JpaRepository` interface and call method `save()` from it. – user1647166 Oct 10 '14 at 09:48
  • Take a look at this http://stackoverflow.com/questions/15943783/how-to-save-a-new-entity-that-refers-existing-entity-in-spring-jpa – Giovanni Oct 10 '14 at 10:14
  • I have changed my code, but ... :( Please, take a look at the changes. Thanks. – user1647166 Oct 10 '14 at 12:25
  • if I call `user.setId()` - it works correct, but I did that for tests only. I don't know User ID before saving... – user1647166 Oct 10 '14 at 12:43
  • Yes because in this case the repository will call merge() instead of persist(). You have to understand to figure out what is happening in your transaction. Try to log org.springframework.transaction at the TRACE level – Giovanni Oct 10 '14 at 12:55