Java Spring Rest Api Custom Repository - java

So I have a student class
#Table(name = "student")
public class Student {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private String firstname;
private String lastname;
private String idnumber;
private String adress;
private boolean active=Boolean.TRUE;
#OneToMany( mappedBy="student")
private Set<Contact> contacts = new HashSet<>();
#ManyToMany(mappedBy = "students")
private Set<Team> teams = new HashSet<>();
}
and I have repository
public interface StudentRepository extends JpaRepository<Student,Long> {
}
I want to create customrepository to find student idnumber, and while creating new student it will check all students if idnumber already exists throw exception.

You can use the jpa repository method
studentRepo.findById(studentId)
To fetch a record in the database with the studentId.
If you want it to find the record exists or not you can make the method return null and check whether the object is null or not and throw the appropriate exception. Like in the below example.
Student student = studentRepo.findById(studentId).orElse(null);
if(student == null)
throw new CustomException("student does not exist");

No need to create a new repo as customrepository to implement custom query. You can write a new method to retrieve data.
public interface StudentRepository extends JpaRepository<Student, Long> {
// using JPA method
Optional<Student> findByIdnumber(String idNumber);
// using JPA query | this is an optional method in case you need to implement any query in future
// ?1 represents the first parameter of the method, which is 'idNumber'
#Query(value = "SELECT s.id FROM Student s WHERE s.idnumber=?1", nativeQuery = true)
String findIdByIdnumber(String idNumber);
}
Through the service you can check whether a record is available or not and perform a respective action (throw an exception).

Related

Spring JPA - read a list of entities (with only partial properties) and update value without SELECTING the whole object from database?

I'm using Spring Boot 1.5.2. I have a big Entity with multiple properties and multiple OneToMany relations, for example:
#Entity
#Table(name = "person")
class Person {
#Id
protected long id;
private String property1;
private String property2;
private String property3;
private String property4;
private String property5;
private String property6;
#OneToMany
private List<Obj1> obj1List;
#OneToMany
private List<Obj2> obj2List;
#OneToMany
private List<Obj3> obj3List;
#OneToMany
private List<Obj4> obj4List;
}
How can I read the list of Person from database, but only with 2 properties id and property2, and update property2=0.
Then, I can use JPA CrudRepository to save():
public interface PersonRepository extends CrudRepository<Person, Long> {
}
for (Person person : personList) {
this.personRepository.save(person)
}
I don't want to use findAll() from CrudRepository which enables Hibernate to SELECT the whole list of Person with a big SQL query before saving to database.
Inside your PersonRepository interface, you should be able to add a Query like this:
#Query("select new Person(id, property2) from Person")
List<Person> findIdAndProperty2();
The other fields should come back null as they haven't been specified in your query. You'll just need to add a constructor to Person with id and property2 as arguments.
For updating, you can use similar syntax...
#Modifying
#Query("update Person set property2 = ?1 where id in ?2")
int updateProperty2(String property2, List<Long> ids);
com.google.common.collect.Iterables.partition can be used to process your updates in chunks. For instance...
for (List<Long> curUpdateIds : Iterables.partition(ids, 1000)) {
personRepository.updateProperty2("0", curUpdateIds);
}

How to prevent automatic update in Spring Data JPA?

In my Spring boot batch application, I am calling a JPA repository class from Tasklet.
The JPA call retrieves a particular value (Entity object) from DB. The problem is, If I update some value in the entity object, once the control goes out of Tasklet, it automatically updates to DB even though I am not calling any save operation. How to prevent this? Default JPA implementation is Hibernate.
Tasklet class
Employee employee = employeeRepository.fetchEmployee(employeeName);
List<Address> addressList = employee.getAddress();
addressList.forEach(e -> e.setStatus(Status.INVALID.toString()));
Repository
#Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
#Query("select em from Employee em where em.employeeName = :employeeName")
public Employee fetchEmployee(#Param("employeeName") Long employeeName);
}
Entity class
#Entity
#Table(name = "Employee")
public class Employee implements java.io.Serializable {
private static final long serialVersionUID = -3769636546619492649L;
private Long id;
private List<Address> address;
private String employeeName;
// Getters and setters
// #OneToMany mapping to Address
}
Even though I am not calling a .save() operation, it automatically updates Address table Status to "INVALID"
This happen because the entity is not in detached state. In EJB we can do this in the following way.
EJB solution
#Query(value = "select * from Employee WHERE EmployeeName = ?1", nativeQuery = true)
#TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public List<Employee> fetchEmployee(String employeeName);
This will make the transaction closed. Changes you make to entity will not get saved in DB
Spring JPA
After a bit of research i found JPA doesn't provide the detach functionality out of the box.
Refer : https://github.com/spring-projects/spring-data-jpa/issues/641
To make it work we can have a custom JPA repository which overrides detach method. An example is given in this link.
https://www.javaer101.com/en/article/1428895.html
Use Deep cloning to solve your issue.
First override the clone method inside your Address class like below.
Note : Please customize the implementation of clone() method by adding your class attributes.Since you didn't mention the structure of the class Address , I have implemented the solution with my own defined class attributes.
Address class
public class Address {
private String country;
private String city;
private String district;
private String addressValue;
public Address() {
super();
}
public Address(String country, String city, String district, String addressValue) {
super();
this.country = country;
this.city = city;
this.district = district;
this.addressValue = addressValue;
}
//Getters and Setters
#Override
protected Object clone() {
try {
return (Address) super.clone();
} catch (CloneNotSupportedException e) {
return new Address(this.getCountry(), this.getCity(), this.getDistrict(),this.getAddressValue());
}
}
}
Then re construct your class Tasket like below.
Tasket Class
Employee employee = employeeRepository.fetchEmployee(employeeName);
List<Address> addressList = employee.getAddress();
List<Address> clonedAddressList = new ArrayList<>();
addressList.forEach(address -> clonedAddressList.add((Address)address.clone()) );
clonedAddressList.forEach(address -> address.setStatus(Status.INVALID.toString()));

Subsequent query execution in JPA

Let's assume there is a class named Person with following structure in spring boot
#Entity
class Person {
Long id;
String name;
#OneToMany
Set<PhoneNumber> phoneNumbers;
}
Person consists of set of phone numbers.
#Entity
class PhoneNumber {
Long id;
#ManyToOne
#JoinByColumn("person_id")
Person person;
String category;
String mobileNumber;
String phoneNumber;
}
PhoneNumber is a class which consists of above fields where category represents mobile or phone etc.
class PersonRepository extends JPARepository<Person, Long> {
Person findById(Long id);
}
So, whenever I want to fetch Person details with some id, I will call the above method findById , then it should fetch Person details along with phoneNumbers whose category is mobile.
The approach should be whenever it executes query internally for the findById method, it should execute subsequent query for fetching PhoneNumber whose category is mobile.
Is there any way I can get it as mentioned above or is there any other approach for achieving it? Please let me know.
PS: If there are any issues or errors in my way of asking please comment below. It will help me.
You can get it. Refer this:
Repo:
class PersonRepository extends JPARepository<Person, Long>,JpaSpecificationExecutor<Post> {
Person findById(Long id);
}
public static Specification<Person> search(Long id) {
return ((root, criteriaQuery, criteriaBuilder) -> {
criteriaQuery.distinct(true);
return criteriaBuilder.and(
criteriaBuilder.equal(root.get("id"), id),
criteriaBuilder.equal(root.join("phone_number").get("category"), "mobile")
);
});
}
personRepo.findAll(search(10));
You can try this method-
// method in Person entity class
public static List<Person> findByIdMobile(long id, String category) {
return find("id = ?1 and phoneNumbers.category = ?2", id, category).list();
}
// can use this as
List<Person> mobilePersons = Person.findByIdMobile(1234,"mobile");

Access Few Columns of a table from CrudRepository in Spring Boot

I have an interface which is extending crud repository
public interface PersonRepo extends CrudRepository<Person, String> {
#Query(value="select name from PERSON where addr=?1", nativeQuery = true)
List<Person> getPeronUsingAddress(String addr);
}
Person entity looks like this:
class Person {
private String name;
private String phoneNumber;
private String address;
//along with getters setters and all basic hibernate annotation to persist and retrieve
}
the person object is saved into the databse and at the time of retrieving the native query is working fine as hibernate executes correct query. But I am not able to get the return type.
If the return type is List of Person then I am getting InvalidDataAccessResourceUsageException
If I create an interface and use the list of interface as return type like
interface response {
String getName();
}
List of Response interface getPeronUsingAddress(String addr);
then I am getting proxy object in the service. I am not able to get the datas from proxy object.
Another approach I did is to use List of object as return type. But it is not possible to downcast to my Person Object.
How to do that.? Or is there any other solution by which I can return selective columns from crud repository and get a Java object with those selected Columns.
In order to fetch selected columns from an entity, you can do like below :
class Person {
private Integer id;
private String name;
private String phoneNumber;
private String address;
//along with getters setters and all basic hibernate annotation to persist and retrieve
}
Create a DTO or Java Object like below :
public class PersonDTO {
private Integer id;
private String name;
private String phoneNumber;
private String address;
public PersonDTO(Integer id, String name, String phoneNumber, String address) {
// logic here
}
//If you want just want name and phone number.
public PersonDTO(String name, String phoneNumber) {
// logic here
}
// you can't create overridden constructors as all members are of same type and at runtime program won't be able to differentiate unless you provide some logic for it.
// getters, setters, any other methods here...
}
Now below will be you Query but it's not native, if you want to keep native query then you will need to use ResultTransformer like here
#Query("select new your.package.PersonDTO(p.name, p.phoneNumber) from Person p where p.id = :id")
public PersonDTO getPersonById(Integer id);

How to search with JpaRepository and nested list of objects?

Description
There is a PersonRepository and Person entity,
Person class contains List<Qualification>. Qualification class has 3 simple fields.
I have tried to add #Query annotation on custom method and use JPQL to get the results, but Qualification class fields were not available for manipulation in JPQL as it repository itself contains List<Qualification> instead of just a simple field of Qualification.
How can I search by these Qualification's nested fields?
Query
Now I need to find list of person entity where qualification's experienceInMonths is greater than 3 and less than 9 AND qualification's name field = 'java'.
Code
Person.java
#Data
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private String id;
#NotEmpty
#Size(min = 2)
private String name;
#NotEmpty
#Size(min = 2)
private String surname;
#ElementCollection(targetClass = java.util.ArrayList.class, fetch = FetchType.EAGER)
private List<Qualification> qualifications = new ArrayList<>();
}
PersonRepository.java
#Repository
public interface PersonRepository extends JpaRepository<Person, String> {
}
Qualification.java
#Data
#AllArgsConstructor
public class Qualification implements Serializable {
#Id #GeneratedValue
private String id;
private String name;
private String experienceInMonths;
}
EDIT: not duplicate of this post, as here is the collection of nested objects. Not just single reference.
First, change experienceInMonths from String to int (otherwise you can not compare the string with the number). Then you can try to use this 'sausage':
List<Person> findByQualifications_experienceInMonthsGreaterThanAndQualifications_experienceInMonthsLessThanAndName(int experienceGreater, int experienceLess, String name);
Or you can try to use this pretty nice method:
#Query("select p from Person p left join p.qualifications q where q.experienceInMonths > ?1 and q.experienceInMonths < ?2 and q.name = ?3")
List<Person> findByQualification(int experienceGreater, int experienceLess, String name);
W can use ‘_’ (Underscore) character inside the method name to define where JPA should try to split.
In this case our method name will be
List<Person> findByQualification_name(String name);

Categories