Spring Data:按示例查询和转换器

4

我有一个实体:

import javax.persistence.Convert;

@Entity(name = "my_entity")
public class MyEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @Convert(converter = StringListConverter.class)
    private List<String> emails;

    // next fields omitted...

}

实体中 emails 字段使用的转换器(典型的实现例如 LocalDateTimeConverter):

import javax.persistence.Converter;

@Converter
public class StringListConverter implements AttributeConverter<List<String>, String> {
    private static final String SPLIT_CHAR = ";";

    @Override
    public String convertToDatabaseColumn(List<String> stringList) {
        if (CollectionUtils.isNotEmpty(stringList)) {
            return String.join(SPLIT_CHAR, stringList);
        } else {
            return null;
        }
    }

    @Override
    public List<String> convertToEntityAttribute(String string) {
        if (StringUtils.isNotBlank(string)) {
            return Arrays.asList(string.split(SPLIT_CHAR));
        } else {
            return Collections.emptyList();
        }
    }

}

我将多个电子邮件地址以分号分隔的形式存储在一个列中。StringListConverter用于执行该转换。

还有Spring Data存储库:

import org.springframework.data.domain.Example;

public interface MyRepository extends JpaRepository<MyEntity, Long> {

    default List<MyEntity> findMatchingMyEntity(MyEntity myEntity) {
        Example<MyEntity> example = Example.of(myEntity);
        return findAll(example);
    }

}

我使用Spring Data中的查询示例(Query by Example)机制。当我有没有使用@Convert(例如String name)的字段时,它有效。但是当我有一个带有@ConvertAttributeConverter)的字段,例如List<String> emails时,它会导致InvalidDataAccessApiUsageException异常。
org.springframework.dao.InvalidDataAccessApiUsageException: Parameter value [abc@company.com] did not match expected type [java.util.List (n/a)]; nested exception is java.lang.IllegalArgumentException: Parameter value [abc@company.com] did not match expected type [java.util.List (n/a)]
    at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:374) ~[spring-orm-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:257) ~[spring-orm-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:528) ~[spring-orm-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61) ~[spring-tx-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    ...
Caused by: java.lang.IllegalArgumentException: Parameter value [abc@company.com] did not match expected type [java.util.List (n/a)]
    at org.hibernate.query.spi.QueryParameterBindingValidator.validate(QueryParameterBindingValidator.java:54) ~[hibernate-core-5.4.8.Final.jar:5.4.8.Final]
    at org.hibernate.query.spi.QueryParameterBindingValidator.validate(QueryParameterBindingValidator.java:27) ~[hibernate-core-5.4.8.Final.jar:5.4.8.Final]
    at org.hibernate.query.internal.QueryParameterBindingImpl.validate(QueryParameterBindingImpl.java:90) ~[hibernate-core-5.4.8.Final.jar:5.4.8.Final]
    ... 146 common frames omitted

因为我尝试使用以下列表进行搜索:["abc@company.com", "def@company.com"],所以导致消息不可思议,但实际上消息中只有一个电子邮件地址。


我尝试在ExampleMatcher中实现transform

import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;

public interface MyRepository extends JpaRepository<MyEntity, Long> {

    default List<MyEntity> findMatchingMyEntity(MyEntity myEntity) {
        ExampleMatcher matcher = ExampleMatcher.matching()
                .withMatcher("emails",
                        match -> match.transform(emailsOptional -> {
                            if (emailsOptional.isPresent()) {
                                List<String> emails = (List<String>) emailsOptional.get();
                                return Optional.ofNullable(new StringListConverter().convertToDatabaseColumn(emails));
                            }
                            return emailsOptional;
                        }));
        Example<MyEntity> example = Example.of(myEntity, matcher);
        return findAll(example);
    }

}

但是它也会引发InvalidDataAccessApiUsageException,但与前一个异常信息不同(我设置了两个电子邮件):

org.springframework.dao.InvalidDataAccessApiUsageException: Parameter value [abc@company.com;def@company.com] did not match expected type [java.util.List (n/a)]; nested exception is java.lang.IllegalArgumentException: Parameter value [abc@company.com;def@company.com] did not match expected type [java.util.List (n/a)]
    at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:374) ~[spring-orm-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:257) ~[spring-orm-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:528) ~[spring-orm-5.2.1.RELEASE.jar:5.2.1.RELEASE]
Caused by: java.lang.IllegalArgumentException: Parameter value [abc@company.com;def@company.com] did not match expected type [java.util.List (n/a)]
    at org.hibernate.query.spi.QueryParameterBindingValidator.validate(QueryParameterBindingValidator.java:54) ~[hibernate-core-5.4.8.Final.jar:5.4.8.Final]
    at org.hibernate.query.spi.QueryParameterBindingValidator.validate(QueryParameterBindingValidator.java:27) ~[hibernate-core-5.4.8.Final.jar:5.4.8.Final]
    ... 146 common frames omitted
1个回答

1
似乎出现了某种原因导致Hibernate尝试像SQL中的IN查询一样,使用expandListValuedParameters方法将电子邮件数组拆分为多个条件。注意 - 使用您的解决方案进行查询,如findAllByEmailsIn(List<String> emailsList)也行不通。 expandListValuedParameters方法已自Hibernate 5.2起被弃用,因此可能存在一些问题,并且在Hibernate 6.0中肯定会有不同的实现。 我还没有找到解决您问题的方法,但有一些解决方法: 1. 将您的List<String> emails包装在另一个类中。 包装类如下:
public class EmailList {

   private List<String> emails;
     
   // getters, setters, constructors ommited

}

更新的模型类:

import javax.persistence.Convert;
  
@Entity(name = "my_entity")
public class MyEntity {
  
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @Convert(converter = StringEmailListConverter.class)
    private EmailList emailList;

    // next fields omitted...
}

更新后的转换器类:

import javax.persistence.Converter;

@Converter
public class StringEmailListConverter implements AttributeConverter<EmailList, String> {
    private static final String SPLIT_CHAR = ";";

    @Override
    public String convertToDatabaseColumn(EmailList emailList) {
        if (emailList != null && CollectionUtils.isNotEmpty(emailList.getEmails())) {
            return String.join(SPLIT_CHAR, emailList.getEmails());
        } else {
            return null;
        }
    }

    @Override
    public EmailList convertToEntityAttribute(String string) {
        if (StringUtils.isNotBlank(string)) {
            return new EmailList(Arrays.asList(string.split(SPLIT_CHAR)));
        } else {
            return new EmailList(Collections.emptyList());
        }
    }

}

Spring Data存储库将与此代码很好地配合使用-无需使用transform

import org.springframework.data.domain.Example;

public interface MyRepository extends JpaRepository<MyEntity, Long> {

    default List<MyEntity> findMatchingMyEntity(MyEntity myEntity) {
        Example<MyEntity> example = Example.of(myEntity);
        return findAll(example);
    }

}
  1. 使用 String[] emails 替代 List<String> emails 你需要分别更改 MyEntity 和 Converter 来使用 String[]。当然,有时候使用 String[] 不是一个选项,因为你特别需要一个 List。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接