Reputation: 2700
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
):
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.)
And Spring Data repository:
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. When I have fields without @Convert
(like String name
) it works. But when I have field with @Convert
(AttributeConverter
) like List<String> emails
it causes InvalidDataAccessApiUsageException
.
org.springframework.dao.InvalidDataAccessApiUsageException: Parameter value [[email protected]] did not match expected type [java.util.List (n/a)]; nested exception is java.lang.IllegalArgumentException: Parameter value [[email protected]] 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 [[email protected]] 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: ["[email protected]", "[email protected]"]
, but in message is only one email)
I've tried to implement transform
in 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);
}
}
But is causes InvalidDataAccessApiUsageException
too, but with different message than previous one (there are two emails that I've set):
org.springframework.dao.InvalidDataAccessApiUsageException: Parameter value [[email protected];[email protected]] did not match expected type [java.util.List (n/a)]; nested exception is java.lang.IllegalArgumentException: Parameter value [[email protected];[email protected]] 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 [[email protected];[email protected]] 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
Upvotes: 4
Views: 3401
Reputation: 447
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.
Note - with your solution doing query like findAllByEmailsIn(List<String> emailsList)
also won't work.
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.
I haven't found a fix for your problem, but there are some workarounds:
List<String> emails
in another classWrapper 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
:
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);
}
}
String[] emails
instead of List<String> emails
You need to change MyEntity and Converter respectively to use String[]
. Of course using String[]
sometimes is not an option because you specifically need a List.Upvotes: 1