Учитывая сущность JPA, я заметил интересную проблему в моем коде:
- , когда поле
@Version
имеет тип Integer
, записи дублируются - когда поле
@Version
имеет примитивный тип int
, записи хранятся правильно
Я воспроизвел проблему в иерархии Parent / Child с двунаправленным отношением (чтобы уменьшить многословность, я использовался Lombok) с использованием Spring Data в Hibernate 4.3.11.Final с базой данных HSQLDB 2.5.0 в памяти (но проблема также возникает при использовании базы данных Hibernate 5.4.10.Final и Oracle 12).
Дочерний класс:
@Entity
@SequenceGenerator(name = "s1", allocationSize = 1, sequenceName = "S1db")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@ToString(exclude = "parent")
class Child {
@Id
@Column
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "s1")
private Long id;
@Column
private String name;
@Version
@Column
private Integer version;
@ManyToOne
@JoinColumn
private Parent parent;
}
Родительский класс:
@Entity
@Getter
@Setter
@NoArgsConstructor
@SequenceGenerator(name = "s2", allocationSize = 1, sequenceName = "s2db")
@ToString
class Parent {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "s2")
@Column
private Long id;
@Column(nullable = false)
private String name;
@OneToMany(targetEntity = Child.class,
mappedBy = "parent",
cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY
)
@OnDelete(action = OnDeleteAction.CASCADE)
private List<Child> children = new ArrayList<>();
}
Репозитории и конфигурация Spring:
@Repository
@javax.transaction.Transactional
interface ChildRepository extends JpaRepository<Child, Long> {}
@javax.transaction.Transactional
@Repository
interface ParentRepository extends JpaRepository<Parent, Long> {}
@Configuration
@EnableJpaRepositories(basePackageClasses = ParentRepository.class)
@EntityScan(basePackageClasses = Parent.class)
class ConfigClass {
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:mem:spring;sql.syntax_ora=true");
dataSource.setUsername("sa");
dataSource.setPassword("");
final Properties properties = new Properties();
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
properties.setProperty("hibernate.hbm2ddl.auto", "create");
properties.setProperty("hibernate.show_sql", "true");
properties.setProperty("hibernate.format_sql", "true");
properties.setProperty("hibernate.use_sql_comments", "true");
properties.setProperty("properties.hibernate.jdbc.batch_size", "10");
final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan(Parent.class.getPackage().getName());
final JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(properties);
return em;
}
}
И, наконец, тестовый класс:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=ConfigClass.class)
@Transactional
public class ParentRepositoryTest {
@Autowired
private ParentRepository parentRepository;
@Autowired
private ChildRepository childRepository;
@Test
public void test() {
// cleanup database
childRepository.deleteAll();
parentRepository.deleteAll();
// Given a parent without child
Parent p = new Parent();
p.setName("Alice");
p = parentRepository.saveAndFlush(p);
// When I add 3 children
for (int i=0; i<3; i++) {
Child c = new Child();
c.setParent(p);
c.setName("child " + i);
c.setVersion(0); // <===== this is the main issue
// When Child.version is an int => will persist 3 elements
// When Child.version is an Integer => will persist 6 elements
p.getChildren().add(c);
}
for (Child c : p.getChildren()) {
childRepository.save(c);
}
// Then the database contains 3 children
Assertions.assertThat(childRepository.findAll()).hasSize(3); // Ouch! will persist 6 children
}
}
Вывод таков:
- Когда
Child.version
имеет примитивный тип int
, тестовый проход. - Когда
Child.version
имеет тип Integer
, тест не пройден.
Хотя решить проблему легко (мне просто нужно установить тип на int
), мне интересно, почему это имеет значение.
Может кто-нибудь объяснить мне, почему тип поля @Version
имеет значение?