Подтвердить что ты не робот

Как сохранить типы JSR-310 с помощью Spring данных JPA?

Я пытаюсь использовать Spring Data JPA 1.8 с API-интерфейсом Java/Дата/время Java JSR-310.

Кажется, все работает, пока я не попытаюсь получить все Транспортные средства между двумя LocalDateTimes. Количество возвращаемых объектов, по-видимому, имеет только некорректную корреляцию с числом, которое оно должно.

Entity

@Repository
public interface VehicleRepository extends JpaRepository<Vehicle, Long> {

    List<Vehicle> findByDateTimeBetween(LocalDateTime begin, LocalDateTime end);

}

Repository

@Entity
@Table(name = "VEHICLE")
public class Vehicle implements Serializable {

  private static final long serialVersionUID = 1L;

  @Id
  @Column(name = "IDX", nullable = false, unique = true)
  @GeneratedValue(strategy = GenerationType.AUTO)
  private long vehicleId;

  @Column(name = "DATE_TIME", nullable = false)
  private LocalDateTime dateTime = LocalDateTime.now();

  // Getters and Setters

}

Соответствующие зависимости

    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-jpa</artifactId>
        <version>1.8.0.RELEASE</version>
    </dependency>
    <dependency> 
        <groupId>org.springframework</groupId> 
        <artifactId>spring-aspects</artifactId> 
        <version>4.0.9.RELEASE</version> 
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>4.3.8.Final</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>4.3.8.Final</version>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.186</version>
    </dependency>
    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
        <version>2.3.5</version>
    </dependency>

Отказоустойчивый тестовый пример

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath*:applicationContextTesting.xml"})
@Transactional
public class VehicleRepositoryTest {

    @Autowired
    private VehicleRepository vehicleRepository;

    @Test
    public void testVehicleBetween() {
        // Given
        Vehicle vehicleMarch1Twelve = new Vehicle();
        Vehicle vehicleMarch1Eighteen = new Vehicle();
        Vehicle vehicleMarch2Five = new Vehicle();
        Vehicle vehicleMarch2Six = new Vehicle();
        LocalDateTime march1Twelve = LocalDateTime.of(2015, Month.MARCH, 1, 12, 0);
        LocalDateTime march1Eighteen = LocalDateTime.of(2015, Month.MARCH, 1, 18, 0);
        LocalDateTime march2Five = LocalDateTime.of(2015, Month.MARCH, 2, 5, 0);
        LocalDateTime march2Six = LocalDateTime.of(2015, Month.MARCH, 2, 6, 0);
        vehicleMarch1Twelve.setDateTime(march1Twelve);
        vehicleMarch1Eighteen.setDateTime(march1Eighteen);
        vehicleMarch2Five.setDateTime(march2Five);
        vehicleMarch2Six.setDateTime(march2Six);

        vehicleRepository.save(vehicleMarch1Twelve);
        vehicleRepository.save(vehicleMarch1Eighteen);
        vehicleRepository.save(vehicleMarch2Five);
        vehicleRepository.save(vehicleMarch2Six);
        vehicleRepository.flush();

        // when
        List<Vehicle> allVehicles = vehicleRepository.findByDateTimeBetween(
            march1Twelve,
            march2Six);
        List<Vehicle> allVehicles2 = vehicleRepository.findByDateTimeBetween(
            march1Twelve.minusMinutes(2),
            march2Six.plusMinutes(2));
        List<Vehicle> threeVehicles = vehicleRepository.findByDateTimeBetween(
            march1Twelve.plusMinutes(2),
            march2Six);
        List<Vehicle> twoVehicles = vehicleRepository.findByDateTimeBetween(
            march1Twelve.plusMinutes(2),
            march2Six.minusMinutes(2));
        List<Vehicle> oneVehicles = vehicleRepository.findByDateTimeBetween(
            march1Twelve.plusMinutes(2),
            march2Six.minusHours(3));

        // then
        Assert.assertTrue("size was " + allVehicles.size(), allVehicles.size() == 4);
        Assert.assertTrue("size was " + allVehicles2.size(), allVehicles2.size() == 4);
        Assert.assertTrue("size was " + threeVehicles.size(), threeVehicles.size() == 3);
        Assert.assertTrue("size was " + twoVehicles.size(), twoVehicles.size() == 2);
        Assert.assertTrue("size was " + oneVehicles.size(), oneVehicles.size() == 1);
        Assert.assertTrue(oneVehicles.get(0).getDateTime().equals(march1Eighteen));
    }

}

Первый список содержит 2 элемента (должно быть 4). Все остальные списки содержат элементы 0! Учитывая, что второй запрос рассчитан на большее время, чем первое.

Может кто-нибудь сказать мне, что я делаю неправильно?


Изменить

Благодарим вас за @Oliver Gierke за быстрый ответ. Я смог исправить проблему, добавив "org.springframework.data.jpa.convert.threeten" в свойство packagesToScan. Теперь он работает правильно.

В качестве ссылки здесь приведена моя рабочая конфигурация, связанная с базой данных (тестирование).

<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
    <property name="driverClassName" value="org.h2.Driver"/>
    <property name="jdbcUrl" value="jdbc:h2:mem:testing"/>
    <property name="username" value="interface"/>
    <property name="password" value=""/>
    <property name="connectionTestQuery" value="SELECT 1" />
</bean>

<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
    <constructor-arg index="0" ref="hikariConfig"/>
</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>

<tx:annotation-driven/>

<bean id="hibernateJpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter"/>
    <property name="packagesToScan" value="com.company.project.domain,org.springframework.data.jpa.convert.threeten"/>
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.H2Dialect</prop>
            <prop key="hibernate.hbm2ddl.auto">update</prop>
        </props>
    </property>
</bean>

<jpa:repositories base-package="com.company.project.dao" transaction-manager-ref="transactionManager"
                  entity-manager-factory-ref="entityManagerFactory"/>
4b9b3361

Ответ 1

UPDATE: ответ ниже действителен, если вам нужно остаться в версии спящего режима < 5.0. Hibernate 5.0 поддерживает сохраняющиеся типы даты/времени JSR-310 из коробки. То есть если вы находитесь в Hibernate 5.0 или новее, ответ Адама - это путь. Все остальные читают.

Основная причина этого ни для одного из широко используемых поставщиков JPA фактически не поддерживает типы JSR-310. Тем не менее, по состоянию на Spring Data JPA 1.8.0 мы отправляем конвертеры JPA 2.0, которые преобразуют типы времени JSR-310, не относящиеся к времени, в наследие Date, чтобы они сохранялись как есть.

Чтобы получить эту работу, просто зарегистрируйте org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters как один из управляемых классов JPA с вашим провайдером. Вот несколько способов сделать это: в стандартной стандартной настройке JPA вы перечислите ее в своем persistence.xml. В настройке LocalContainerEntityManagerFactoryBean вы можете просто добавить пакет класса к свойству packagesToScan. Если вы используете Spring Boot, добавляя класс к аннотации @EntityScan, трюк.

Последнее описано более подробно в блоге посвященном новым функциям Spring поезда с данными по имени Fowler

Ответ 2

При использовании Hibernate 5.0+ вы можете войти в

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-java8</artifactId>
    <version>${hibernate.version}</version>
</dependency>

в вашем пути к классам, который автоматически зарегистрирует Type, соответствующий классам JSR310.

Ответ 3

Мне потребовалось некоторое время, чтобы выяснить, как использовать LocalDateTime в моей сущности JPA. Я был в последней версии загрузки Spring. И отлаживается много в ConversionServices.

Ответ Oliver Gierkes помог мне в достижении окончательной рабочей настройки:

Добавить Spring -data-jpa 1.8.0 или выше в управление зависимостями

compile("org.springframework.data:spring-data-jpa:1.8.2.RELEASE")

Включить @EntityScan для Jsr310JpaConverters + (по крайней мере) ваш Application.class

@EntityScan(
  basePackageClasses = { Application.class, Jsr310JpaConverters.class }
)
@SpringBootApplication
class Application { … }