wake-up-neo.com

Wie kann ich JSR-310-Typen mit Spring Data JPA beibehalten?

Ich versuche, Spring Data JPA 1.8 mit der Java 8 Date/Time API JSR-310 zu verwenden.

Alles scheint zu funktionieren, bis ich versuche, alle Fahrzeuge zwischen zwei LocalDateTimes zu bekommen. Die Anzahl der zurückgegebenen Entitäten scheint nur eine lose Korrelation mit der Anzahl zu haben, die sie haben sollte.

Entität

@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

}

Relevante Abhängigkeiten

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

Fehlerhaftes Testbeispiel

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

}

Die erste Liste enthält 2 Elemente (sollte 4 sein). Alle anderen Listen enthalten 0 Elemente! Angesichts der zweiten Anforderung ist eine größere Zeitspanne als die erste erforderlich.

Kann mir jemand sagen, was ich falsch mache?


Bearbeiten

Vielen Dank @ Oliver Gierke für die schnelle Antwort. Ich konnte das Problem beheben, indem ich "org.springframework.data.jpa.convert.threeten" zur Eigenschaft packagesToScan hinzufügte. Jetzt scheint es richtig zu funktionieren.

Als Referenz dient hier meine funktionierende datenbankbezogene (Test-) Konfiguration.

<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

UPDATE: Die Antwort unten ist gültig, wenn Sie in einer Ruhezustand-Version <5.0 bleiben müssen. Hibernate 5.0 unterstützt die sofortige Beibehaltung von JSR-310-Datums- und Zeittypen. Das heißt Wenn Sie sich im Ruhezustand 5.0 oder neuer befinden, ist Adams Antwort der richtige Weg. Alle anderen, lies weiter.

Die Hauptursache dafür ist, dass noch keiner der weit verbreiteten JPA-Anbieter JSR-310-Typen von Anfang an unterstützt. Ab Spring Data JPA 1.8.0 werden jedoch JPA 2.0-Konverter ausgeliefert, die JSR-310-Typen, die keine Zeitzonen haben, in ein älteres Date konvertieren, sodass sie unverändert beibehalten werden können.

Um dies zum Laufen zu bringen, registriere einfach org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters als eine der verwalteten JPA-Klassen bei Ihrem Provider. Es gibt zwei Möglichkeiten, dies zu tun: In einem sehr standardmäßigen JPA-Setup schreiben Sie es in Ihr persistence.xml. In einem LocalContainerEntityManagerFactoryBean-basierten Setup können Sie das Paket der Klasse einfach zur Eigenschaft packagesToScan hinzufügen. Wenn Sie Spring Boot verwenden, fügen Sie die Klasse zum @EntityScan Annotation macht den Trick.

Letzteres wird ausführlicher im Blogpost beschrieben, der die neuen Funktionen des Spring Data Release-Zugs mit dem Namen Fowler ships behandelt

48
Oliver Drotbohm

Wenn Sie den Ruhezustand> = 5.0, <5.2 verwenden, können Sie vorbeischauen

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

in Ihrem Klassenpfad, der automatisch Types registriert, die den JSR310-Klassen entsprechen.

(Dank an @AbhijitSarkar) Seit 5.2 "wurde das Hibernate-Java8-Modul in Hibernate-Core zusammengeführt und die Java 8 Datums-/Zeittypen werden jetzt nativ unterstützt." ( Migration Leitfaden 5.2 )

34
Adam Michalik

Ich habe eine ganze Weile gebraucht, um herauszufinden, wie ich LocalDateTime in meiner JPA-Entität verwenden kann. Ich war auf der neuesten Spring Boot-Version. Und viele Fehler in den ConversionServices behoben.

Die Antwort von Oliver Gierkes hat mir sehr geholfen, zum endgültigen Arbeitsaufbau zu gelangen:

Fügen Sie Ihrem Abhängigkeitsmanagement Spring-data-jpa 1.8.0 oder höher hinz

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

Aktivieren Sie @EntityScan für die Jsr310JpaConverter + (mindestens) Ihre Application.class

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