2

I have a DB (Postgres) table with a unique constraint for one column. I have a test marked with @Transactional annotation, that updates that unique column value to a not unique value. I expect that the update operation should fail, but it executes successfully. Moreover, when I get updated object from the database (inside the same transaction), the column value is updated there.

The simplified version of JPA entity:

@Entity
@Table(name = "entities")
public class Entity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    // The unique column
    @Column(name = "name", unique = true)
    @NotNull
    private String name;

    ...
}

The simplified version for the test:

@Test
@Transactional
public void test() {
    Entity firstEntity = new Entity();
    firstEntity.setName("First Entity Name");

    // This just calls corresponding JPA repository .save method
    entityService.create(firstEntity);

    Entity secondEntity = new Entity();
    secondEntity.setName("Second Entity Name");
    entityService.create(secondEntity);

    // Update name to a not unique value
    secondEntity.setName(firstEntity.getName);

    // This calls corresponding JPA repository .save method. 
    // It also catches DataIntegrityViolationException and throws 
    // a more user friendly exception instead
    entityService.update(secondEntity);
}

This code works as I expect, if @Transactional annotation is removed or transaction is committed. I also tried to call EntityManager.flush(), as advised here, but this code throws ConstraintViolationException after resulting data is flushed, so I can't test that my entityService.update method works correctly and throws proper exception.

Please also note that if I try to create a new entry with not unique data in transactional test (not update), then test works as expected - DataIntegrityViolationException is thrown when not unique entity is created.

Could somebody clarify if it is possible to make update scenario work as expected keeping test transactional so I don't need to care about data clean up?

Community
  • 1
  • 1
  • 1
    Your service method is the actually flawed method here. Because if you can catch the exception in your service it means you have a wrong transaction boundary, your service should be `@Transactional` which means the exception would only occur AFTER finishing the execution of the service method. If you really want what you have call `saveAndFlush` (but I would consider that an anti pattern). – M. Deinum Apr 19 '17 at 18:50
  • @M.Deinum, thanks for your response. First, I might oversimplify the example. In reality there is a bunch of calls there and try-catch wraps a transactional service method. Second, I don't think that it influences the fact that the unique column is updated to not unique value, though it should not. Does it? – knesterovich Apr 19 '17 at 20:55
  • The whole point is the fact that a flush is only done as part of the tx commit (or manually). Your transaction spans the whole call now (and as stated imho your test is flawed as you should at least flush after the `create` as without that nothing is persisted yet). Due the the larger tx boundary your test fails. – M. Deinum Apr 20 '17 at 05:50
  • @M.Deinum that makes sense to me, thank you. My only question here is why `create` scenario works then? I.e. create entity1, then create entity2 with the same name within the same transactions. This works as I expect, i.e. entity2 creation fails. Would you mind to clarify that, if you can? – knesterovich Apr 20 '17 at 06:39
  • because a create needs to issue a query to the database to get an id for the entity, this cannot be delayed, whereas an update can. – M. Deinum Apr 20 '17 at 07:28

0 Answers0