Access Few Columns of a table from CrudRepository in Spring Boot - java

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

Related

Convert a string to an enum description in a JPA projection interface

I have a projection interface used in a native query in Spring JPA, one of the values ​​mapped as String is an enum inside Java.
I would like to get the enum description field based on the string returned from the database in my projection interface, I thought of a custom annotation for that, but I didn't come up with a construction idea.
Can anyone share a solution to this scenario.
Ex My Enum
#AllArgsConstructor
#Getter
public enum PersonType {
PHYSICAL_PERSON("F","Physical Person"),
LEGAL_PERSON("J","Legal Person");
private String code;
private String description;
}
My inteface projection
public interface PersonProjection {
Long getId();
String getName();
String getObs();
String getPersonType();
}
My repository
#Query(value = "select p.id as id, p.description as name, p.obs as obs, p.type as personType from public.person p where p.code = :code", nativeQuery = true)
List<PersonProjection> findByCode(#Param("code") Long code);
My interface is being returned from the database like this:
id=1,name="Mike",obs="Tests",personType="F";
Is there any way in this projection interface I can use an annotation where I can get the description of the enum by the value of the returned string
Ex:
public interface PersonProjection {
Long getId();
String getName();
String getObs();
#DescriptionEnum(PersonType.class)
String getPersonType();
}
In the domain class with the JPA annotations I can, but since I'm out, returning the data in an interface like a DTO, I can't get the same resource.

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");

How to get the required fields data from an Entity to VO object using Spring Data MongoDB

In Spring Data Jpa we have a flexibility to map the required fields query result to a VO object with out exposing the entity object outside.
DB Used :: mysql
1st way ::
#Query("SELECT new CountryVO(c.id,c.name) FROM Country c")
public List<CountryVO> getCountries();
//Country Entity
public class Country{
private long id;
private String name;
#DBRef
private State state;
}
// State Entity
public class State{
private long id;
private String name;
}
//Country VO
public class CountryVO{
private long id;
private String name;
private String stateName;
}
2nd way ::
#Query("SELECT DISTINCT c.name FROM Country c")
public List<String> getNames();
Now my point is I am using Spring Data mongoDB with mongoDB database and here the querying way is different like below
#Query(value = "{}", fields = "{'id':1,'name':1,'state.name':1}")
List<CountryVO> getAllCountries();
In the above query we can mention what ever fields we want in the fields attribute and the remaining fields will come as null values, but I want to map the output result to a VO like Spring Data Jpa.
Please let me know any possibilities
Thanks in Advance
Just use your CountyVO as return type:
#Query(value = "{}", fields = "{'id':1,'name':1}")
List<CountryVO> getAllCountries();

How to use projecting types in Spring Data MongoDB repository's query methods?

I have been playing around with Spring Data and MongoDB and have a question about limiting the amount of data for certain queries. I've seen adding custom queries within the MongoRepository, however I haven't seen any examples of limiting the amount of data and returning classes that are basically a subset of larger classes.
For instance I have a User class that has several fields, but I also want to create a UserShort class that has a subset of the fields in the User class. For instance UserShort would only contain the id and firstName / lastName / email fields, rather than everything.
I've seen I can specify/limit the fields that are returned, but can I have those returned into a different class? At the moment the UserShort will return null unless I specify the User class instead, but the fields will be limited to the ones I specify. Wasn't sure if this is possible at all? I realize the User class below isn't huge, but it's the concept I'm after.
A user interface:
public interface Users {}
Subset class:
public class UserShort implements Users {
#Id
private String id;
private String firstName;
private String lastName;
#Indexed(unique = true)
private String email;
//getters / setters
}
Full class:
#Document
public class User implements Users {
#Id
private String id;
private String firstName;
private String lastName;
private String username;
private String password;
private Date dob;
private Status status;
#Indexed(unique = true)
private String email;
private Gender gender;
private String locale;
private Date registerDate;
#DBRef
private List<UserAddress> addresses;
public User(){
addresses = new ArrayList<UserAddress>();
}
//getters / setters
}
And the repository interface:
public interface UserRepository extends MongoRepository<Users, String> {
public User findByEmail(String email);
#Query(value="{ 'email' : ?0 }", fields="{ 'firstName' : 1, 'lastName' : 1}")
public UserShort findUserShortByEmail(String email);
}
As long as the return type of the query method is assignable to the managed domain type (Users in your case) we will prefer the return type to determine the collection to run the query against. Thus, in your case we'd execute the query against userShort instead of users which is why you do not get any results. That behavior is in place to support storing inheritance hierarchies into different collections.
If you switched to User as the domain type for the repository, things should work exactly as expected. This would also have the benefit of preventing clients from handing UserShort instances to the save(…) method which will wipe out properties contained in User but not in UserShort. Here's the final repository interface declaration.
interface UserRepository extends MongoRepository<User, String> {
User findByEmail(String email);
#Query(value="{ 'email' : ?0 }", fields="{ 'firstName' : 1, 'lastName' : 1}")
UserShort findUserShortByEmail(String email);
}
P.S.: #byte-crunch outlined in the comments that this currently only works for collections of projections but not for returning single instances. This has been reported and fixed in DATAMONGO-1030 and will be available in 1.5.4 and 1.6.0 GA.

Categories