web-dev-qa-db-fra.com

Comment conserver les types JSR-310 avec Spring Data JPA?

J'essaie d'utiliser Spring Data JPA 1.8 avec le Java 8 API date/heure JSR-310.

Tout semble fonctionner, jusqu'à ce que j'essaie d'obtenir tous les véhicules entre deux LocalDateTimes. Le nombre d'entités renvoyées ne semble avoir qu'une faible corrélation avec le nombre qu'il devrait.

Entité

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

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

}

Dépôt

@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

}

Dépendances pertinentes

    <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>

Exemple de test d'échec

@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));
    }

}

La première liste contient 2 éléments (devrait être 4). Toutes les autres listes contiennent 0 éléments! Étant donné que la deuxième demande concerne une durée plus longue que la première.

Quelqu'un peut-il me dire ce que je fais mal?


Modifier

Merci @ Oliver Gierke pour la réponse rapide. J'ai pu résoudre le problème en ajoutant "org.springframework.data.jpa.convert.threeten" à la propriété packagesToScan. Maintenant, cela semble fonctionner correctement.

Comme référence, voici ma configuration de base de données (test) de travail.

<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"/>
40
tobijdc

MISE À JOUR: La réponse ci-dessous est valide si vous devez rester sur une version Hibernate <5.0. Hibernate 5.0 prend en charge les types de date/heure JSR-310 persistants prêts à l'emploi. C'est à dire. si vous êtes sur Hibernate 5.0 ou plus récent, la réponse d'Adam est la voie à suivre. Tout le monde, lisez la suite.

La cause profonde de cela chez aucun des fournisseurs JPA largement utilisés ne prend encore en charge les types JSR-310 prêts à l'emploi. Cependant, à partir de Spring Data JPA 1.8.0, nous livrons des convertisseurs JPA 2.0 qui traduiront les types JSR-310 non zonés dans le temps en un Date hérité afin qu'ils puissent être conservés tels quels.

Pour que cela fonctionne, enregistrez simplement org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters comme l'une des classes JPA gérées avec votre fournisseur. Il y a plusieurs façons de le faire: dans une configuration JPA très standard, vous la répertoriez dans votre persistence.xml. Dans une configuration basée sur LocalContainerEntityManagerFactoryBean, vous pouvez simplement ajouter le package de la classe à la propriété packagesToScan. Si vous utilisez Spring Boot en ajoutant la classe au @EntityScan l'annotation fait l'affaire.

Ce dernier est décrit un peu plus en détail dans le article de blog couvrant les nouvelles fonctionnalités du train de sortie de Spring Data nommé Fowler.

48
Oliver Drotbohm

Lorsque vous utilisez Hibernate> = 5.0, <5.2, vous pouvez passer

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

sur votre chemin de classe qui enregistrera automatiquement Types correspondant aux classes JSR310.

(Merci @AbhijitSarkar) Depuis la version 5.2, "le module hibernate-Java8 a été fusionné en hibernate-core et les types Java 8 date/heure sont désormais pris en charge nativement." ( Guide de migration 5.2 )

34
Adam Michalik

Il m'a fallu un certain temps pour comprendre comment utiliser LocalDateTime dans mon entité JPA. J'étais sur la dernière version de Spring boot. Et débogué beaucoup dans les ConversionServices.

La réponse d'Oliver Gierkes m'a beaucoup aidé à arriver à la configuration de travail finale:

Ajoutez Spring-data-jpa 1.8.0 ou supérieur à votre gestion des dépendances

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

Activez @EntityScan pour les convertisseurs Jsr310Jpa + (au moins) votre Application.class

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