Getting empty contents in the Postman API when filtering through Spring data Specification - spring

I'm using the Spring Data JPA Specifications for Filtering data.
But When i'm hitting this URL http://localhost:9091/api/student/all?salary_like=1500
if i'm filtering through name also getting empty contents.
i'm getting the empty contents here.
But in eclipse console it's generating the correct query:
Hibernate: select student0_.id as id1_0_, student0_.address as address2_0_, student0_.age as age3_0_, student0_.name as name4_0_, student0_.salary as salary5_0_ from student_data_with_projection student0_ where (student0_.name like ?) and (student0_.age like ?) and (student0_.address like ?) and student0_.salary=1500.0 limit ?
And My code is:
POJO
#Data
#Component
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Setter
#Getter
#Table(name = "student_data_with_projection")
public class Student {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "age")
private int age;
#Column(name = "salary")
private Float salary;
#Column(name = "address")
private String address;
}
Controller
#RestController
#RequestMapping(path = "api/student/") //This is a Base URL in Our Controller.
public class StudentController {
#Autowired
StudentRepository studentRepository;
#GetMapping(path = "all")
public #ResponseBody
Iterable<Student> getAllStudentWIthProjection(#RequestParam(required = false, defaultValue = "") String name_like,
#RequestParam(required = false, defaultValue = "") int age_like,
#RequestParam(required = false) Float salary_like,
#RequestParam(required = false, defaultValue = "") String address_like,
#RequestParam(required = false, defaultValue = "0") int pageNum,
#RequestParam(required = false, defaultValue = "20") int pageSize) {
StudentSpecification spec1 =
new StudentSpecification(new SearchCriteria("name", ":", name_like));
StudentSpecification spec2 =
new StudentSpecification(new SearchCriteria("age", ":", age_like));
StudentSpecification spec3 =
new StudentSpecification(new SearchCriteria("address", ":", address_like));
Specification<Student> specGroup = Specification.where(spec1).and(spec2).and(spec3);
if (salary_like != null) {
StudentSpecification spec4 =
new StudentSpecification(new SearchCriteria("salary", ":", salary_like));
specGroup = specGroup.and(spec4);
}
Page<Student> findAll = studentRepository.findAll(specGroup, PageRequest.of(pageNum, pageSize));
return findAll;
}
}
Repository
public interface StudentRepository extends PagingAndSortingRepository<Student, Long>, JpaSpecificationExecutor<Student> {
}
Specification
#AllArgsConstructor
public class StudentSpecification implements Specification<Student> {
private SearchCriteria criteria;
public StudentSpecification(SearchCriteria searchCriteria) {
super();
this.criteria=searchCriteria;
}
public SearchCriteria getCriteria() {
return criteria;
}
#Override
public Predicate toPredicate
(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
if (criteria.getOperation().equalsIgnoreCase(">")) {
if (root.get(criteria.getKey()).getJavaType() == Date.class) {
return builder.greaterThanOrEqualTo(root.<Date>get(criteria.getKey()), (Date)criteria.getValue());
} else {
return builder.greaterThanOrEqualTo(
root.<String> get(criteria.getKey()), criteria.getValue().toString());
}
}
else if (criteria.getOperation().equalsIgnoreCase("<")) {
if (root.get(criteria.getKey()).getJavaType() == Date.class) {
return builder.lessThanOrEqualTo(root.<Date>get(criteria.getKey()), (Date)criteria.getValue());
} else {
return builder.lessThanOrEqualTo(
root.<String> get(criteria.getKey()), criteria.getValue().toString());
}
}
else if (criteria.getOperation().equalsIgnoreCase(":")) {
if (root.get(criteria.getKey()).getJavaType() == String.class) {
return builder.like(
root.<String>get(criteria.getKey()), "%" + criteria.getValue() + "%");
} else {
return builder.equal(root.get(criteria.getKey()), criteria.getValue());
}
}
return null;
}
}
Criteria
#Data
#AllArgsConstructor
public class SearchCriteria {
private String key;
private String operation;
private Object value;
public SearchCriteria(String key, String operation, Object value) {
super();
this.key = key;
this.operation = operation;
this.value = value;
}
/* Getter and Setter */
}
I have uploaded the code in GitHub: https://github.com/avinashm294/Filters.git
How can i fix this to filter my data.

I had used the #Getter and #Setter annotation of lombok in the POJO which was not working. After Adding the getter and setter explicitly now it's working.

the like in the query work as =
you need to append % before and after the name
example
StudentSpecification spec1 =
new StudentSpecification(new SearchCriteria("name", ":", "%"+name_like+"%"));

Related

move validation to the JPQL query level

I am looking for a way to move the validation method from Service to Repository
One Picture has one PictureData.
This is the method:
// TODO move validation to the JPQL query level
.filter(pic -> pic.getPictureData().getFileName() != null)
This is my Service
#Service
#ConditionalOnProperty(name = "picture.storage.type", havingValue = "file")
public class PictureServiceFileImpl implements PictureService {
private static final Logger logger = LoggerFactory.getLogger(PictureServiceFileImpl.class);
#Value("${picture.storage.path}")
private String storagePath;
private final PictureRepository repository;
#Autowired
public PictureServiceFileImpl(PictureRepository repository) {
this.repository = repository;
}
#Override
public Optional<String> getPictureContentTypeById(long id) {
return repository.findById(id)
// TODO move validation to the JPQL query level
.filter(pic -> pic.getPictureData().getFileName() != null)
.map(Picture::getContentType);
}
#Override
public Optional<byte[]> getPictureDataById(long id) {
return repository.findById(id)
// TODO move validation to the JPQL query level
.filter(pic -> pic.getPictureData().getFileName() != null)
.map(pic -> Path.of(storagePath, pic.getPictureData().getFileName()))
.filter(Files::exists)
.map(path -> {
try {
return Files.readAllBytes(path);
} catch (IOException ex) {
logger.error("Can't open picture file", ex);
throw new RuntimeException(ex);
}
});
}
#Override
public PictureData createPictureData(byte[] picture) {
String fileName = UUID.randomUUID().toString();
try (OutputStream os = Files.newOutputStream(Path.of(storagePath, fileName))) {
os.write(picture);
} catch (IOException ex) {
logger.error("Can't create picture file", ex);
throw new RuntimeException(ex);
}
return new PictureData(fileName);
}
}
The Entities
#Entity
#Table(name = "pictures")
public class Picture {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name", nullable = false)
private String name;
#Column(name = "content_type", nullable = false)
private String contentType;
#OneToOne(fetch = FetchType.LAZY, cascade= CascadeType.ALL, optional = false, orphanRemoval = true)
#JoinColumn(name="picture_data_id")
private PictureData pictureData;
#ManyToOne
private Product product;
public Picture() {
}
public Picture(String name, String contentType, PictureData pictureData, Product product) {
this.name = name;
this.contentType = contentType;
this.pictureData = pictureData;
this.product = product;
}
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 getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
public PictureData getPictureData() {
return pictureData;
}
public void setPictureData(PictureData pictureData) {
this.pictureData = pictureData;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
}
#Entity
#Table(name = "pictures_data")
public class PictureData {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Lob
#Type(type="org.hibernate.type.BinaryType") // для правильной работы PostgreSQL
#Column(name = "data", length = 33554430) // для правильной hibernate-валидации в MySQL
private byte[] data;
#Column(name = "file_name")
private String fileName;
public PictureData() {
}
public PictureData(byte[] data) {
this.data = data;
}
public PictureData(String fileName) {
this.fileName = fileName;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public byte[] getData() {
return data;
}
public void setData(byte[] data) {
this.data = data;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
}
I am struggling to get a query working in JPQL.
public interface PictureRepository extends JpaRepository<Picture, Long> {
#Query ("SELECT p FROM Picture p JOIN p.pictureData d WHERE d.data IS NOT NULL ")
Picture filterPictureWherePictureDataIsNotNull ();
}
Since you already have entity level join, you can directly use below method
public interface PictureRepository extends JpaRepository<Picture, Long>
{
#Query ("SELECT p FROM Picture p WHERE p.pictureData.data IS NOT NULL ")
Picture filterPictureWherePictureDataIsNotNull ();
}
Another observation as well,
You repo method might return list of Picture and not a one picture.So, return type should ideally be
#Query ("SELECT p FROM Picture p WHERE p.pictureData.data IS NOT NULL ")
List<Picture> filterPictureWherePictureDataIsNotNull ();

Spring data jpa specification and pageable in #manytomany using join table repository

I have a use case to filter and paginate the record with #manytomany relation using a separate join table.
Below are the relation and entities
public class User {
private Long userId;
private String userName
#OneToMany(mappedBy = "user")
private List<UserRole> userRole;
}
public class Role {
private Long roleId;
private String roleName
#OneToMany(mappedBy = "role")
private List<UserRole> userRole;
}
public class UserRole{
private Long id;
private Integer active
#ManyToOne
#MapsId("userId")
private User user;
#ManyToOne
#MapsId("roleId")
private Role role;
}
#Repository
public interface UserRoleRepository extends
JpaRepository<UserRole, String>,
JpaSpecificationExecutor<UserRole> {
}
public class UserRoleSpecification implements Specification<UserRole>
{
private SearchCriteria criteria;
public RuleEntitySpecification(SearchCriteria criteria ) {
this.criteria = criteria;
}
#Override
public Predicate toPredicate(Root<UserRole> root,
CriteriaQuery<?> query,
CriteriaBuilder criteriaBuilder) {
if(criteria.getOperation().equalsIgnoreCase("eq")) {
if(root.get(criteria.getKey()).getJavaType() == String.class)
{
return criteriaBuilder.like(root.get(criteria.getKey()),
"%" + criteria.getValue() + "%");
} else {
return criteriaBuilder.equal(root.get(criteria.getKey()),
criteria.getValue());
}
}
return null;
}
}
public class SearchCriteria implements Serializable {
private String key;
private String operation;
private Object value;
}
UserRoleSpecificationBuilder specBuilder = new UserRoleSpecificationBuilder();
specBuilder.with("active", "eq" , 1); // giving us proper result
Specification<UserRole> spec = specBuilder.build();
Pageable paging = PageRequest.of(0, 5, Sort.by("user.userId"));
Page<UserRole> pagedResult = userRoleRepository.findAll(spec,paging);
But when we try to filter based on Rule/User table properties like userName/roleName specBuilder.with("user.userName", "eq" , "xyz");, I am getting following exception:
org.springframework.dao.InvalidDataAccessApiUsageException:
Unable to locate Attribute with the the given name
[user.userName] on this ManagedType
Kindly suggest if there is any way to achieve the filter using UserRole Join Table repository and specification
Pagination is also required hence using repository of Type UserRole JoinTable.
#Override
public Predicate toPredicate(Root<UserRole> root,
CriteriaQuery<?> query,
CriteriaBuilder criteriaBuilder) {
if (criteria.getOperation().equalsIgnoreCase("eq")) {
String key = criteria.getKey();
Path path;
if (key.contains(".")) {
String attributeName1 = key.split("\\.")[0];
String attributeName2 = key.split("\\.")[1];
path = root.get(attributeName1).get(attributeName2);
} else {
path = root.get(key);
}
if (path.getJavaType() == String.class) {
return criteriaBuilder.like(path, "%" + criteria.getValue() + "%");
} else {
return criteriaBuilder.equal(root.get(key), criteria.getValue());
}
}
return null;
}

Multi column search using Specifications Spring Data Jpa within associated entity?

I am taking this question Perform multi column search on Date, Integer and String Data type fields of Single Table? and This method must return a result of type Specification<Employee> in Java 8 further ahead.
Actually I wanted to search within association entity as well as a part of global search. Will that be possible using JPA 2 Specifications API ?
I've Employee and Department #OneToMany bi-directional relationship.
Employee.java
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Entity
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "EMPLOYEE_ID")
private Long employeeId;
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "LAST_NAME")
private String lastName;
#Column(name = "EMAIL_ID")
private String email;
#Column(name = "STATUS")
private String status;
#Column(name = "BIRTH_DATE")
private LocalDate birthDate;
#Column(name = "PROJECT_ASSOCIATION")
private Integer projectAssociation;
#Column(name = "GOAL_COUNT")
private Integer goalCnt;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "DEPT_ID", nullable = false)
#JsonIgnore
private Department department;
}
Department.java
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Entity
public class Department implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "DEPT_ID")
private Long departmentId;
#Column(name = "DEPT_NAME")
private String departmentName;
#Column(name = "DEPT_CODE")
private String departmentCode;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "department")
#JsonIgnore
private Set<Employee> employees;
}
and I saved Data like below.
MyPaginationApplication.java
#SpringBootApplication
public class MyPaginationApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(MyPaginationApplication.class, args);
}
#Autowired
private EmployeeRepository employeeRepository;
#Autowired
private DepartmentRepository departmentRepository;
#Override
public void run(String... args) throws Exception {
saveData();
}
private void saveData() {
Department department1 = Department.builder()
.departmentCode("AD")
.departmentName("Boot Depart")
.build();
departmentRepository.save(department1);
Employee employee = Employee.builder().firstName("John").lastName("Doe").email("john.doe#gmail.com")
.birthDate(LocalDate.now())
.goalCnt(1)
.projectAssociation(2)
.department(department1)
.build();
Employee employee2 = Employee.builder().firstName("Neha").lastName("Narkhede").email("neha.narkhede#gmail.com")
.birthDate(LocalDate.now())
.projectAssociation(4)
.department(department1)
.goalCnt(2)
.build();
Employee employee3 = Employee.builder().firstName("John").lastName("Kerr").email("john.kerr#gmail.com")
.birthDate(LocalDate.now())
.projectAssociation(5)
.department(department1)
.goalCnt(4)
.build();
employeeRepository.saveAll(Arrays.asList(employee, employee2, employee3));
}
}
EmployeeController.java
#GetMapping("/employees/{searchValue}")
public ResponseEntity<List<Employee>> findEmployees(#PathVariable("searchValue") String searchValue) {
List<Employee> employees = employeeService.searchGlobally(searchValue);
return new ResponseEntity<>(employees, HttpStatus.OK);
}
EmployeeSpecification.java
public class EmployeeSpecification {
public static Specification<Employee> textInAllColumns(Object value) {
return (root, query, builder) -> builder.or(root.getModel().getDeclaredSingularAttributes().stream()
.filter(attr -> attr.getJavaType().equals(value.getClass()))
.map(attr -> map(value, root, builder, attr))
.toArray(Predicate[]::new));
}
private static Object map(Object value, Root<?> root, CriteriaBuilder builder, SingularAttribute<?, ?> a) {
switch (value.getClass().getSimpleName()) {
case "String":
return builder.like(root.get(a.getName()), getString((String) value));
case "Integer":
return builder.equal(root.get(a.getName()), value);
case "LocalDate":
return builder.equal(root.get(a.getName()), value);//date mapping
default:
return null;
}
}
private static String getString(String text) {
if (!text.contains("%")) {
text = "%" + text + "%";
}
return text;
}
}
When I hit the /employees/{searchValue}, I want searching to be happened in Department Table along with Employee table (may be using Joins something like that). Is that possible ? If yes, how can we do that ?
Or:
Will this be good approach to put like here? Got reference from Using #Query
#Query("SELECT t FROM Todo t WHERE " +
"LOWER(t.title) LIKE LOWER(CONCAT('%',:searchTerm, '%')) OR " +
"LOWER(t.description) LIKE LOWER(CONCAT('%',:searchTerm, '%'))")
List<Todo> findBySearchTerm(#Param("searchTerm") String searchTerm);
Any pointers?
If you take a look at my post actually I have a solution for join
#Override
public Specification<User> getFilter(UserListRequest request) {
return (root, query, cb) -> {
query.distinct(true); //Important because of the join in the addressAttribute specifications
return where(
where(firstNameContains(request.search))
.or(lastNameContains(request.search))
.or(emailContains(request.search))
)
.and(streetContains(request.street))
.and(cityContains(request.city))
.toPredicate(root, query, cb);
};
}
private Specification<User> firstNameContains(String firstName) {
return userAttributeContains("firstName", firstName);
}
private Specification<User> lastNameContains(String lastName) {
return userAttributeContains("lastName", lastName);
}
private Specification<User> emailContains(String email) {
return userAttributeContains("email", email);
}
private Specification<User> userAttributeContains(String attribute, String value) {
return (root, query, cb) -> {
if(value == null) {
return null;
}
return cb.like(
cb.lower(root.get(attribute)),
containsLowerCase(value)
);
};
}
private Specification<User> cityContains(String city) {
return addressAttributeContains("city", city);
}
private Specification<User> streetContains(String street) {
return addressAttributeContains("street", street);
}
private Specification<User> addressAttributeContains(String attribute, String value) {
return (root, query, cb) -> {
if(value == null) {
return null;
}
ListJoin<User, Address> addresses = root.joinList("addresses", JoinType.INNER);
return cb.like(
cb.lower(addresses.get(attribute)),
containsLowerCase(value)
);
};
}
private String containsLowerCase(String searchField) {
return "%" + searchField.toLowerCase() + "%";
}
Here you can see how I search the users by their address columns (city and street).
EDIT: Also you cannot use the #Query annotation that much dinamically (you van insert parameter values dinamically, but not parameters. That's where Specificaion is handy)
EDIT2: I know this is not the 2.x.x Spring version, but 1.5.x, but the idea is the same for joins.

Dynamic type caste Spring pathvariable

I am planning to create one simple Spring Rest Service Project with JPA which will fetch the details from the database based on the entity name and entity id given in path variables.
consider following code.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.ds.dao.EntityDAO;
import com.ds.entities.Employees;
import javax.persistence.Entity;
#Controller
#RequestMapping("/")
public class DynaRestController {
#Autowired
EntityDAO entityDAO;
#RequestMapping(value = "{entityName}/{enityId}",method = RequestMethod.GET)
public #ResponseBody Object getEntity(#PathVariable("entityName") String entityName,#PathVariable("enityId") Object id) {
return entityDAO.getEntityById(entityName, id);
}
}
Entity DAO Class
public class EntityDAO {
#Autowired
EntityManager entityManager;
public Object getEntityById(String entityName, Object id) {
EntityType<?> entityType = getEntityByName(entityName);
Object idcasted = entityType.getIdType().getJavaType().cast(id);
System.out.println(idcasted.getClass().getName());
Object entity = entityManager.find(entityType.getJavaType(), idcasted);
System.out.println("Entity.. Name .." + entityName);
// Employees entity = session.load(Employees.class, id);
return entity;
}
private EntityType<?> getEntityByName(String name) {
Set<EntityType<?>> entities = entityManager.getMetamodel().getEntities();
for (Iterator<EntityType<?>> iterator = entities.iterator(); iterator.hasNext();) {
EntityType<?> entityType = (EntityType<?>) iterator.next();
if (entityType.getName().equals(name))
return entityType;
}
return null;
}
}
Employees Class
#Configurable
#Entity
#Table(name = "employees", catalog = "employees")
public class Employees implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private int empNo;
private Date birthDate;
private String firstName;
private String lastName;
private String gender;
private Date hireDate;
private Set<Titles> titleses = new HashSet<Titles>(0);
private Set<Salaries> salarieses = new HashSet<Salaries>(0);
private Set<DeptEmp> deptEmps = new HashSet<DeptEmp>(0);
private Set<DeptManager> deptManagers = new HashSet<DeptManager>(0);
public Employees() {
}
public Employees(int empNo, Date birthDate, String firstName, String lastName, String gender, Date hireDate) {
this.empNo = empNo;
this.birthDate = birthDate;
this.firstName = firstName;
this.lastName = lastName;
this.gender = gender;
this.hireDate = hireDate;
}
public Employees(int empNo, Date birthDate, String firstName, String lastName, String gender, Date hireDate,
Set<Titles> titleses, Set<Salaries> salarieses, Set<DeptEmp> deptEmps, Set<DeptManager> deptManagers) {
this.empNo = empNo;
this.birthDate = birthDate;
this.firstName = firstName;
this.lastName = lastName;
this.gender = gender;
this.hireDate = hireDate;
this.titleses = titleses;
this.salarieses = salarieses;
this.deptEmps = deptEmps;
this.deptManagers = deptManagers;
}
#Id
#Column(name = "emp_no", unique = true, nullable = false)
public int getEmpNo() {
return this.empNo;
}
public void setEmpNo(int empNo) {
this.empNo = empNo;
}
#Temporal(TemporalType.DATE)
#Column(name = "birth_date", nullable = false, length = 10)
public Date getBirthDate() {
return this.birthDate;
}
public void setBirthDate(Date birthDate) {
this.birthDate = birthDate;
}
#Column(name = "first_name", nullable = false, length = 14)
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
#Column(name = "last_name", nullable = false, length = 16)
public String getLastName() {
return this.lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
#Column(name = "gender", nullable = false, length = 2)
public String getGender() {
return this.gender;
}
public void setGender(String gender) {
this.gender = gender;
}
#Temporal(TemporalType.DATE)
#Column(name = "hire_date", nullable = false, length = 10)
public Date getHireDate() {
return this.hireDate;
}
public void setHireDate(Date hireDate) {
this.hireDate = hireDate;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "employees")
public Set<Titles> getTitleses() {
return this.titleses;
}
public void setTitleses(Set<Titles> titleses) {
this.titleses = titleses;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "employees")
public Set<Salaries> getSalarieses() {
return this.salarieses;
}
public void setSalarieses(Set<Salaries> salarieses) {
this.salarieses = salarieses;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "employees")
#JsonBackReference
public Set<DeptEmp> getDeptEmps() {
return this.deptEmps;
}
public void setDeptEmps(Set<DeptEmp> deptEmps) {
this.deptEmps = deptEmps;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "employees")
public Set<DeptManager> getDeptManagers() {
return this.deptManagers;
}
public void setDeptManagers(Set<DeptManager> deptManagers) {
this.deptManagers = deptManagers;
}
}
When i am dynamically casting the path variable by using following code
Object idcasted = entityType.getIdType().getJavaType().cast(id);
Object entity = entityManager.find(entityType.getJavaType(), idcasted);
it is throwing ClassCastExpcetion
java.lang.ClassCastException: Cannot cast java.lang.String to int
at java.lang.Class.cast(Class.java:3369) ~[na:1.8.0_112]
at com.techm.att.ds.dao.EntityDAO.getEntityById(EntityDAO.java:33) ~[classes/:na]
at com.techm.att.ds.dao.EntityDAO$$FastClassBySpringCGLIB$$8e64d745.invoke() ~[classes/:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:738) ~[spring-aop-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~[spring-aop-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136) ~[spring-tx-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.13.RELEASE.jar:4.3.13.RELEASE]
any Help will be highly appriciated..
I write you a simple example regarding the comments.
This is the same behavior. Your RestController gets actually a string:
public static void main(String[] args) {
Object myString = "myString";
System.out.println(myString.getClass()); // class java.lang.String
int.class.cast(myString);
}
The cast method checks the instanceof your given value and it fails:
public T cast(Object obj) {
if (obj != null && !isInstance(obj))
throw new ClassCastException(cannotCastMsg(obj));
return (T) obj;
}

Getting ConstraintViolationException while saving a row with embedded key in the table with many-to-many mapping between two entities using Spring JPA

In our spring boot Restful WebService, we have two master tables with many-to-many relationship between them. But in the transaction table, we want one extra field (current_time) as part of the embedded key other than the primary keys of the two tables. Now, we’ve created a separate class for defining embedded primary key using #Embeddable. Now, while inserting one transaction row to transaction table using Spring JPA, I am manually setting the primary keys in the corresponding entity and calling the save method on corresponding repository. But It is giving me ConstraintViolationException as the current_time is going with null value even if I have manually set it. Any help would be highly appreciated.
First Entity is as follows :
#Entity
#Table(name = "project")
public class Project {
#Id
#GenericGenerator(name = "projectid", strategy = "com.sample.upload.entity.ProjectIDGenerator")
#GeneratedValue(generator = "projectid")
#Column(name = "projectid")
private String projectID;
#Column(name = "project_name")
private String projectName;
#Column(name = "project_descr")
private String projectDesc;
#Column(name = "project_input_path")
private String projectPath;
#Column(name = "project_creation_time")
private Calendar projectCreationTime;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "project_migration", joinColumns = #JoinColumn(name = "projectid", referencedColumnName = "projectid"), inverseJoinColumns = #JoinColumn(name = "migratorid", referencedColumnName = "migratorid"))
private List<Migrator> migrators;
#Column(name = "account_name")
private String accountName;
#Column(name = "account_group")
private String accountGroup;
public String getProjectID() {
return projectID;
}
public void setProjectID(String projectID) {
this.projectID = projectID;
}
public String getAccountName() {
return accountName;
}
public void setAccountName(String accountName) {
this.accountName = accountName;
}
public String getAccountGroup() {
return accountGroup;
}
public void setAccountGroup(String accountGroup) {
this.accountGroup = accountGroup;
}
public String getProjectName() {
return projectName;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
public String getProjectDesc() {
return projectDesc;
}
public void setProjectDesc(String projectDesc) {
this.projectDesc = projectDesc;
}
public String getProjectPath() {
return projectPath;
}
public void setProjectPath(String projectPath) {
this.projectPath = projectPath;
}
public Calendar getProjectCreationTime() {
return projectCreationTime;
}
public void setProjectCreationTime(Calendar projectCreationTime) {
this.projectCreationTime = projectCreationTime;
}
public List<Migrator> getMigrators() {
return migrators;
}
public void setMigrators(List<Migrator> migrators) {
this.migrators = migrators;
}
}
Second Entity :
#Entity
#GenericGenerator(name = "generatorName", strategy = "increment")
#Table(name = "migrator")
public class Migrator {
#Id
#GeneratedValue(generator = "generatorName")
#Column(name = "migratorid")
private String migratorId;
#Column(name = "src_tech_name")
private String srcTechName;
#Column(name = "dest_tech_name")
private String destTechName;
#Column(name = "migrator_name")
private String migratorName;
#Column(name = "migrator_type")
private String migratorType;
public String getMigratorId() {
return migratorId;
}
public void setMigratorId(String migratorId) {
this.migratorId = migratorId;
}
public String getSrcTechName() {
return srcTechName;
}
public void setSrcTechName(String srcTechName) {
this.srcTechName = srcTechName;
}
public String getDestTechName() {
return destTechName;
}
public void setDestTechName(String destTechName) {
this.destTechName = destTechName;
}
public String getMigratorName() {
return migratorName;
}
public void setMigratorName(String migratorName) {
this.migratorName = migratorName;
}
public String getMigratorType() {
return migratorType;
}
public void setMigratorType(String migratorType) {
this.migratorType = migratorType;
}
#Override
public String toString() {
return "Technology [migratorId=" + migratorId + ", srcTechName=" + srcTechName + ", destTechName="
+ destTechName + ", migratorName=" + migratorName + ", migratorType=" + migratorType + "]";
}
}
The join (transaction) table's entity :
#Entity
#Table(name = "project_migration")
public class ProjectMigration {
#EmbeddedId
private ProjectMigrationID migrationId;
#Column(name ="migration_finish_time")
private Calendar migrationFinishTime;
#Column(name ="time_in_millis_for_migration")
private long timeInMillisForMigration;
#Column(name ="migration_status")
private String migrationStatus;
#Column(name ="migrated_codebase_path")
private String migratedCodeBasePath;
The embedded Primary Key class is as follows:
#Embeddable
public class ProjectMigrationID implements Serializable {
private static final long serialVersionUID = -3623993529011381924L;
#Column(name = "projectid")
private String projectId;
#Column(name = "migratorid")
private String migratorId;
#Column(name = "migration_start_time")
private Calendar migrationStartTime;
public ProjectMigrationID() {
}
public ProjectMigrationID(String projectId, String migratorId, Calendar migrationStartTime) {
this.projectId = projectId;
this.migratorId = migratorId;
this.migrationStartTime = migrationStartTime;
}
The snippet from service Class :
for (String migratorId : data.getMigratorIds()) {
Migrator migrator = migratorRepository.findByMigratorId(migratorId);
migrators.add(migrator);
}
if (projectId != null) {
project = projectRepository.findByProjectID(projectId);
System.out.println(project==null);
project.setMigrators(migrators);
System.out.println("I am here");
if (project != null) {
//project.setMigrationStatus("In Progress");
ProjectMigrationID pmId = new ProjectMigrationID();
pmId.setProjectId(project.getProjectID());
pmId.setMigratorId(project.getMigrators().get(0).getMigratorId());
pmId.setMigrationStartTime(new GregorianCalendar());
ProjectMigration pm = new ProjectMigration();
pm.setMigrationId(pmId);
pm.setMigrationStatus("Pending");
projectMigrationRepository.save(pm);
That's because of the #JoinTable where the date is not included and it skips the insertion. If you include a column with all the primary keys needed, it will work as expected.
Only the columns mapped via #JoinTable will be included during insertion or update (defaults to true when mapped)
Either include the date time column in the Project class or use association without #JoinTable.
I'm editing via mobile. So please ignore typos if any.

Resources