无法提交JPA交易;嵌套异常为javax.persistence.RollbackException:提交事务时出错

时间:2018-09-22 20:20:20

标签: spring spring-mvc spring-boot thymeleaf

尝试在数据库中插入用户时出现错误。

我进行了自定义注释,以验证密码是否与确认密码匹配(当字段不匹配时有效),但是当passowrd匹配时,我出现此错误:

enter image description here

这是我的代码,这是我的字段匹配@Annotation:

package mereuta.marian.tennis01.annotations;


import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Constraint(validatedBy = FieldsValueMatchValidator.class)
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldsValueMatch {

    String message() default "Fields values don't match!";

    String field();

    String fieldMatch();

    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

    @Target({ ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @interface List {
        FieldsValueMatch[] value();
    }
}

这是字段验证器:

package mereuta.marian.tennis01.annotations;

import mereuta.marian.tennis01.model.Utilisateur;
import org.springframework.beans.BeanWrapperImpl;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class FieldsValueMatchValidator implements ConstraintValidator<FieldsValueMatch , Object> {

    private String field;
    private String fieldMatch;


    @Override
    public void initialize(FieldsValueMatch fieldsValueMatch) {
        this.field=fieldsValueMatch.field();
        this.fieldMatch=fieldsValueMatch.fieldMatch();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {


        Object fieldValue = new BeanWrapperImpl(value)
                .getPropertyValue(field);
        Object fieldMatchValue = new BeanWrapperImpl(value)
                .getPropertyValue(fieldMatch);

        if (fieldValue != null) {
            return fieldValue.equals(fieldMatchValue);
        } else {
            return fieldMatchValue == null;
        }
    }
}

这是我的实体:

@FieldsValueMatch(field = "password", fieldMatch = "confirmPassword", 
message = "Password do not match!")
@Entity(name = "utilisateurs")
public class Utilisateur {
    @Id @GeneratedValue
    @Column(name = "id_utilisateur")
    private Integer id;
    @NotNull
    @Size(min = 4, max = 255)
    @Column(name = "password")
    private String password;
    @Transient
    @NotNull
    private String confirmPassword;

这是控制器:

@PostMapping("/addUtilisateur")
    public String addUtilisateur(@Valid @ModelAttribute("utilisateur") Utilisateur utilisateur, BindingResult bindingResult, Model model) {


        if (bindingResult.hasErrors() ) {
            model.addAttribute("message", "le mot de passe ne correspond pas");
            return "utilisateur/formRegister";
        }


        utilisateurMetier.creerUtilisateur(utilisateur);

        return "utilisateur/utilisateurAjoute";


    }

最后是View:

<div class="container">
        <form id="contact" th:action="@{addUtilisateur}" method="post" th:object="${utilisateur}">
            <h3>Créer compte</h3>


                <input placeholder="password" type="password" th:field="*{password}" tabindex="2" required/>
                <span class="text text-danger" th:if="${#fields.hasErrors('password')}" th:errors="*{password}"></span>

            </fieldset>


            <fieldset>

                <input placeholder="password" type="password" th:field="*{confirmPassword}" tabindex="2" required/>
                <span class="text text-danger" th:if="${#fields.hasErrors('confirmPassword')}"
                      th:errors="*{confirmPassword}" th:text="${message}"></span>


            </fieldset>

对于自定义注释,我在以下位置找到了一个示例:https://www.baeldung.com/spring-mvc-custom-validator

@Override
    public void creerUtilisateur(Utilisateur utilisateur) {

        Role role;
        float credit = 0;
        boolean actif = true;

        role = roleRepository.getOne(3);


        System.out.println(role);

        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();


        utilisateur.setPassword(encoder.encode(utilisateur.getPassword()));

        utilisateur.setRole(role);
        utilisateur.setCredit(credit);
        utilisateur.setActif(actif);


        utilisateurRepository.save(utilisateur);
    }

预先感谢您的帮助

1 个答案:

答案 0 :(得分:1)

正如已经提到的,ContraintViolationException抛出在'creerUtilisateur'方法内部。因此,当两个字段(密码,confirmPassword)具有相同的值时,在将Utilisateur bean传递给Spring MVC控制器方法(addUtilisateur(@Valid @ModelAttribute("utilisateur")...)时,其验证就可以正常进行。稍后,您对密码进行编码并更改Utilitsateur的“ password”实例变量的值:

utilisateur.setPassword(encoder.encode(utilisateur.getPassword()));

现在,“ password”和“ passwordConfirm”不再相等!当将此实体持久保存在utilisateurRepository.save(utilisateur);中时,JPA将再次对您的实体进行bean验证,然后再将其保存到数据库中(预持久化)。当JPA / Hibernate触发预持久,预更新或预删除生命周期事件时,将自动执行验证。然后抛出ContraintViolationException!

在您的creerUtilisateur方法中,只需为实例变量'password'和'passwordConfirm'设置编码密码,从而确保它们仍通过FieldsValueMatchValidator.isValid(Object value, ConstraintValidatorContext context)中的相等性检查:

 BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
 final String encodedPassword = encoder.encode(utilisateur.getPassword());
 utilisateur.setPassword(encodedPassword);
 utilisateur.setPasswordConfirm(encodedPassword);
 //...
 utilisateurRepository.save(utilisateur);

您还可以尝试自定义JPA的Bean验证行为:

https://www.thoughts-on-java.org/automatically-validate-entities-with-hibernate-validator/

Disable Hibernate validation for Merge/Update