如何使用 Spring Data JPA 持久化 JSR-310 类型?

Posted

技术标签:

【中文标题】如何使用 Spring Data JPA 持久化 JSR-310 类型?【英文标题】:How to persist JSR-310 types with Spring Data JPA? 【发布时间】:2015-06-13 13:54:59 【问题描述】:

我正在尝试将 Spring Data JPA 1.8 与 Java 8 日期/时间 API JSR-310 一起使用。

似乎一切正常,直到我尝试在两个 LocalDateTimes 之间获取所有车辆。 返回的实体数量似乎与它应该返回的数量只有松散的相关性。

实体

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

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


存储库

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


第一个 List 包含 2 个元素(应该是 4 个)。所有其他 List 都包含 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"/>

【问题讨论】:

【参考方案1】:

更新:如果您需要保持在低于 5.0 的 Hibernate 版本,则以下答案是有效的。 Hibernate 5.0 支持开箱即用的持久化 JSR-310 日期/时间类型。 IE。如果您使用的是 Hibernate 5.0 或更新版本,Adam's answer 是您的最佳选择。其他人,请继续阅读。

导致这种情况的根本原因是,目前没有一个广泛使用的 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 注释就可以了。

blog post 中更详细地描述了后者,其中涵盖了名为 Fowler 的 Spring Data 发布系列的新功能

【讨论】:

更正:DataNucleus JPA 支持开箱即用的 java.time 类型,并且从一年前(Java8 出现时)开始支持,我一直在使用该支持。 在 JPA 2.2 中这个 bug 会被修复,这样的 spring 数据会有什么问题吗? 这不是错误,而是事情的运作方式。不幸的是,我无法预测 2018 年;)。 只是给 spring-boot 用户的一个提示。我正在使用springBootVersion = '1.2.7.RELEASE',它是当前最新的,它附带spring-data-jpa:1.7.4.RELEASE - 因此,由于答案,如果您使用spring boot,您需要更新data-jpa 依赖项 感谢您的回答,在 Spring Boot 应用程序中将 Jsr310JpaConverters 添加到 @EntityScan 对我来说是诀窍。但是,当我添加 hibernate envers 进行审计并标记要审计的 JSR310 类型的实体时,它会抛出 Caused by: org.hibernate.MappingException: Could not determine type for: BasicType adapter for AttributeConverter&lt;Instant,Date&gt;, at table:,我猜 envers 不知道这些转换器,有没有一种优雅的方法来解决这个问题?我正在使用 spring boot 1.3.3.RELEASE 和 cloud Brixton.M4。谢谢。【参考方案2】:

当使用Hibernate >=5.0,

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

在您的类路径上,它将自动注册与 JSR310 类对应的Types。

(感谢@AbhijitSarkar)从 5.2 开始,“hibernate-java8 模块已合并到 hibernate-core 中,现在原生支持 Java 8 日期/时间类型。” (Migration Guide 5.2)

【讨论】:

【参考方案3】:

我花了很长时间才弄清楚如何在我的 JPA 实体中使用 LocalDateTime。我使用的是最新的 Spring Boot 版本。并在 ConversionServices 中进行了很多调试。

Oliver Gierkes 的回答帮助我完成了最终的工作设置:

将 Spring-data-jpa 1.8.0 或更高版本添加到您的依赖管理中

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

为 Jsr310JpaConverters +(至少)您的 Application.class 启用 @EntityScan

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

【讨论】:

为我工作。 github 上也有一个关于 java8 特性的示例项目。 github.com/spring-projects/spring-data-examples/tree/master/jpa/… 我无法让它与 Spring-boot 1.5.8 的精选依赖项一起使用。 (它带有hibernate-core-5.0.12.Final.jar)。我必须添加 org.hibernate:hibernate-java8:5.1.0.Final 才能让它工作。 如果mysql数据类型是TIMESTAMP可以用吗?

以上是关于如何使用 Spring Data JPA 持久化 JSR-310 类型?的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot:整合Spring Data JPA

spring data jpa 的简单使用

SpringBoot整合Spring Data JPA多数据源

数据持久化Spring Data JPA 入门

Spring boot之 JPA/Hibernate/Spring Data

集成Spring Data JPA