JPA CrudRepository for findIn method - spring

In one requirement I need find in a list by id. Below is the example.
class Employee{
Long id;
Contractor contractor;
}
class Contractor {
Long id;
List<Address> addressList;
}
class Address {
Long id;
String line;
}
Now in EmployeeController.java class I need to find out by employee Id and address Id.
I have CrudRepository as below.
interface EmployeeRepository extends CrudRepository<Employee, Long> {
Employee findOne(Long id);
// I want one method here that can find by employee id and address id.
Employee findByIdAndAddress();
}
When I try to run the spring application up it gives the below exception.
PropertyReferenceException: No property Address found for type Employee!
Thank you!

Well, the exception itself is quite clear, it tries to find a property "address" on Employee, which does not exist, so you'll have to join Contractor and Address to make that work. However, as far as I can see in the list of reserved keywords, you can't do that with method naming.
So the solution is to write your own query, for example:
#Query("SELECT e FROM Employee e JOIN e.contractorList c JOIN c.addressList a " +
"WHERE e.id = :id AND a.id = :addressId")
Employee findByIdAndAddress(#Param("id") Long id, #Param("addressId") Long addressId);

Related

CRUDRepository findBy foreign key id causing exception: Unable to locate Attribute with the the given name [classroomId] on this ManagedType

I am getting an exception when creating a custom findBy method by a foreign key.
Entity class:
#Entity
#Getter
#Setter
public class Thread {
private #Id #GeneratedValue Long id;
private String subject;
#ManyToOne(fetch = FetchType.LAZY)
#JsonIgnore
private Classroom classroom;
protected Thread() {}
public Long getClassroomId() {
return this.classroom.getId();
}
}
ThreadRepository class:
public interface ThreadRepository extends CrudRepository<Thread, Long> {
List<Thread> findByClassroomId(Long id);
}
I get the exception:
java.lang.IllegalArgumentException: Unable to locate Attribute with the the given name [classroomId] on this ManagedType [com.futurerprood.unicycleservice.entity.threads.Thread]
But the exception goes away if I remove the getClassroomId() in the Thread class. I have this function so that the json serialization will pick up only the classroom id instead of the whole classroom object in an endpoint response.
Why is this function causing the foreign key unable to be found?
You can do one of the following:
Provide the query to the repository method
#Query("select e from Thread t join t.classroom c where c.id = :id")
List<Thread> findByClassroomId(Long id);
Rename the repository method
List<Event> findByClassroom_Id(Long id);
Update
Explanation as to why these two are working
First, have a look at https://docs.spring.io/spring-data/jpa/docs/1.4.3.RELEASE/reference/htmlsingle/#d0e391 and understand how property traversal based on method name happens in Spring data JPA in order to generate the query and how ambiguity resolution is recommended.
In the first one, we tell spring data, it does not need to do property traversal to generate the JPA query as we are giving the query so it does not get any ambiguity.
In the second, as recommended in the reference, we are resolving the ambiguity for Spring Data JPA by telling it to go to Classroom object first. But as #crizzis pointed out under the question comment, Spring data should have treated it as ambiguity in the first place

Hibernate JPA issue doesnt apply IS-A Relationship of table at some times

I have a Parent Table A and extended Child tables B and C
ex:
public class A {
private Long id;
//getters setters//
}
public class B extends A{
private String name;
//getters setters//
}
now at my repository,
public interface B extends JpaRepository<B, Long> {
#Query("select a from A a")//--This works
B getAll()
#Query("select a.id, b.name from A a join a.B b")//-- This doesn't
B getSpecificCols()
}
I need an explanation since "B is an A" that's why getAll() works
how come getSpecificCols() isn't working?
Error is
java.lang.ClassCastException: java.lang.Long cannot be cast to <package>.B
BTW I'm using Hibernate Spring
The query returns a long (a.id) and a string (b.name). The interface assumes that you return an entity (B). The returned id cannot be cast into a B, so you get a ClassCastException. If you want individual fields you can return List<Object[]>, where each record will be an array with id and name. Or you can use a projection (https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections).
By the way, does the first query really work when there are A entities in the database that are not B entities, for example if A also has a subclass C? It looks plain wrong to select the base class and return the derived class without filtering. I would select from B to return B.
After reading documentation what I needed all along was to put constructors in my entity/table where you put what specific parameters you need.

JPA #Query annotation does not seem to be using paramter

I have a Spring Boot application using JPA/Hibernate as entity management/modeling. I have the following user class:
#Entity
public class User {
#Id
private Long Id;
private String name;
//more fields, getters and setters below
}
I want users of my application to be able to search for users by name. So in my repository interface, I have:
public interface UserRepository extends JpaRepository<User, Long> {
#Query(value = "SELECT u from User u WHERE lower(u.name) LIKE lower(:name)")
List<User> findByNameLike(#Param(value="name") String nmae);
}
Then in my UserController, I have:
#GetMapping(value = "/users/")
public #ResponseBody List<User> search(#RequestParam String name) {
return this.userRepository.findByNameLike(name);
}
This always returns an empty list. I know the name parameter is coming in correctly.
Moreover, I do know that it is recognizing the #Query annotation, because if I change the query to something like
SELECT u FROM User u
it will return me all users. Or if I change it to
SELECT u from User u WHERE u.name = '%Bob%'
and remove the parameter from the method, it will return all users whose name is Bob. I've tried other variations of the query, such as
SELECT u FROM User u WHERE lower(u.name) LIKE CONCAT('%', lower(:name), '%')
SELECT u FROM User u WHERE u.name = :name (even a simple equals doesn't work)
and so on and so forth. Every source I look at whose syntax I copy seems to say I'm writing the query right, so I think something else must be going on.
I've also tried letting Spring generate the query for me, using
public List<User> findByNameLike(String name);
public List<User> findByNameContaining(String name);
which also don't seem to work. Any help would be appreciated here!

SpringBoot: Is this correct way to save a new entry which has ManyToOne relationship?

I have two entities Person and Visit
Person has OneToMany relationship with Visit.
I was wondering if I want to save an new entry of Visit, and interm of using RestController. Is my approach correct? Or is there another way which is more efficient?
So I have the following controller which takes a VisitModel from the RequestBody, is it a correct way to call it like so?
VisitModel has the ID of person, and the needed properties for the Visit entity. I use the ID of person to look up in the personRepository for the related Person entry, whereafter I issue it to a new instance of Visit and then use the visitRepository to save it.
#RequestMapping(value="", method=RequestMethod.POST)
public String checkIn(#RequestBody VisitModel visit) {
Person person = personRepository.findById(visit.personId);
Visit newVisit = new Visit(visit.getCheckIn, person);
visitRepository.save(newVisit);
return "success";
}
The Visit entity looks as following
#Entity
public class Visit {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#JsonProperty("check_in")
private Date checkIn;
#JsonProperty("check_out")
private Date checkOut;
#ManyToOne
#JoinColumn(name="personId")
private Person person;
public Visit(Date checkIn, Person person) {
this.checkIn = checkIn;
this.person = person;
}
public Date getCheckIn() {
return checkIn;
}
public void setCheckIn(Date checkIn) {
this.checkIn = checkIn;
}
public Date getCheckOut() {
return checkOut;
}
public void setCheckOut(Date checkOut) {
this.checkOut = checkOut;
}
public Person getPerson() {
return person;
}
}
I want to know of the following approach is correct. Or is there another way which is better?
You don't need to get a Person from the database to associate it with a Visit, of course. Because of, you need to have only id of a Person to save it in the foreign key column personId.
If you use JPA EntityManager
Person person = entityManager.getReference(Person.class, visit.personId);
for Hibernate Session
Person person = session.load(Person.class, visit.personId);
This methods just create a proxy and don't do any database requests.
With Hibernate Session I used new Person(personId) as #MadhusudanaReddySunnapu suggested. Everything worked fine.
What is the difference between EntityManager.find() and EntityManger.getReference()?
Hibernate: Difference between session.get and session.load
Yes, that seems to me to be the standard way to map a bidirectional relationship. EDIT: The personId column points to the "id" field of the Person entity.Eg:
#Id
private Long id;
UPDATE: 1: The VisitModel is a 'DTO' or Data Transfer Object. Any separate package is fine. You could consider putting them into a separate jar, so that anyone using your API (with java) can use the jar to create the data before making the call. 2) The way you save it is fine as far as I can see.

Spring data JPA query with parameter properties

What is the simplest way of declaring a Spring data JPA query that uses properties of an input parameter as query parameters?
For example, suppose I have an entity class:
public class Person {
#Id
private long id;
#Column
private String forename;
#Column
private String surname;
}
and another class:
public class Name {
private String forename;
private String surname;
[constructor and getters]
}
... then I would like to write a Spring data repository as follows:
public interface PersonRepository extends CrudRepository<Person, Long> {
#Query("select p from Person p where p.forename = ?1.forename and p.surname = ?1.surname")
findByName(Name name);
}
... but Spring data / JPA doesn't like me specifying property names on the ?1 parameter.
What is the neatest alternative?
This link will help you: Spring Data JPA M1 with SpEL expressions supported. The similar example would be:
#Query("select u from User u where u.firstname = :#{#customer.firstname}")
List<User> findUsersByCustomersFirstname(#Param("customer") Customer customer);
https://spring.io/blog/2014/07/15/spel-support-in-spring-data-jpa-query-definitions
Define the query method with signatures as follows.
#Query(select p from Person p where p.forename = :forename and p.surname = :surname)
User findByForenameAndSurname(#Param("surname") String lastname,
#Param("forename") String firstname);
}
For further details, check the Spring Data JPA reference
What you want is not possible. You have to create two parameters, and bind them separately:
select p from Person p where p.forename = :forename and p.surname = :surname
...
query.setParameter("forename", name.getForename());
query.setParameter("surname", name.getSurname());
You could also solve it with an interface default method:
#Query(select p from Person p where p.forename = :forename and p.surname = :surname)
User findByForenameAndSurname(#Param("surname") String lastname,
#Param("forename") String firstname);
default User findByName(Name name) {
return findByForenameAndSurname(name.getLastname(), name.getFirstname());
}
Of course you'd still have the actual repository function publicly visible...
You can try something like this:
public interface PersonRepository extends CrudRepository<Person, Long> {
#Query("select p from Person AS p"
+ " ,Name AS n"
+ " where p.forename = n.forename "
+ " and p.surname = n.surname"
+ " and n = :name")
Set<Person>findByName(#Param("name") Name name);
}
if we are using JpaRepository then it will internally created the queries.
Sample
findByLastnameAndFirstname(String lastname,String firstname)
findByLastnameOrFirstname(String lastname,String firstname)
findByStartDateBetween(Date date1,Date2)
findById(int id)
Note
if suppose we need complex queries then we need to write manual queries like
#Query("SELECT salesOrder FROM SalesOrder salesOrder WHERE salesOrder.clientId=:clientId AND salesOrder.driver_username=:driver_username AND salesOrder.date>=:fdate AND salesOrder.date<=:tdate ")
#Transactional(readOnly=true)
List<SalesOrder> findAllSalesByDriver(#Param("clientId")Integer clientId, #Param("driver_username")String driver_username, #Param("fdate") Date fDate, #Param("tdate") Date tdate);
The simplicity of Spring Data JPA is that it tries to interpret from the name of the function in repository without specifying any additional #Query or #Param annotations.
If you are supplying the complete name, try to break it down as firstname and lastname and then use something like this -
HotelEntity findByName(String name);
My HotelEntity does contain the field name so JPA tries to interpret on its own to infer the name of the field I am trying to query on and create a subsequent query internally.
Some more evidence from JPA documentation -
Further details - here
Are you working with a #Service too? Because if you are, then you can #Autowired your PersonRepository to the #Service and then in the service just invoke the Name class and use the form that #CuriosMind... proposed:
#Query(select p from Person p where p.forename = :forename and p.surname = :surname)
User findByForenameAndSurname(#Param("surname") String lastname,
#Param("forename") String firstname);
}
and when invoking the method from the repository in the service, you can then pass those parameters.
for using this, you can create a Repository for example this one:
Member findByEmail(String email);
List<Member> findByDate(Date date);
// custom query example and return a member
#Query("select m from Member m where m.username = :username and m.password=:password")
Member findByUsernameAndPassword(#Param("username") String username, #Param("password") String password);
#Autowired
private EntityManager entityManager;
#RequestMapping("/authors/{fname}/{lname}")
public List actionAutherMulti(#PathVariable("fname") String fname, #PathVariable("lname") String lname) {
return entityManager.createQuery("select A from Auther A WHERE A.firstName = ?1 AND A.lastName=?2")
.setParameter(1, fname)
.setParameter(2, lname)
.getResultList();
}

Resources