【问题标题】:Persisting new object with nested objects from Spring MVC view使用 Spring MVC 视图中的嵌套对象持久化新对象
【发布时间】:2021-04-16 11:47:02
【问题描述】:

上下文

有一个使用 Spring MVC 和 Spring Data JPA 的应用程序。 JPA 提供者是 Hibernate,视图层由 JSP 页面呈现。

有一个用于创建新用户的页面。通过此页面创建新用户时,该用户被分配到一个用户组(从列表中选择)。 User 是新的,但此时选定的 Group 已存在。 User @Entity 中有一组 Group 对象。 Users 和 Groups 之间的双向关联是 @ManyToMany,并且一个表 (group_members) 链接这些实体。

当视图层将User 对象传递给控制器​​时,setGroups(Set<Group> groups) 方法将在User 上调用。然后这个User 对象通过userDetailsService.add(user) 持久化,即@Transactional


问题

Groups 在页面呈现时加载到视图中,然后它们与持久性单元分离。 在保持User 时,关联的Group 被分离,Group 端与User 不同步。 User 实体定义了可以同步事务两侧(addGroup(Group group)removeGroup(Group group))的实用方法,但我找不到使用它们的方法,因为这样它们只能在获取的事务之外调用在服务层创建。

问题

在这种情况下,让所有实体始终处于有效状态的最佳方法是什么? User 和 Group 都应该传递给服务层吗?


代码

为了简洁,省略了一些部分。

createuser.jsp

<body>
<sf:form id="details" method="post" action="${pageContext.request.contextPath}/docreate" modelAttribute="user">

<h2>Sing up</h2>
<table class="formtable">
  <tr>
    <td class="label">Userame: </td> <td><sf:input path="username" name="username" type="text" /> <div class="error"> <sf:errors path="username"></sf:errors> </div></td>
  </tr>
    <tr>
    <td class="label">Password: </td> <td><sf:input id="password" path="password" name="password" type="password" /> <div class="error"><sf:errors path="password"></sf:errors> </div></td>
  </tr>
    <tr>
    <td class="label">Confirm password: </td> <td><input id="confirmpass" name="confirmpass" type="password" /> <div id="matchpass"></div></td>
  </tr>
  <tr>
   <td class="label" align="right">Group</td><td><sf:select id="groups" path="groups" items="${groups}" itemValue="id" 
            itemLabel="groupName"/></td>
  </tr>
    <tr>
    <td> </td> <td class="button"><input value="Create user" type="submit" /> </td>
  </tr>
</table>

</sf:form>
</body>

UserController.java

@RequestMapping(value="/docreate", method=RequestMethod.POST)
public String doCreate(Model model, @Validated(FormValidationGroup.class) User user, BindingResult result) {

    // User validation ...
    user.setEnabled(true);

    // Duplicate user check ...
    userDetailsService.add(user);       
    return "usercreated";
}

UserDetailsS​​erviceImpl.java

@Transactional
public void add(User user) {
    if (!contains(user.getUsername())) {
        userRepository.save(user);
    }       
}

用户.java

@Entity
@Table(name="users",schema="sec")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String username;    
    private String password;
    private boolean enabled;    

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name="group_members", schema="sec", joinColumns= { @JoinColumn(name="user_id") }, inverseJoinColumns = { @JoinColumn(name="group_id") } )
    private Set<Group> groups = new HashSet<>();
    
    // ...
    
    public void addGroup(Group group) {
        this.groups.add(group);
        group.getUsers().add(this);
    }

    public void removeGroup(Group group) {
        this.groups.remove(group);
        group.getUsers().remove(this);
    }
    
    public void setGroups(Set<Group> groups) {
        for (Group group : groups) {
            this.groups.add(group);
        }
    }
}

Group.java

@Entity
@Table(name="groups",schema="sec")
public class Group {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String groupName;   
    // ...
    
    @ManyToMany(fetch = FetchType.LAZY, mappedBy = "groups")
    private Set<User> users = new HashSet<>();
    
    // Method to synchronize bidirectional association
    public void addUser(User user) {
        this.users.add(user);
        user.getGroups().add(this);
    }
    
    public void removeUser(User user) {
        this.users.remove(user);
        user.getGroups().remove(this);
    }
    // ...

    public Set<User> getUsers() {
        return users != null ? users : new HashSet<>();
    }

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

【问题讨论】:

    标签: java spring hibernate spring-mvc jsp


    【解决方案1】:

    由于事务是在服务层启动的,实现这种同步的唯一可能选择是

    1. 服务层或
    2. DAO 层

    服务层定义了一个add(user)方法和一个set(user)方法,两者都调用save(user)方法,因此在包含该方法的DAO层中同步关联是合乎逻辑的。 此外,在 DAO 层中引用EntityManager 似乎更合适。

    @Autowired
    private GroupRepository groupRepository;
    
    @PersistenceContext
    EntityManager em;
    
    // ...
    
    public User save (User user) {
        // If the group already exists, then attach it to the persistence context by  getting a reference to it else attach it by saving
        Set<Group> groups = user.getGroups();
        if (!groups.isEmpty()) {
            Set<Group> managedGroups = groups.stream()
                                                .map(group -> group.getId() == 0L ? groupRepository.save(group) : em.getReference(Group.class, group.getId()))
                                                .collect(Collectors.toSet());
            // Synchronize the bidirectional association
            for (Group group : managedGroups) {
                user.addGroup(group);
            }
        }
        else {
            // Default group
            Optional<Group> result = groupRepository.findByGroupName("USERS");
            if (result.isPresent())
                user.addGroup(result.get());
        }
        // ...
    }
    

    【讨论】:

      猜你喜欢
      • 2015-12-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-07
      • 2019-04-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多