SDN:4 向 Converter 注入值失败

SDN:4 Value injection into Converter fails

我为图表编写了自定义转换器 属性,如下所示。

实体class

@NodeEntity(label = "Person")
public class Person extends AbstractEntity {

  @Property(name = "accessCount")
  private Long accessCount;


  @Property(name = "lastAccessDate")
  @Convert(LocalDateTimeConverter.class)
  private LocalDateTime lastAccessDate;


  public Long getAccessCount() {
    return accessCount;
  }

  public void setAccessCount(final Long accessCount) {
    this.accessCount = accessCount;
  }

  public LocalDateTime getLastAccessDate() {
    return lastAccessDate;
  }

  public void setLastAccessDate(final LocalDateTime lastAccessDate) {
    this.lastAccessDate = lastAccessDate;
  }

}

转换器

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

import org.apache.commons.lang3.StringUtils;
import org.neo4j.ogm.typeconversion.AttributeConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class LocalDateTimeConverter implements AttributeConverter<LocalDateTime, String> {

  private static final Logger LOG = LoggerFactory.getLogger(LocalDateTimeConverter.class);

  @Value("${neo4j.dateTime.format:yyyy-MM-dd HH:mm:ss.SSS}")
  private String dateTimeFormat;

  @Override
  public String toGraphProperty(final LocalDateTime value) {
    LOG.debug("Converting local date time: {} to string ...", value);
    if (value == null) {
      return "";
    }
    return String.valueOf(value.format(getDateTimeFormatter()));
  }

  @Override
  public LocalDateTime toEntityAttribute(final String value) {
    LOG.debug("Converting string: {} to local date time ...", value);
    if (StringUtils.isBlank(value)) {
      return null;
    }
    return LocalDateTime.parse(value, getDateTimeFormatter());
  }

  public DateTimeFormatter getDateTimeFormatter() {
    return DateTimeFormatter.ofPattern(dateTimeFormat);
  }
}

单元测试通过

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestContextConfiguration.class)
@DirtiesContext
@TestExecutionListeners(inheritListeners = false, listeners = { DataSourceDependencyInjectionTestExecutionListener.class })
public class LocalDateTimeConverterTest {

  public static final String DATE_TIME_VALUE = "2015-06-22 13:05:04.546";

  @Autowired
  protected LocalDateTimeConverter localDateTimeConverter;

  @Test
  public void should_get_date_time_formatter() {
    final DateTimeFormatter dateTimeFormatter = localDateTimeConverter.getDateTimeFormatter();
    assertNotNull(dateTimeFormatter);
  }

  @Test
  public void should_convert_local_date_time_property_from_graph_property_string_for_database() throws Exception {
    final LocalDateTime localDateTime = LocalDateTime.of(2015, Month.JUNE, 22, 13, 5, 4, 546000000);
    final String actual = localDateTimeConverter.toGraphProperty(localDateTime);
    final String expected = localDateTime.format(localDateTimeConverter.getDateTimeFormatter());
    assertEquals(expected, actual);
  }

  @Test
  public void should_convert_string_from_database_to_local_date_time() throws Exception {
    final LocalDateTime localDateTime = localDateTimeConverter.toEntityAttribute(DATE_TIME_VALUE);
    assertNotNull(localDateTime);
    assertThat(localDateTime.getDayOfMonth(), equalTo(22));
    assertThat(localDateTime.getMonthValue(), equalTo(6));
    assertThat(localDateTime.getYear(), equalTo(2015));
    assertThat(localDateTime.getHour(), equalTo(13));
    assertThat(localDateTime.getMinute(), equalTo(5));
    assertThat(localDateTime.getSecond(), equalTo(4));
    assertThat(localDateTime.getNano(), equalTo(546000000));
  }
}

但是,当我尝试从存储库中使用它时,如下所示。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestContextConfiguration.class)
@DirtiesContext
@TestExecutionListeners(inheritListeners = false, listeners = { DataSourceDependencyInjectionTestExecutionListener.class })
public class PersonRepositoryTest extends AbstractRepositoryTest<CypherFilesPopulator> {

  private static final Logger LOG = LoggerFactory.getLogger(PersonRepositoryTest.class);
  public static final String CQL_DATASET_FILE = "src/test/resources/dataset/person-repository-dataset.cql";

  @Autowired
  PersonRepository personRepository;

  @Test
  public void should_find_all_persons() {
    LOG.debug("Test: Finding all persons ...");
    final Iterable<Person> persons = personRepository.findAll();
    persons.forEach(person -> {LOG.debug("Person: {}", person);});
  }

  @Override
  public CypherFilesPopulator assignDatabasePopulator() {
    return DatabasePopulatorUtil.createCypherFilesPopulator(Collections.singletonList(CQL_DATASET_FILE));
  }
}

我的单元测试失败了,因为没有进行值注入。

org.neo4j.ogm.metadata.MappingException: Error mapping GraphModel to instance of com.example.model.node.Person
    at org.neo4j.ogm.mapper.GraphEntityMapper.mapEntities(GraphEntityMapper.java:97)
    at org.neo4j.ogm.mapper.GraphEntityMapper.map(GraphEntityMapper.java:69)
    at org.neo4j.ogm.session.response.SessionResponseHandler.loadAll(SessionResponseHandler.java:181)
    at org.neo4j.ogm.session.delegates.LoadByTypeDelegate.loadAll(LoadByTypeDelegate.java:69)
    at org.neo4j.ogm.session.delegates.LoadByTypeDelegate.loadAll(LoadByTypeDelegate.java:99)
    at org.neo4j.ogm.session.Neo4jSession.loadAll(Neo4jSession.java:119)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
    at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:133)
    at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:121)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
    at com.sun.proxy.$Proxy110.loadAll(Unknown Source)
    at org.springframework.data.neo4j.repository.GraphRepositoryImpl.findAll(GraphRepositoryImpl.java:123)
    at org.springframework.data.neo4j.repository.GraphRepositoryImpl.findAll(GraphRepositoryImpl.java:118)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:475)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:460)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:432)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:61)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.transaction.interceptor.TransactionInterceptor.proceedWithInvocation(TransactionInterceptor.java:99)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
    at com.sun.proxy.$Proxy124.findAll(Unknown Source)
    at com.example.repository.PersonRepositoryTest.should_find_all_persons(PersonRepositoryTest.java:36)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:73)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:73)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:224)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:83)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access[=15=]0(ParentRunner.java:58)
    at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:68)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:163)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:78)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:212)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
Caused by: java.lang.NullPointerException: pattern
    at java.util.Objects.requireNonNull(Objects.java:228)
    at java.time.format.DateTimeFormatterBuilder.appendPattern(DateTimeFormatterBuilder.java:1571)
    at java.time.format.DateTimeFormatter.ofPattern(DateTimeFormatter.java:534)
    at com.example.converter.LocalDateTimeConverter.getDateTimeFormatter(LocalDateTimeConverter.java:41)
    at com.example.converter.LocalDateTimeConverter.toEntityAttribute(LocalDateTimeConverter.java:37)
    at com.example.converter.LocalDateTimeConverter.toEntityAttribute(LocalDateTimeConverter.java:14)
    at org.neo4j.ogm.entityaccess.FieldWriter.write(FieldWriter.java:64)
    at org.neo4j.ogm.mapper.GraphEntityMapper.writeProperty(GraphEntityMapper.java:164)
    at org.neo4j.ogm.mapper.GraphEntityMapper.setProperties(GraphEntityMapper.java:129)
    at org.neo4j.ogm.mapper.GraphEntityMapper.mapNodes(GraphEntityMapper.java:110)
    at org.neo4j.ogm.mapper.GraphEntityMapper.mapEntities(GraphEntityMapper.java:94)
    ... 74 more

我想知道 SDN4 是如何实例化我的转换器对象的?我在这里找不到我做错了什么。用于 SDN 3.4 的类似方法。当我升级到 SDN 4 时它开始崩溃。

发生这种情况是因为在这种情况下实际上并不是 Spring 构造了 AttributeConverter 个实例。 AttributeConverter 来自底层对象图映射器 (OGM),根据设计,这不是 Spring 感知的,因此忽略 class 上的任何 Spring 注释管理。

但是,如果您通过指定目标类型而不是 AttributeConverter class 来更改 Person 字段上的 @Convert 注释,那么您可以使用 Spring 的 ConversionService 代替。您可以使用 MetaDataDrivenConversionService 注册您想要的 Spring 转换器,框架应该使用它进行转换。

您的元数据驱动转换服务可以在您的 Neo4jConfiguration 子class 中构建,如下所示:

@Bean
public ConversionService springConversionService() {
   return new MetaDataDrivenConversionService(getSessionFactory().metaData());
}