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

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)]

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

null values inserted while auditing

My AuditListener
public class EmployeeAuditListeners {
#PrePersist
public void prePersist(Employee employee){
perform(employee,Action.INSERTED);
}
#PreUpdate
public void preUpdate(Employee employee){
perform(employee,Action.UPDATED);
}
#PreRemove
public void preRemove(Employee employee){
perform(employee,Action.DELETED);
}
#Transactional
public void perform(Employee emp, Action action){
EntityManager em = BeanUtil.getBean(EntityManager.class);
CommonLogs commonLogs = new CommonLogs();
commonLogs.setQuery("new query");
em.persist(commonLogs);
}
}
and My Auditable.class
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
public abstract class Auditable<U> {
#CreatedBy
protected U createdBy;
#CreatedDate
#Temporal(TemporalType.TIMESTAMP)
protected Date createdDate;
#LastModifiedBy
protected U lastModifiedBy;
#LastModifiedDate
#Temporal(TemporalType.TIMESTAMP)
protected Date lastModifiedDate;
}
My CommonLogs.class
#Entity
#EntityListeners(AuditingEntityListener.class)
public class CommonLogs extends Auditable<String> {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String query;
public CommonLogs() {
}
public CommonLogs(String query) {
this.query = query;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getQuery() {
return query;
}
public void setQuery(String query) {
this.query = query;
}
}
My Employee.java class
#Entity
#EntityListeners(EmployeeAuditListeners.class)
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String address;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
and I have a simple Rest Controller
#RestController
#RequestMapping("/api")
public class EmployeeController {
#Autowired
private EmployeeRepository employeeRepository;
#PostMapping("/employees")
public Employee createEmployee(#RequestBody Employee employee){
return employeeRepository.save(employee);
}
}
I want to log it on my table (common_logs) every time i perform some crud operations on my Employee Entity.
the above given example is working to some extent as it successfully stores employee and invokes EmployeeAuditListeners.
but now while saving CommongLog entity i expect it's parent class Auditable to automatically insert createdBy, createdDate etc. for now only query and id is inserted on common_logs table and remaining columns are null.
You can review the documentation for Auditing in here.
To enable the automatic Auditing, you must add the annotation #EnableJpaAuditing in your Application class:
#SpringBootApplication
#EnableJpaAuditing
class Application {
static void main(String[] args) {
SpringApplication.run(Application.class, args)
}
}
If you want the fields #CreatedBy and #LastModifiedBy too, you will also need to implement the AuditorAware<T> interface. For example:
class SpringSecurityAuditorAware implements AuditorAware<User> {
public User getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return null;
}
return ((MyUserDetails) authentication.getPrincipal()).getUser();
}
}

Spring JPARepository Update a field

I have a simple Model in Java called Member with fields - ID (Primary Key), Name (String), Position (String)
I want to expose an POST endpoint to update fields of a member. This method can accept payload like this
{ "id":1,"name":"Prateek"}
or
{ "id":1,"position":"Head of HR"}
and based on the payload received, I update only that particular field. How can I achieve that with JPARepository?
My repository interface is basic -
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository("memberRepository")
public interface MemberRepository extends JpaRepository<Member, Integer>{
}
My Member model -
#Entity
#Table(name="members")
public class Member {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="member_id")
private Integer id;
#Column(name="member_name")
#NotNull
private String name;
#Column(name="member_joining_date")
#NotNull
private Date joiningDate = new Date();
#Enumerated(EnumType.STRING)
#Column(name="member_type",columnDefinition="varchar(255) default 'ORDINARY_MEMBER'")
private MemberType memberType = MemberType.ORDINARY_MEMBER;
public Member(Integer id, String name, Date joiningDate) {
super();
this.id = id;
this.name = name;
this.joiningDate = joiningDate;
this.memberType = MemberType.ORDINARY_MEMBER;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getJoiningDate() {
return joiningDate;
}
public void setJoiningDate(Date joiningDate) {
this.joiningDate = joiningDate;
}
public MemberType getMemberType() {
return memberType;
}
public void setMemberType(MemberType memberType) {
this.memberType = memberType;
}
public Member(String name) {
this.memberType = MemberType.ORDINARY_MEMBER;
this.joiningDate = new Date();
this.name = name;
}
public Member() {
}
}
Something like this should do the trick
public class MemberService {
#Autowired
MemberRepository memberRepository;
public Member updateMember(Member memberFromRest) {
Member memberFromDb = memberRepository.findById(memberFromRest.getid());
//check if memberFromRest has name or position and update that to memberFromDb
memberRepository.save(memberFromDb);
}
}

Can't save many-to-many relations by form in JSP

The Context
I have a simple workshop application with three entities - Job, Employee and Customer. I am trying to create web interface which will add new Job in this case. Job has many to many relations with Employee and Customer. In Job entity there are lists of Employee and Customer as well.
The Problem
When I try to post my request with new Job through HTTP I get Bad Request 400 with description:
The server cannot or will not process the request due to something
that is perceived to be a client error (e.g., malformed request
syntax, invalid request message framing, or deceptive request
routing).
I don't know where excactly is bug.
Forms for adding Customer and Employee work fine.
The Code
Entities:
Employee
#Component
#Entity
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Pattern (regexp="[a-zA-Z]+")
#NotEmpty
private String employeeName;
#Pattern (regexp="[a-zA-Z]+")
#NotEmpty
private String employeeSurname;
#ManyToMany(mappedBy = "employeeList")
private List<Job> jobList;
public Employee() {
}
public Employee(int id, String employeeName, String employeeSurname) {
this.id = id;
this.employeeName = employeeName;
this.employeeSurname = employeeSurname;
}
public Employee(String employeeName, String employeeSurname) {
this.employeeName = employeeName;
this.employeeSurname = employeeSurname;
}
public List<Job> getJobList() {
return jobList;
}
public void setJobList(List<Job> jobList) {
this.jobList = jobList;
}
public String getEmployeeName() {
return employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
public String getEmployeeSurname() {
return employeeSurname;
}
public void setEmployeeSurname(String employeeSurname) {
this.employeeSurname = employeeSurname;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}}
Customer
#Component
#Entity
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Pattern(regexp="[a-zA-Z]+")
#NotEmpty
private String CustomerName;
#Pattern (regexp="[a-zA-Z]+")
#NotEmpty
private String CustomerSurname;
#Pattern (regexp = "\\w+")
#NotEmpty
private String car;
private int phonenumber;
#ManyToMany(mappedBy = "customerList")
private List<Job> jobList;
public Customer(String customerName, String customerSurname, String car, int phonenumber) {
CustomerName = customerName;
CustomerSurname = customerSurname;
this.car = car;
this.phonenumber = phonenumber;
}
public Customer(int id, String customerName, String customerSurname, String car, int phonenumber) {
this.id=id;
CustomerName = customerName;
CustomerSurname = customerSurname;
this.car = car;
this.phonenumber = phonenumber;
}
public Customer() {
}
public List<Job> getJobList() {
return jobList;
}
public void setJobList(List<Job> jobList) {
this.jobList = jobList;
}
public String getCustomerName() {
return CustomerName;
}
public void setCustomerName(String customerName) {
CustomerName = customerName;
}
public String getCustomerSurname() {
return CustomerSurname;
}
public void setCustomerSurname(String customerSurname) {
CustomerSurname = customerSurname;
}
public int getPhonenumber() {
return phonenumber;
}
public void setPhonenumber(int phonenumber) {
this.phonenumber = phonenumber;
}
public String getCar() {
return car;
}
public void setCar(String car) {
this.car = car;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}}
Job
#Component
#Entity
public class Job {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#NotEmpty
#Pattern(regexp="[a-zA-Z]+")
private String jobName;
#ManyToMany(fetch = FetchType.EAGER)
private List<Employee> employeeList;
#LazyCollection(LazyCollectionOption.FALSE)
#ManyToMany
private List<Customer> customerList;
public Job() {
}
public Job(String jobName, List<Employee> employeeList, List<Customer> customerList) {
this.jobName = jobName;
this.employeeList = employeeList;
this.customerList = customerList;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getJobName() {
return jobName;
}
public void setJobName(String jobName) {
this.jobName = jobName;
}
public List<Employee> getEmployeeList() {
return employeeList;
}
public void setEmployeeList(List<Employee> employeeList) {
this.employeeList = employeeList;
}
public List<Customer> getCustomerList() {
return customerList;
}
public void setCustomerList(List<Customer> customerList) {
this.customerList = customerList;
}}
DaoImpl
JobDaoImpl
#Repository
public class JobDaoImpl implements JobDao {
#PersistenceContext
EntityManager entityManager;
#Override
public List<Job> findAllJobs() {
return entityManager.createQuery("select j from Job j order by j.id", Job.class)
.getResultList();
}
#Override
public Job addJob(Job job) {
entityManager.persist(job);
entityManager.flush();
entityManager.refresh(job);
return job;
}}
Service
JobService
#Service
#Transactional
public class JobService {
private JobDao jobDao;
public List<Job> findAllJobs(){
return jobDao.findAllJobs();
}
public Job addNewJob(Job job){return jobDao.addJob(job);}
public JobService(JobDao jobDao) {
this.jobDao = jobDao;
}
}
Controller
JobController
#Controller
public class JobController {
JobService jobService;
EmployeeService employeeService;
CustomerService customerService;
public JobController(JobService jobService, EmployeeService employeeService, CustomerService customerService) {
this.jobService = jobService;
this.employeeService = employeeService;
this.customerService = customerService;
}
//JOB INDEX
#RequestMapping("job-index.html")
public ModelAndView getJobIndex() {
ModelAndView modelAndView = new ModelAndView("jobViews/jobIndex");
return modelAndView;
}
//SHOW EMPLOYEES
#RequestMapping("show-jobs.html")
public ModelAndView getAllJobs() {
ModelAndView modelAndView = new ModelAndView("jobViews/jobs");
modelAndView.addObject("jobs", jobService.findAllJobs());
return modelAndView;
}
//ADD NEW JOB GET METHOD
#GetMapping(value = "add-job.html")
public ModelAndView addNewJob(){
return new ModelAndView("jobViews/addJob","job", new Job());
}
//ADD NEW JOB POST METHOD
#PostMapping(value = "add-job.html")
public ModelAndView addNewJob(#ModelAttribute Job job){
return new ModelAndView("jobViews/addJobConfirmation","job",job);
}
#ModelAttribute("employeeInit")
public List<Employee> initializeEmployees() {
return employeeService.findAllEmployee();
}
#ModelAttribute("customerInit")
public List<Customer> initializeCustomer(){ return customerService.findAllCustomer();}}
JSP Views
addJob.jsp
<f:form method="post" modelAttribute="job">
<p>Job name:<f:input path="jobName"/></p>
<f:hidden path="id"/>
<f:select path="employeeList" multiple="true">
<f:options items="${employeeInit}" itemLabel="employeeSurname" itemValue="id"></f:options>
</f:select>
<f:select path="customerList" multiple="true">
<f:options items="${customerInit}" itemLabel="customerSurname" itemValue="id"></f:options>
</f:select>
<button type="submit">Add</button>

Sprint Date Rest successful, but no data

Entity
#Data
#Accessors(chain = true, fluent = true)
#Entity
#Table(name = "T_NOTE")
#Access(AccessType.FIELD)
public class Note implements Serializable
{
#Id
#GeneratedValue
private Long id;
private Date date;
#Column(length = 2000)
private String content;
private String title;
private String weather;
}
Repository
#RepositoryRestResource(collectionResourceRel = "note", path = "note")
public interface NoteRepository extends AbstractRepository<Note, Long>
{
}
GET http://localhost:8080/note/2
{
"_links": {
"self": {
"href": "http://localhost:8080/note/2"
}
}
}
No entity field data, why?
EIDT
After I add standard setter/getter, everything is ok now.
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public Date getDate()
{
return date;
}
public void setDate(Date date)
{
this.date = date;
}
public String getContent()
{
return content;
}
public void setContent(String content)
{
this.content = content;
}
public String getTitle()
{
return title;
}
public void setTitle(String title)
{
this.title = title;
}
public String getWeather()
{
return weather;
}
public void setWeather(String weather)
{
this.weather = weather;
}
Is this cause by jackson mapper ? How can I use fluent API with this ?Why not just use reflection to generate JSON ?
EDIT
What I need is this configuration
#Configuration
#Import(RepositoryRestMvcConfiguration.class)
public class ShoweaRestMvcConfiguration extends RepositoryRestMvcConfiguration
{
#Override
protected void configureJacksonObjectMapper(ObjectMapper mapper)
{
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
}
}
Caused by this
#Accessors is probably stepping over the #Data annotation, and with fluent = true it generates getters with the same name as the field, like id() and date() (#Accessor documentation). That's why Spring doesn't see any of the fields.
I think you can safely remove both #Accessors and #Access, since #Access's takes the default value from id (if you annotated the field, it will be FIELD, if you annotated the getter, it will be PROPERTY).

ElementCollection and "failed to lazily initialize a collection of role" exception

I'm very new to Spring and I'm trying to figure out how to use #ElementCollection.
I have the following classes:
#Embeddable
public class Phone {
private String type;
private String areaCode;
#Column(name="P_NUMBER")
private String number;
public Phone() {
}
public Phone(String type, String areaCode, String number) {
super();
this.type = type;
this.areaCode = areaCode;
this.number = number;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public String getAreaCode() {
return areaCode;
}
public void setAreaCode(String areaCode) {
this.areaCode = areaCode;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
#Entity
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "EMP_ID")
private long id;
#ElementCollection//(fetch=FetchType.EAGER)
#CollectionTable(name = "PHONE", joinColumns = #JoinColumn(name = "OWNER_ID"))
private List<Phone> phones = new ArrayList<Phone>();;
public Employee() {
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public List<Phone> getPhones() {
return phones;
}
public void setPhones(List<Phone> phones) {
this.phones = phones;
}
}
Repository:
#Repository
public interface EmployeeRepository extends CrudRepository<Employee, Long>{
public Employee findById(long id);
}
Then I use it in main method:
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
EmployeeRepository repository = context.getBean(EmployeeRepository.class);
Phone phone = new Phone("work", "613", "494-1234");
Employee emp = new Employee();
emp.getPhones().add(phone);
repository.save(emp);
emp = repository.findById(1);
for (Phone p : emp.getPhones()) {
System.out.println(p.getNumber());
}
context.close();
}
It throws exception (when emp.getPhones() is called): Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: elcol.repository.Employee.phones, could not initialize proxy - no Session
If I add (fetch=FetchType.EAGER) to #ElementCollection annotation(commented in the code above in Employee class) - everything is ok.
How can I fix this without FetchType.EAGER?
In findById(long id) implementation, add this Hibernate.initialize(emp.getPhones()).
Your repository service should return all the data you will need already initialized so the client that calls the service stays independent of it. In short, If you don't need employees phones on the client side, don't initialize it. If you do need it - initialize it.
EDIT
With spring data you obviously don't have the implementation, so you can specify the query which will be used, and fetch the data in the query (the question is tagged with jpa so I guess you can use JpaRepository)
#Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long>{
#Query("SELECT e FROM Employee e JOIN FETCH e.phones WHERE e.id = (:id)")
public Employee findById(long id);
}

Resources