如何使用 Spring Data JPA 保留 JSR-310 类型?
How to persist JSR-310 types with Spring Data JPA?
我正在尝试将 Spring Data JPA 1.8 与 Java 8 Date/Time 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个)。所有其他列表包含 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"/>
更新:如果您需要继续使用 < 5.0 的 Hibernate 版本,则以下答案有效。 Hibernate 5.0 开箱即用地支持持久化 JSR-310 date/time 类型。 IE。如果您使用的是 Hibernate 5.0 或更新版本, 是最佳选择。其他人,请继续阅读。
none 的根本原因是广泛使用的 JPA 提供程序实际上支持开箱即用的 JSR-310 类型。但是,从 Spring Data JPA 1.8.0 开始,我们发布了 JPA 2.0 转换器,它将非时区 JSR-310 类型转换为遗留 Date
,以便它们可以按原样保留。
要使其正常工作,只需向您的提供商注册 org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters
作为托管 JPA class 之一。有几种方法可以做到这一点:在非常标准的 JPA 设置中,您将其列在 persistence.xml
中。在基于 LocalContainerEntityManagerFactoryBean
的设置中,您只需将 class 的包添加到 packagesToScan
属性。如果您使用 Spring 启动,将 class 添加到 @EntityScan
注释中就可以了。
后者在 blog post 中有更详细的描述,涵盖新功能 Spring 名为 Fowler ships 的数据发布列车
我花了很长时间才弄清楚如何在我的 JPA 实体中使用 LocalDateTime
。我使用的是最新的 Spring 引导版本。并在 ConversionServices 中进行了大量调试。
Oliver Gierkes 的回答对我进行最终的工作设置帮助很大:
将 Spring-data-jpa 1.8.0 或更高版本添加到您的依赖管理中
compile("org.springframework.data:spring-data-jpa:1.8.2.RELEASE")
为 Jsr310JpaConverters 启用 @EntityScan +(至少)您的 Application.class
@EntityScan(
basePackageClasses = { Application.class, Jsr310JpaConverters.class }
)
@SpringBootApplication
class Application { … }
当使用Hibernate >=5.0, <5.2时,可以drop in
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-java8</artifactId>
<version>${hibernate.version}</version>
</dependency>
在您的类路径上,它将自动注册对应于 JSR310 类.
的 Type
s
(感谢@AbhijitSarkar)从 5.2 开始,"the hibernate-java8 module has been merged into hibernate-core and the Java 8 date/time types are now natively supported."(Migration Guide 5.2)
我正在尝试将 Spring Data JPA 1.8 与 Java 8 Date/Time 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个)。所有其他列表包含 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"/>
更新:如果您需要继续使用 < 5.0 的 Hibernate 版本,则以下答案有效。 Hibernate 5.0 开箱即用地支持持久化 JSR-310 date/time 类型。 IE。如果您使用的是 Hibernate 5.0 或更新版本,
none 的根本原因是广泛使用的 JPA 提供程序实际上支持开箱即用的 JSR-310 类型。但是,从 Spring Data JPA 1.8.0 开始,我们发布了 JPA 2.0 转换器,它将非时区 JSR-310 类型转换为遗留 Date
,以便它们可以按原样保留。
要使其正常工作,只需向您的提供商注册 org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters
作为托管 JPA class 之一。有几种方法可以做到这一点:在非常标准的 JPA 设置中,您将其列在 persistence.xml
中。在基于 LocalContainerEntityManagerFactoryBean
的设置中,您只需将 class 的包添加到 packagesToScan
属性。如果您使用 Spring 启动,将 class 添加到 @EntityScan
注释中就可以了。
后者在 blog post 中有更详细的描述,涵盖新功能 Spring 名为 Fowler ships 的数据发布列车
我花了很长时间才弄清楚如何在我的 JPA 实体中使用 LocalDateTime
。我使用的是最新的 Spring 引导版本。并在 ConversionServices 中进行了大量调试。
Oliver Gierkes 的回答对我进行最终的工作设置帮助很大:
将 Spring-data-jpa 1.8.0 或更高版本添加到您的依赖管理中
compile("org.springframework.data:spring-data-jpa:1.8.2.RELEASE")
为 Jsr310JpaConverters 启用 @EntityScan +(至少)您的 Application.class
@EntityScan(
basePackageClasses = { Application.class, Jsr310JpaConverters.class }
)
@SpringBootApplication
class Application { … }
当使用Hibernate >=5.0, <5.2时,可以drop in
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-java8</artifactId>
<version>${hibernate.version}</version>
</dependency>
在您的类路径上,它将自动注册对应于 JSR310 类.
的Type
s
(感谢@AbhijitSarkar)从 5.2 开始,"the hibernate-java8 module has been merged into hibernate-core and the Java 8 date/time types are now natively supported."(Migration Guide 5.2)