简体   繁体   English

Spring Data:示例查询和转换器

[英]Spring Data: Query By Example and Converter

I have an entity:我有一个实体:

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...

}

Converter which is used by emails field in entity (typical implementation like LocalDateTimeConverter ):实体中的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();
        }
    }

}

(I store emails separated by semicolons in one column. StringListConverter do that conversion.) (我将电子邮件以分号分隔在一列中StringListConverter执行该转换。)

And Spring Data repository:和 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);
    }

}

I use Query by Example mechanism from Spring Data.我使用 Spring Data 中的 Query by Example 机制。 When I have fields without @Convert (like String name ) it works.当我有没有@Convert字段(如String name )时,它可以工作。 But when I have field with @Convert ( AttributeConverter ) like List<String> emails it causes InvalidDataAccessApiUsageException .但是当我有像List<String> emails这样的@Convert ( AttributeConverter ) 字段时,它会导致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

(message is weird because I've tried search with that list: ["abc@company.com", "def@company.com"] , but in message is only one email) (消息很奇怪,因为我尝试使用该列表进行搜索: ["abc@company.com", "def@company.com"] ,但消息中只有一封电子邮件)


I've tried to implement transform in ExampleMatcher :我试图在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);
    }

}

But is causes InvalidDataAccessApiUsageException too, but with different message than previous one (there are two emails that I've set):但是也是导致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

It seems that for some reason Hibernate is trying to split the array of emails into multiple conditions just like in IN query in SQL using expandListValuedParameters method.似乎出于某种原因,Hibernate 试图将电子邮件数组拆分为多个条件,就像 SQL 中的IN查询一样,使用expandListValuedParameters方法。 Note - with your solution doing query like findAllByEmailsIn(List<String> emailsList) also won't work.注意 - 您的解决方案执行像findAllByEmailsIn(List<String> emailsList)这样的查询也不起作用。

Method expandListValuedParameters is deprecated since Hibernate 5.2, so it may contains some problems and for sure will be implemented differently in Hibernate 6.0.方法expandListValuedParameters自 Hibernate 5.2 起expandListValuedParameters被弃用,因此它可能包含一些问题,并且在 Hibernate 6.0 中肯定会以不同的方式实现。

I haven't found a fix for your problem, but there are some workarounds:我还没有找到解决您的问题的方法,但有一些解决方法:

  1. Wrap your List<String> emails in another class将您的List<String> emails包装在另一个类中

Wrapper class:包装类:

public class EmailList {

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

}

Updated model class:更新模型类:

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...
}

Updated converter class:更新的转换器类:

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());
        }
    }

}

And Spring Data repository will work fine with this code - no need for using transform :并且 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. Use String[] emails instead of List<String> emails You need to change MyEntity and Converter respectively to use String[] .使用String[] emails而不是List<String> emails您需要分别更改 MyEntity 和 Converter 以使用String[] Of course using String[] sometimes is not an option because you specifically need a List.当然,有时使用String[]不是一种选择,因为您特别需要一个 List。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM