Проверка JSR303 не проверяет, если @Id не является @GeneratedValue с Spring Data Jpa и Hibernate

В простом проекте мне нравится тестировать проверку @NotNull (и некоторые другие пользовательские).

Поэтому я написал несколько юнит-тестов, которые ожидают этого: @Test(expect=ValidationException.class

Минимальный мавинизированный пример для воспроизведения проблемы, которую я загрузил на github здесь:

Я понял, что это работает хорошо, если @Id является сгенерированным значением. Но если система выдает @Id, проверка игнорируется.

Эти классы покажут минимальную настройку для воспроизведения проблемы:

Два объекта (один со сгенерированным значением, другой без него):

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class GeneratedId {
    @Id
    @GeneratedValue
    private Long    id;

    @NotNull
    private String  content;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class GivenId {
    @Id
    private Long    id;

    @NotNull
    private String  content;
}

Модульный тест:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath*:/applicationContext.xml")
@Transactional
@ActiveProfiles("embedded")
public class MyEntityTest
{
    @Autowired GeneratedIdService   generatedIdService;

    @Autowired GivenIdService       givenIdService;

    // This test will pass
    @Test(expected = ValidationException.class)
    public void shouldNotAllowNullValues1()
    {
        this.generatedIdService.save(new GeneratedId());
    }

    // This test will fail
    @Test(expected = ValidationException.class)
    public void shouldNotAllowNullValues2()
    {
        this.givenIdService.save(new GivenId(1L, null));
    }
}

Это стандартная служба и репозиторий

public interface GeneratedIdRepository extends JpaRepository<GeneratedId, Long> {
}

public interface GivenIdRepository extends JpaRepository<GivenId, Long> {
}

@Service
public class GeneratedIdService {
    @Autowired GeneratedIdRepository    repository;

    public GeneratedId save(final GeneratedId entity) {
        return this.repository.save(entity);
    }
}

@Service
public class GivenIdService {
    @Autowired GivenIdRepository    repository;

    public GivenId save(final GivenId entity) {
        return this.repository.save(entity);
    }
}

В настоящее время я использую Spring 3.1.4, Spring-Data 1.3.4, Hibernate 4.1.10 и Hibernate-Validator 4.2.0.

Любое предложение, как проверка была пропущена?

Редактировать 1:

Пробовал без ломбока на обеих Сущностях, все равно возникает ошибка.


person d0x    schedule 16.08.2013    source источник


Ответы (2)


Если вы хотите, чтобы поставщик постоянства сбрасывал EntityManager до фиксации или отката транзакции, вам нужно либо сбросить его вручную, либо использовать saveAndFlush(…) для JpaRepository.

Причина этого в том, что в случае с автоматически сгенерированным идентификатором поставщик сохраняемости должен выполнить сброс, чтобы иметь возможность привязать идентификатор к объекту Java. В случае назначенных вручную идентификаторов просто нет необходимости сбрасывать в какой-либо более ранний момент времени, чем в конце транзакции, поэтому поставщик постоянства избегает взаимодействия с базой данных.

Помимо этих технических деталей, я бы сказал, что полагаться на поставщика постоянства для выполнения такого рода проверки в любом случае проблематично с архитектурной точки зрения. Если провайдер обнаруживает нарушение, вы, по сути, передаете недопустимый объект через все виды бизнес-логики. Чтобы убедиться, что вам не нужно кодировать это в обороне (нуль проверяет все и везде), вы можете просто заставить свойство не быть обнуляемым, проверив значение, переданное конструктору или сеттеру. Таким образом, вы, по сути, знаете, что всякий раз, когда вы получаете экземпляр объекта, значение никогда не будет null, независимо от того, был ли вызван какой-то третий фреймворк или какой-то разработчик случайно забыл его вызвать.

person Oliver Drotbohm    schedule 21.08.2013

Принудительный сброс заставляет проверку работать:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath*:/applicationContext.xml")
@Transactional
@ActiveProfiles("embedded")
public class MyEntityTest
{

    @PersistenceContext
    private EntityManager entityManager;

    @Autowired
    GeneratedIdService  generatedIdService;

    @Autowired
    GivenIdService      givenIdService;

    // This test will pass
    @Test(expected = ValidationException.class)
    public void shouldNotAllowNullValues1()
    {
        this.generatedIdService.save(new GeneratedId());
    }

    // This test will fail
    @Test(expected = ValidationException.class)
    public void shouldNotAllowNullValues2()
    {
        this.givenIdService.save(new GivenId(1L, null));
        entityManager.flush();
    }
}

см. также: этот вопрос

person Community    schedule 16.08.2013
comment
Спасибо за предложение. Странно, что нам нужно сбрасывать, только если идентификатор не генерируется автоматически. Если это путь, это похоже на обходной путь или ошибку в структуре. Я буду w8 для некоторых других ответов. В противном случае я собираюсь принять ваше. Спасибо - person d0x; 16.08.2013