【问题标题】:Weird java foreign key constraint failure when running unit tests运行单元测试时奇怪的java外键约束失败
【发布时间】:2016-06-21 16:01:59
【问题描述】:

所以我最近因为在尝试对我的服务进行单元测试时遇到的问题而陷入困境。

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

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

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

查看堆栈跟踪后,当我尝试将引用保存到 testclass 中的数据库时,我发现每个测试都由于外键约束而失败。

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);
        }
    }
}

【问题讨论】:

  • 欢迎来到 Stack Overflow!代码很多,如果可能,请尽量缩小到Minimum, Complete, Verifiable Example
  • 旁注:对于单元测试,您可能应该模拟任何数据库调用,并且只使用数据库进行集成/验收测试。
  • 如果您的测试单独运行良好但一起运行时失败,那么这通常表明测试数据存在问题。您是否撤消了每次测试所做的任何更改?具体来说,您是在每次测试之前重置数据库,还是每次测试运行只重置一次?
  • @ScottWeldon 感谢您的反馈,我已经缩小了我的代码部分以使其更易于查看。

标签: java mysql spring hibernate unit-testing


【解决方案1】:

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

@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)

在每个测试类之上。

这解决了我的问题。

【讨论】:

    猜你喜欢
    • 2010-09-21
    • 1970-01-01
    • 1970-01-01
    • 2013-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-26
    • 2013-03-04
    相关资源
    最近更新 更多