运行单元测试时奇怪的java外键约束失败

时间:2016-06-21 16:02:00

标签: java mysql spring hibernate unit-testing

因为当我尝试对我的服务进行单元测试时遇到问题,所以我最近一直撞到了墙上。

我有一个User,它与Product,Customer和Quotation类有多个单向关系。 我使用SpringBoot和Spring JPA存储库来使用Hibernate作为ORM对MySQL数据库执行操作。

我也有不同的服务调用存储库来对数据库执行这些操作,正是这些我试图进行单元测试。

当我单独运行单元测试类时,它们都可以成功运行,但是当我一起运行它们时,QuotationService测试完全失败。但值得注意的是,它们总是在CustomerService测试之后运行。

在查看堆栈跟踪之后,我发现当我尝试将报价保存到测试类中的数据库时,每个测试都因为外键约束而失败。

Caused by: java.sql.SQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`kmobeheerdb`.`quotation`, CONSTRAINT `FK_fq5x6je1sicnskcj35yqhp85g` FOREIGN KEY (`quotation_id`) REFERENCES `user` (`user_id`))
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:683)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:663)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:653)
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:108)
at com.mysql.cj.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2061)
at com.mysql.cj.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1819)
at com.mysql.cj.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2033)
at com.mysql.cj.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:1969)
at com.mysql.cj.jdbc.PreparedStatement.executeLargeUpdate(PreparedStatement.java:4953)
at com.mysql.cj.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1954)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:208)
... 105 more

当我尝试保存新报价时,部分测试总是失败的是@Before部分。

@Autowired private QuotationService quotationService;
    @Autowired private UserService userService;

    private User user;
    private Quotation quotation;

    @Before
    public void setup() throws UserServiceException, QuotationServiceException {
        String userEmail = "tzestermail@mail.com";
        String userName = "testername";

        User newUser = new User(userEmail, userName);
        user = userService.register(newUser);

        //Tests fail on the createQuotation statement beneath
        Quotation newQuotation = new Quotation();
        quotation = quotationService.createQuotation(newQuotation, user.getUserId()); 

        user = userService.getUser(user.getUserId());

        assertNotNull(user);
        assertNotNull(quotation);
        assertEquals(1, user.getQuotations().size());
    }

我搜索了很多东西试图解决这个问题,包括使关系双向,但没有真正起作用,错误仍然存​​在。

供参考:

用户类:

@Entity
@Table(name = "user", uniqueConstraints = {
        @UniqueConstraint(name = "UX_user_email", columnNames = {"email"})
})
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "user_id", nullable = false)
    private int userId;
    private String email;
    private String password;

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "productId", orphanRemoval = true)
    private Set<Product> products;

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "quotationId", orphanRemoval = true)
    private Set<Quotation> quotations;

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "customerId", orphanRemoval = true)
    private Set<Customer> customers;

    public User(String email, String password) {
        this.email = email;
        this.password = password;
    }

    public User() {
    }

    public void addQuotation(Quotation quotation){
        if(quotation != null){
            if(quotations == null){
                quotations = new HashSet<>();
            }
            quotations.add(quotation);
        }
    }

    public void removeQuotation(Quotation quotation){
        if(quotation != null && quotations.contains(quotation)){
            quotations.remove(quotation);
        }
    }

    //Omitted getters and setters
}

报价类:

@Entity
public class Quotation {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "quotation_id", nullable = false)
    private int quotationId;
    @OneToMany(fetch = FetchType.EAGER, mappedBy = "productId", cascade = CascadeType.PERSIST)
    private Set<Product> products;

    private double routeTotal;
    private double total;
    private String memo;

    //Omitted getters and setters
}

报价服务:

@Transactional(rollbackFor = QuotationServiceException.class)
@Service
public class QuotationServiceImpl implements QuotationService {
    private Logger logger = LogManager.getLogger();
    @Autowired private ExceptionStrings exceptionStrings;
    @Autowired private UserService userService;
    @Autowired private QuotationRepository quotationRepository;

    @Override
    public Quotation createQuotation(Quotation quotation, int userId) throws QuotationServiceException {
        if (quotation == null || userId == 0 || userService.getUser(userId) == null) {
            logger.error(exceptionStrings.createQuotationInputError);
            throw new QuotationServiceException(exceptionStrings.createQuotationInputError);
        }
        try {
            User user = userService.getUser(userId);
            user.addQuotation(quotation);
            return quotationRepository.save(quotation);
        } catch (DataIntegrityViolationException e) {
            logger.error(exceptionStrings.createQuotationError);
            throw new QuotationServiceException(exceptionStrings.createQuotationError, e);
        }
    }
}

1 个答案:

答案 0 :(得分:0)

我很确定我找到了问题的解决方案。正如斯科特威尔登建议我检查我的数据库是否每次运行都重置。我的hibernate被配置为create-drop,但似乎我还需要通过放置来强制Spring配置重新加载

@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)

在每个测试类之上。

这解决了我的问题。