Parameter value [multiVLANSupport] did not match expected type [java.util.List (n/a)]

Viewed 928

I have created an entity class that has a column which uses Attribute Converter of JPA:

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

The converter class is :

@Converter
  public class StringListConverter implements AttributeConverter<List<String>, String> {

    @Override
    public String convertToDatabaseColumn(List<String> list) {
      
      return String.join(",", list); 
    }

    @Override
    public List<String> convertToEntityAttribute(String joined) {
      return new ArrayList<>(Arrays.asList(joined.split(",")));
    }

  }

The expected values of the column in the Tables are like

functionSpecificationLabel
multiVLANSupport,telepresence,csaid

Now I need to return the rows that have multiVLANSupport,telepresence,csaid as value in functionSpecificationLabel column.

My Query in the repository for the same is :

@Query("Select pd from ProductDetailsEntity pd where pd.functionSpecificationLabel in (:labels)")
  Optional<ProductDetailsEntity> findByFunctionSpecificationLabel(@Param("labels") final List<String> labels);

Now I face the issue as :

Parameter value [multiVLANSupport] did not match expected type [java.util.List (n/a)]

1 Answers

I am not exactly sure if this is even possible, here is how i have implemented to store list of values in an entity class using @ElementCollection You can read more about it here https://thorben-janssen.com/hibernate-tips-query-elementcollection/

A good discussion can be found here How to persist a property of type List<String> in JPA?. My suggestion is to avoid storing any values in db based on a delimiter.

Ideally, when storing such labels it is better to map them using OneToMany relationship. Also note that this will create an additional table in this case animal_labels.

Answer 1

Repository

@Repository
public interface AnimalRepository extends JpaRepository<Animal, UUID> {
    List<Animal> findDistinctAnimalsByLabelsIsIn(List<String> cute);
}

Entity class

@Entity
@Table(name = "animal")
public class Animal {
    @Id
    @GeneratedValue
    @Type(type = "uuid-char")
    private UUID id;

    private String name;

    @ElementCollection(targetClass = String.class)
    private List<String> labels;

    public Animal() {
    }

    public Animal(String name, List<String> labels) {
        this.name = name;
        this.labels = labels;
    }

    public UUID getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<String> getLabels() {
        return labels;
    }

    public void setLabels(List<String> labels) {
        this.labels = labels;
    }

}

Test:

@ExtendWith(SpringExtension.class)
@Transactional
@SpringBootTest(classes = TestApplication.class)
class CustomConverterTest {

    @Autowired
    private EntityManager entityManager;

    @Autowired
    private AnimalRepository animalRepository;


    @Test
    void customLabelConverter() {
        Animal puppy = new Animal("Puppy", Arrays.asList("cute", "intelligent", "spy"));
        Animal meow = new Animal("Cat", Arrays.asList("cute", "intelligent"));
        entityManager.persist(puppy);
        entityManager.persist(meow);


        List<Animal> animalWithCutelabels = animalRepository.findDistinctAnimalsByLabelsIsIn(Arrays.asList("cute"));
        List<Animal> animalWithSpylabels = animalRepository.findDistinctAnimalsByLabelsIsIn(Arrays.asList("spy"));
        List<Animal> animalWithCuteAndSpylabels = animalRepository.findDistinctAnimalsByLabelsIsIn(Arrays.asList("cute", "spy"));
        Assertions.assertEquals(2, animalWithCutelabels.size());
        Assertions.assertEquals(1, animalWithSpylabels.size());
        Assertions.assertEquals(2, animalWithCuteAndSpylabels.size());
    }
}

Answer 2

If you do have any choice but to only go with the comma separated values then please find answer below for this approach:

Repository(since this is a string we cannot use list like in)

@Repository
public interface AnimalRepository extends JpaRepository<Animal, UUID> {
    // Also note that the query goes as string and not list 
    List<Animal> findAllByLabelsContaining(String labels);
}

Test:

@Test
    void customLabelConverter() {
        Animal puppy = new Animal("Puppy", String.join(",", Arrays.asList("cute", "intelligent", "spy")));
        Animal meow = new Animal("Cat", String.join(",", Arrays.asList("cute", "intelligent")));
        entityManager.persist(puppy);
        entityManager.persist(meow);


        List<Animal> animalWithCutelabels = animalRepository.findAllByLabelsContaining(String.join(",", Arrays.asList("cute")));
        List<Animal> animalWithSpylabels = animalRepository.findAllByLabelsContaining(String.join(",", Arrays.asList("spy")));
        Assertions.assertEquals(2, animalWithCutelabels.size());
        Assertions.assertEquals(1, animalWithSpylabels.size());
    }

Entity:

@Entity
@Table(name = "animal")
public class Animal {
    @Id
    @GeneratedValue
    @Type(type = "uuid-char")
    private UUID id;

    @Column
    private String name;

    @Column
    private String labels;

    public Animal() {
    }

    public Animal(String name, String labels) {
        this.name = name;
        this.labels = labels;
    }

    public UUID getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<String> getLabels() {
        if (StringUtils.isEmpty(labels)) return Collections.emptyList();
        return new ArrayList<>(Arrays.asList(labels.split(AnimalLabelsConverter.DELIMITER_COMMA)));
    }

    public void setLabels(List<String> labels) {
        if (CollectionUtils.isEmpty(labels)) {
            this.labels = "";
        } else {
            this.labels = String.join(AnimalLabelsConverter.DELIMITER_COMMA, labels);
        }
    }

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

        @Override
        public String convertToDatabaseColumn(List<String> labels) {
            if (CollectionUtils.isEmpty(labels)) return "";
            return String.join(DELIMITER_COMMA, labels);
        }

        @Override
        public List<String> convertToEntityAttribute(String dbData) {
            if (StringUtils.isEmpty(dbData)) return Collections.emptyList();
            return new ArrayList<>(Arrays.asList(dbData.split(DELIMITER_COMMA)));
        }
    }
}
Related