Повторяющиеся записи гибернации, когда @Version имеет тип Integer, но не при использовании примитивного типа int - PullRequest
1 голос
/ 08 февраля 2020

Учитывая сущность 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 имеет значение?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...