Spring JPA - Find By EmbeddedId partially - java

Below code is for demo purpose only.
My Entity bean looks like this
#Entity
class Employee {
#EmbeddedId
private EmployeeKey employeeKey;
private String firstName;
private String lastName;
// Other fields
// Getter and Setters
}
The Embeddable class:
#Embeddable
class EmployeeKey implements Serializable {
private int employeeId;
private String branchName;
private String departmentName;
//Getter and Setters
}
I can write JPARepository interface method to find Employees by the EmbeddedId that returns me results as well.
interface EmployeeRepository extends JpaRepository<Employee, EmployeeKey> {
List<Employee> findByEmployeeKey(EmployeeKey employeeKey);
}
Question:
Suppose, while querying I have employeeId and branchName only, and I don't want to put filter on departmentName
In such cases how can I write my Repository method
Does JPA have something in-build for such scenario?

List<Employee> findByEmployeeKeyEmployeeIdAndEmployeeKeyBranchName(
int employId,
String branchName);
Should work Have a look at query derivation

Here is how it worked for me.
#Ketrox's answer is absolutely correct and works fine. But in my real scenario I had 6 fields to search by and which resulted in an 120+ characters long method name. (Something like below)
List<Employee> findByEmployeeKeyField1AndEmployeeKeyField2AndEmployeeKeyField3AndEmployeeKeyField4AndEmployeeKeyField5AndEmployeeKeyField6(String field1, String field2, String field3, String field4, String field5, String field6);
Which is certainly not good enough to read and more than good enough to make codenarc unhappy.
Finally I used find by example and that turned out to be really pleasant solution.
Repository:
//skipped lines
import org.springframework.data.domain.Example
//skipped lines
interface EmployeeRepository extends JpaRepository<Employee, EmployeeKey>{
List<Employee> findAll(Example<Employee> employee);
}
Usage:
// Prepare Employee key with all available search by keys (6 in my case)
EmplyeeKey key = new EmplyeeKey();
key.setField1("field1_value");
key.setField2("field2_value");
//Setting remaining 4 fields
// Create new Employee ans set the search key
Employee employee = new Employee();
employee.setEmployeeKey(key);
// Call the findAll by passing an Example of above Employee object
List<Employee> result = employeeRepository.findAll(Example.of(employee));
I have elaborated the search by Spring Data JPA find by #EmbeddedId Partially

Related

Returning nested custom classes from Spring JPA, Hibernate

I have a Student class.
#Entity
public class Student {
private long id;
private String name;
private String department;
private String subDepartment;
private int marks;
//getters, setters and constructors.
}
I want to be able to get min, max and average marks grouped by department and then subDepartment.
My repository:
public class StudentRepository extends JpaRepository<Student,Long> {
#Query("Select s.department, s.subDepartment , min(marks), max(marks), avg(marks) from Student as s groupby s.department, s.subDepartment")
List<Object[]> getStatsByDepartmentAndSubDepartment()
}
Of course, this works but I want to avoid using List<Object[]> and instead get something like:
Map<String, Map<String,Stats>> class
Where first key is Department, second key is subDepartment and Stats class encapsulates min,max,average
Is there a way to do this with Spring JPA. ? I checked and nested classes are not possible.
How about Hibernate or any other solution? I am a newbie when it comes to Hibernate.
I am using Spring boot and can add any dependency needed. Any database is ok, even H2 for a start.
You cannot return a map.
You can convert the rows using a class DTO.
Class DTO with constructor
You can also create a class DTO:
class StudentDTO {
public StudentDTO(String dep, String subDep, Integer min, Integer max, Double avg ) {
this.stats = new Stats(min, max, avg);
...
}
}
And then call it in the select clause:
select new StudentDTO(s.department, s.subDepartment, avg(s.marks), ...) from ...
This will return a List<StudentDTO>.
As long as the constructor matches the select clause, it will work fine.
Interface DTO
You can also define DTO using interfaces, but I don't think it works if you have nested classes.
This will work though:
interface StudentViev {
String getDepartment();
String getSubdepartment();
Integer getMinMark();
Integer getMaxMark();
Double getAvgMark();
}
and now you can use it as type for the list:
#Query("Select s.department as department, s.subDepartment as subDepartment, min(marks) as minMark, max(marks) as maxMark, avg(marks) as avgMark from Student as s groupby s.department, s.subDepartment")
List<StudentView> getStatsByDepartmentAndSubDepartment()
Note that I've used aliases in the select cluase so that the column names returned match the getters in the interface

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

Crudrepository / Join single field from other table to entity as readonly

Bear with me for any mistakes I make, as this is my first question here.
I have a database with two tables, one table called: PERSON
with the following entity:
#Entity
class Person {
#Id
private String guid;
private String firstName;
private String organisationGuid;
...
...
}
And one table called: ORGANISATION
with the following entity:
#Entity
class Organisation {
#Id
private String guid;
private String name;
...
...
}
As you can see, every Person belongs to an Organisation.
And now I need to list all my persons, with the name of the organisation. I do not want the full Organisation-entity on the Person-entity, rather just the name. Like so:
[{
"guid": "xxx",
"firstName": "Name",
"organisationGuid": "yyy",
"organisationName": "Name of yyy"
}]
How can I accomplish this in the easiest way possible?
Things I have already tried:
1) Adding property to Person and modyfing select-statement
#Entity
class Person {
#Id
private String guid;
private String firstName;
private String organisationGuid;
private String organisationName;
...
...
}
--
#Repository
public interface PersonRepository extends CrudRepository<Person, String> {
#Query(nativeQuery = true, value = "select p.*, o.name as organisation_name from person p left join organisation o on p.organisation_guid = o.guid")
List<Person> findAll();
}
Result: This works fine when using findAll but as soon as I try to save a Person I get an error stating that column ORGANISATION_NAME does not exist.
2) OK, makes sense, so I tried to put #Transient on the field in the entity
#Entity
class Person {
...
#Transient
private String organisationName;
...
...
}
Result: Now it works to save the entity, but I never get the organisationName (as it is marked as Transient).
3) Well damn, then I try to use the annotation #ReadOnlyProperty
#Entity
class Person {
...
#ReadOnlyProperty
private String organisationName;
...
...
}
Result: Same error as in (1). I can findAll but as soon as I try to save a person entity hibernate reports that the column does not exist (because in order to save an item, hibernate first needs to select it, and this particular select does NOT use my own custom select I created in the repository).
4) So then I created a class called PersonOrganisation (with #Table(name="organisation")) with a #ManyToOne-relation from Person to PersonOrganisation, where PersonOrganisation is an entity with just two fields, guid and name.
Result: Error. I can findAll but as soon as I try to save a person entity hibernate reports that the organisationGuid does not match a PersonOrganisation in the database (as it seems that PersonOrganisation is not an Organisation the way Hibernate sees it).
Many things that can be improved here:
1) Add a relationship in the Person relating to the Organization:
#Entity
class Person {
#Id
private String guid;
private String firstName;
#ManyToOne
#JoinColumn(name = "organisationGuid")
private Organisation organisation;
2) create a Result Class which would be holding the projection results:
package com.mypkg;
#Entity
class PersonOrganization {
private String guid;
private String firstName;
private String organisationGuid;
private String organisationName;
public PersonOrganization(String guid, String firstName
, String organisationGuid, String organisationName){
// set the fields
}
}
3) Change the query (dont use native.. its not necessary):
#Repository
public interface PersonRepository extends CrudRepository<Person, String> {
#Query("select NEW com.mypkg.PersonOrganization(p.guid as guid ...
, o.name as organisationName)
from person p left join p.organisation o")
List<PersonOrganization> findPersonWithOrganization();
}
Remeber to add aliases to each result column to match the consturctor of the PersonOrganization class.
I started using the solution from Maciej above, but ran into problems when I didn't want to create an extra "projection" entity with the same fields as the original entity (there were 20 additional fields the on Person entity).
So I actually found another solution which I am very happy with.
1) I started out with adding a #ManyToOne in Person (like Maciej suggested)
#Entity
public class Person {
#Id
private String guid;
private String firstName;
#ManyToOne
private Organisation organisation;
...
...
}
2) I also added a custom serializer to the get-method for the Organisation on the Person entity:
#JsonSerialize(using = OrganisationLightSerializer.class)
public Organisation getOrganisation() {
return organisation;
}
The custom serializer is super simple:
public class OrganisationLightSerializer extends JsonSerializer<Organisation> {
#Override
public void serialize(Organisation organisation, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
throws IOException, JsonProcessingException {
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("name", organisation.getName());
jsonGenerator.writeEndObject();
}
}
3) Then I changed all find-queries in my repository and added join fetch, and with this I stopped hibernate from using (at least) two SQL-queries when fetching a list of Persons:
#Repository
public interface PersonRepository extends CrudRepository<Person, String> {
#Override
#Query("select p from Person p join fetch p.organisation o")
List<Person> findAll();
}
Result: I did not get the JSON-structure exactly the way I wanted it, but I managed to get just the pertinent information from each Organisation on each Person. The resulting JSON when fetching persons looks like so (as you can see I skipped the guid of the Organisation in the end, but it can easily be added again by just changing the custom serializer):
[{
"guid": "xxx",
"firstName": "Name",
"organisation": { name: "Name of yyy"}
}]
Disclaimer: Now I realize that my response here is not an exact answer to my own query, as I stated I wanted the JSON as a flat structure, but having an Organisation object in the JSON, which only contains the name of the Organisation is almost as good a solution. Should I edit/change/comment the question so that it reflects the actual answer here, or is this minor change an "acceptable" deviation of the requirements.

Naming repository method in springboot based on entity camelcase variable nam

I know in spring boot that creating a find method in the repository will be base on the variable name of the entity. How can I create a find method in the repository if the variable in my entity is in a camel case. This is what I created is this right?
class Person {
private int id;
private Teacher teacher; //other variable...getters and setters }
private BranchManager branchManager; //other variable...getters and setters }
class Teacher {
private String firstName ;
private String lastName; //getters and setters }
class BranchManager {
private String firstName ;
private String lastName; //getters and setters }
in my repository
#Repository
public interface PersonRepository extends CrudRepository<Person, Long>{
public Person findPersonByTeacherFirstName(String firstName);
//how to find Person by BranchManager firstName and lastName
}
First of all, you have not declared Teacher object in Person. Assuming that you will declare as follows
private Teacher teacher; // name of this object is important to create the repository methods
Try
List<Person> findByTeacher_FirstName(String name);
Note that it findBy returns a List.
If you want to return only the first record. You can use the following
Person findFirstByTeacher_FirstName(String name);
Also, Personally I prefer
Optional<Person> findFirstByTeacher_FirstName(String name);
as this will return Optional.empty() in case it couldn't find a record. Also, it will force you to check for its presence, thereby, avoiding NullPointerException.
Update 1
As requested in comment,
If you want to you want to find by firstName and lastName, you can use the following
Person findFirstByTeacher_FirstNameAndTeacher_LastName(String firstName, String lastName);
You can refer to this link for these conventions.
Update 2
Search by branchManager's firstName and lastName
Person findFirstByBranchManager_FirstNameAndBranchManager_LastName(String firstName, String lastName);
Update 3
As mentioned by Aluan Haddad in comments, this practice is not scalable.
As an option, I am giving an alternative way to hit queries to DB.
You can create an example object and pass it to findAll(Example e) implementation of JpaRepository.
final Teacher exampleTeacher = new Teacher();
exampleTeacher.setName("Teacher Name");
final Person examplePerson = new Person();
example.setTeacher(exampleTeacher);
Now pass this examplePerson to Repository using
List<Person> findAll(Example.of(examplePerson));
This will return all the persons whose teacher's name is Teacher Name.
And as an alternative to C# EntityFramework, you can use QueryDSL in Java.

Update or saveorUpdate in CRUDRepository

I know there's already a similar question answered previously, but my problem is implementing save with update while there are 3 methods in the interface.
I'm currently using the following methods in my project and don't know how to make saveOrUpdate in this.
The following are my classes:
public interface CompanyRepository extends CrudRepository<Company,Long>{
Company findByCompanyName (String companyName);
List<Company> findAll();
Company findById(Long id);
}
The following is part of my Company Class
#Entity
public class Company extends BaseEntity{
#NotNull
#Size(min = 2, max = 16)
private String companyName;
#Length(max = 60)
private String desc;
//TODO max: add LOGO class later for pic saving
#OneToMany
private List<MobileModel> mobileModels;
public Company()
{
super();
mobileModels = new ArrayList<>();
}
//Getters n setters
}
The following is my baseEntity clas
#MappedSuperclass
public abstract class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected final Long id;
#Version
private Long version;
//Getters n setters
}
Thanks in advance.
I read everywhere and tried so many things for 5 hours.
I just want CompanyRepository to implement all 3 methods without me overriding them in some other class but if I have too then explain how because part of my code is dependent on CompanyRepository. I just wish to add save with update, please explain with respect to my code.
CrudRepository has only save but it acts as update as well.
When you do save on entity with empty id it will do a save.
When you do save on entity with existing id it will do an update that means that after you used findById for example and changed something in your object, you can call save on this object and it will actually do an update because after findById you get an object with populated id that exist in your DB.
save in CrudRepository can accept a single entity or Iterable of your entity type.
putting below if check resolve my issue and save method is working as save and update both when i pass id it updates the record in database and when i dont pass id it save new record in database
place is incoming object in my case and new place is new object of place in which i am setting the place id
if(place.getPlaceId()!=0)
newPlace.setPlaceId(place.getPlaceId());

Categories