Search code examples
springhibernatejpaintegration-testingjunit5

Hibernate does not throw exception on duplicate


Hi All I am trying to implement basic CRUD functionality however what bothers me is that CrudRepository is not throwing any exception while saving duplicate Entity, can you tell me what I am missing here?

The test that I cannot pass looks like:

  @Test
    void onDuplicate() {

        ReviewEntity duplicated = savedReviewEntity;

        assertEquals(savedReviewEntity.getId(), duplicated.getId());

        assertThrows(DataIntegrityViolationException.class, () -> {
            reviewRepository.save(duplicated);
        });
    }

This is how Entity looks like:

@NoArgsConstructor
@AllArgsConstructor
@Builder
@Getter
@Setter
@Entity
@Table(name = "reviews", indexes = { @Index(name = "reviews_unique_idx", unique = true, columnList = "movieId,reviewId") })
public class ReviewEntity {

    @Id
    @GeneratedValue
    private Integer id;

    @Version
    private Integer version;

    private Integer movieId;
    private Integer reviewId;
    private String author;
    private String subject;
    private String content;
    private String serviceAddress;
}

I am using CrudRepository

public interface ReviewRepository extends CrudRepository<ReviewEntity, Integer> {

    @Transactional(readOnly = true)
    List<ReviewEntity> findByMovieId(int movieId);
}

I you want to look at whole test class it looks like:

@ExtendWith(SpringExtension.class)
@DataJpaTest
@Transactional(propagation = NOT_SUPPORTED)
class ReviewRepositoryTest {

    public static final int BASE_MOVIE_ID = 1;
    public static final int BASE_REVIEW_ID = 2;
    @Autowired
    ReviewRepository reviewRepository;

    ReviewEntity savedReviewEntity;

    @Autowired
    EntityManager entityManager;

    @BeforeEach
    void setUp() {
        reviewRepository.deleteAll();

        ReviewEntity reviewEntity = ReviewEntity.builder()
                .movieId(BASE_MOVIE_ID)
                .reviewId(BASE_REVIEW_ID)
                .author("Fake Author")
                .content("Fake Content")
                .subject("Fake Subject")
                .serviceAddress("Fake Service Address")
                .build();

        savedReviewEntity = reviewRepository.save(reviewEntity);

        assertReview(reviewEntity, savedReviewEntity);
    }

    @Test
    void create() {
        reviewRepository.deleteAll();

        ReviewEntity reviewEntity = ReviewEntity.builder()
                .movieId(BASE_MOVIE_ID)
                .reviewId(BASE_REVIEW_ID)
                .author("Fake Author")
                .content("Fake Content")
                .subject("Fake Subject")
                .serviceAddress("Fake Service Address")
                .build();

        ReviewEntity saved = reviewRepository.save(reviewEntity);

        assertReview(reviewEntity, saved);
    }

    @Test
    void update() {

        String updatedContent = "Updated Content";

        savedReviewEntity.setContent(updatedContent);

        reviewRepository.save(savedReviewEntity);

        ReviewEntity updated = reviewRepository.findById(savedReviewEntity.getId()).get();

        assertEquals(updatedContent, updated.getContent());

    }


    @Test
    void delete() {

        reviewRepository.delete(savedReviewEntity);

        assertEquals(0, reviewRepository.count());

    }

    @Test
    void findByMovieId() {

        List<ReviewEntity> reviewEntities = reviewRepository.findByMovieId(savedReviewEntity.getMovieId());
        ReviewEntity firstEntity = reviewEntities.get(0);

        assertThat(reviewEntities, hasSize(1));
        assertReview(savedReviewEntity, firstEntity);

    }


    @Test
    void onDuplicate() {

        ReviewEntity duplicated = savedReviewEntity;

        assertEquals(savedReviewEntity.getId(), duplicated.getId());

        assertThrows(DataIntegrityViolationException.class, () -> {
            reviewRepository.save(duplicated);
        });
    }


    @Test
    void optimisticLockVerification() {
        String r1ConcurrentContent = "r1ConcurrentContent";
        String r2ConcurrentContent = "r2ConcurrentContent";

        ReviewEntity r1 = reviewRepository.findById(savedReviewEntity.getId()).get();
        ReviewEntity r2 = reviewRepository.findById(savedReviewEntity.getId()).get();

        r1.setContent(r1ConcurrentContent);
        reviewRepository.save(r1);

        try {
            r2.setContent(r2ConcurrentContent);
            reviewRepository.save(r2);

            fail("Expected an OptimisticLockingFailureException");
        } catch (OptimisticLockingFailureException e) {
            System.out.println("OptimisticLockingFailureException should be throw.");
        }

        ReviewEntity updated = reviewRepository.findById(savedReviewEntity.getId()).get();
        assertEquals(1, (int) updated.getVersion());
        assertEquals(r1ConcurrentContent, updated.getContent());
    }

    private void assertReview(ReviewEntity expected, ReviewEntity actual) {
        assertAll("Executing assertReview(..)", () -> {
            assertEquals(expected.getId(), actual.getId());
            assertEquals(expected.getVersion(), actual.getVersion());
            assertEquals(expected.getMovieId(), actual.getMovieId());
            assertEquals(expected.getReviewId(), actual.getReviewId());
            assertEquals(expected.getContent(), actual.getContent());
            assertEquals(expected.getAuthor(), actual.getAuthor());
            assertEquals(expected.getSubject(), actual.getSubject());
            assertEquals(expected.getServiceAddress(), actual.getServiceAddress());
        });
    }
}

Solution

  • DataIntegrityViolationException is thrown when an insert or update in the database violates any of the integrity constraints. In your method, you are trying to overwrite an entry in the table with itself which does not create any such violation. Hibernate does not create any new entry in the table, it simply updates it(with the same object here since there are no changes).