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 数据存储库:

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)的字段时,它可以工作。但是当我有@Convert( AttributeConverter)的字段时,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"],但消息中只有一封电子邮件)


我试图transform实现ExampleMatcher

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
4

1 回答 1

1

似乎出于某种原因,Hibernate 试图将电子邮件数组拆分为多个条件,就像IN在 SQL usingexpandListValuedParameters方法中查询一样。注意 - 使用您的解决方案进行查询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[]有时使用不是一种选择,因为您特别需要一个列表。
于 2021-08-29T17:14:05.257 回答