简体   繁体   中英

Spring Data JPA update makes some fields null

I have a simple scenario where there are entities:

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Data
public abstract class AuditModel implements Serializable {

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "created_at", nullable = false, updatable = false)
    @CreatedDate
    private Date createdAt;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "updated_at", nullable = false)
    @LastModifiedDate
    private Date updatedAt;

    @Column(name = "created_by")
    @CreatedBy
    private String createdBy;

    @Column(name = "modified_by")
    @LastModifiedBy
    private String modifiedBy;

}

User.java

@Data
@Entity(name = "users")
@NoArgsConstructor
@RequiredArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class User extends AuditModel{

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="id", unique = true, nullable = false)
    private Integer userId;

    @NonNull
    private String userFirstName;

    @NonNull
    private String userLastName;

    @Email
    @NonNull
    private String userEmail;

    @NonNull
    private String userPassword;

    @Column(name = "enabled")
    private boolean userEnabled;

    @NonNull
    private String userFbLink;

    @NonNull
    private String userTwLink;

    @NonNull
    private String userLiLink;

    @NonNull
    @ManyToMany(fetch = FetchType.EAGER, cascade=CascadeType.MERGE)
    @JoinTable(
            name="user_role",
            joinColumns={@JoinColumn(name="USER_ID", referencedColumnName="ID")},
            inverseJoinColumns={@JoinColumn(name="ROLE_ID", referencedColumnName="ID")})
    private List<Role> roles;

}

A role entity is as simple as: Role.java

@Data
@Entity(name = "roles")
@NoArgsConstructor
@RequiredArgsConstructor
@EqualsAndHashCode(callSuper = false, exclude = {"userList"})
public class Role  extends AuditModel{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="id", unique = true, nullable = false)
    private Integer roleId;

    @NonNull
    @Column(nullable = false, unique = true)
    @NotEmpty
    private String roleName;

    @ManyToMany(mappedBy = "roles")
    private List<User> userList;

}

Spring Security configuration is as:

SecurityConfiguration.java

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    private final UserDetailsServiceImpl userDetailsService;

    @Autowired
    public SecurityConfiguration(UserDetailsServiceImpl userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/assets/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        String [] permissibleResources = {"/register**", "/login**","/post_register", "/confirm**", "/reset", "/api/**"};
        http.csrf().disable()
                .authorizeRequests()
                    .antMatchers(permissibleResources).permitAll()
                        // START Add missing configs
                        .anyRequest()
                        .authenticated()
                        // END Add missing configs
                .and()
                    .formLogin()
                    .loginPage("/login")
                        // username password
                        .usernameParameter("username")
                        .passwordParameter("password")
                        // success and failure handlers
                    .successHandler(appAuthenticationSuccessHandler())
                    .failureHandler(appAuthenticationFailureHandler())
                    .permitAll()
                .and()
                    .logout()
                        .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                        .logoutSuccessUrl("/login?logout")
                        .invalidateHttpSession(true)
                        .clearAuthentication(true)
                .permitAll()
                .and()
                    .exceptionHandling().accessDeniedHandler(accessDeniedHandler())
                .and()
                    .headers()
                    .defaultsDisabled()
                    .cacheControl()
        ;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .authenticationProvider(daoAuthenticationProvider());
    }

    @Bean
    public AccessDeniedHandler accessDeniedHandler(){
        return new AppAccessDeniedHandler();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider(){
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(bCryptPasswordEncoder());

        return provider;
    }

    @Bean
    public PasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder(5);
    }

    // Auth success handler
    @Bean
    public AuthenticationSuccessHandler appAuthenticationSuccessHandler(){
        return new AppAuthenticationSuccessHandler();
    }

    // Auth failure handler
    @Bean
    public AuthenticationFailureHandler appAuthenticationFailureHandler() {

        ExceptionMappingAuthenticationFailureHandler failureHandler = new ExceptionMappingAuthenticationFailureHandler();
        Map<String, String> failureUrlMap = new HashMap<>();
        failureUrlMap.put(BadCredentialsException.class.getName(), AppAuthenticationFailureHandler.BAD_CREDENTIALS_URL);
        failureUrlMap.put(AccountExpiredException.class.getName(), AppAuthenticationFailureHandler.EXPIRED_URL);
        failureUrlMap.put(LockedException.class.getName(), AppAuthenticationFailureHandler.LOCKED_URL);
        failureUrlMap.put(DisabledException.class.getName(), AppAuthenticationFailureHandler.DISABLED_URL);
        failureHandler.setExceptionMappings(failureUrlMap);

        return failureHandler;

    }

}

everything was working fine, but whine I update specific fields of the user entity with the following form:

<form th:action="@{/user/__${user.userId}__/update}" th:object="${user}" method="post">
    <h5>Personal</h5>
    <hr>          
    <input type="hidden" th:value="${user.userId}" th:field="*{userId}" /> <br>       
    <input type="text" th:field="*{userFirstName}" th:value="${user.userFirstName}" required /> <br>
    <input type="text" th:field="*{userLastName}" th:value="${user.userLastName}" required /><br>
    <h5>Social</h5>
    <hr>
    <input type="url" th:field="*{userFbLink}" th:value="${user.userFbLink}" /><br>
    <input type="url" th:field="*{userTwLink}" th:value="${user.userTwLink}" /><br>        
    <input type="url" th:field="*{userLiLink}" th:value="${user.userLiLink}" /><br>
    <button name="save-user" type="submit">Save</button>
    </div>
</form>

through UserController.java given bellow:

@Controller
@RequestMapping(value = "/user")
public class UserController {

    private final UserService userService;

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

    @PreAuthorize(value = "hasAuthority('USER')")
    @GetMapping(value = "/{id}/edit")
    public String editBio(
            @PathVariable("id") Integer id,
            Model model
    ){
        User user = userService.findById(id);
        String pageTitle = "Edit User " + user.getUserFirstName() ;
        model.addAttribute("pageTitle", pageTitle);
        model.addAttribute("user", user);
        return "edit_user";
    }

    @PreAuthorize(value = "hasAuthority('USER')")
    @PostMapping(value = "/{id}/update")
    public String updateBio(
            @PathVariable("id") Integer id,
            @ModelAttribute("user") @Valid User user,
            BindingResult result
    ){
        if (result.hasErrors()) {
            return "edit_user";
        }
        userService.save(user);
        return "redirect:/user/" + id;
    }
}

it performs query as:

Hibernate: update users set created_by=?, modified_by=?, user_first_name=?, user_last_name=?, updated_at=?, enabled=?, user_fb_link=?, user_li_link=?, user_password=?, user_tw_link=?, where id=?

and

Hibernate: delete from user_role where user_id=?

Inserts new data only in the fields mentioned in the update form, all other fields become null , even user password becomes null and enabled field becomes false . User role is also deleted. When I log out and try to log back in, I get error disabled . A couple of days ago it was working fine without any weird behaviour. I searched a lot and found @DynamicUpdate with Spring Data JPA , but it never helped me. Anyone, please help me in this case.

When you are saving User user all the data of the current user object will update. If any data is not in update form that means that data is null and for boolean it's false . So fetch the data from the database using id then set the new value in fetch user then save.

@PostMapping(value = "/{id}/update")
public String updateBio(
        @PathVariable("id") Integer id,
        @ModelAttribute("user") @Valid User updatedUser,
        BindingResult result
){
    if (result.hasErrors()) {
        return "edit_user";
    }
    User user = userService.findById(updatedUser.getUserId());
    // Set the updated data from updatedUser to user 
    user.setUserFirstName(updatedUser.getUserFirstName()); // set other updated field like this
    // Now save
    userService.save(user);
    return "redirect:/user/" + id;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM