How to use JPA Criteria API in JOIN - jpa

I have five tables: Company, Product/Service, Address, Country and
City.
A Company can have n products with category, 1 address with 1 country
and 1 city inside the address entity.
A user has chosen "England - Leeds".
I know now that I have to select every companies from db where city
is Leeds and populate product/service-list with those companies'
products or services. After that user can select for instance dentist
from the third list.
After that I know Enlgand - Leeds - Dentist and I have to populate
the last list with compenies (dentists in Leeds)
public class Company implements java.io.Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Short companyId;
#OneToOne(cascade=CascadeType.PERSIST)
private Address address;
private String companyName;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "company",cascade=CascadeType.PERSIST)
private Set<Product> products = new HashSet<Product>(0);
public class Product implements java.io.Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "productId", unique = true, nullable = false)
private Integer productId;
private Short branchId;
private String productName;
private String sku;
private String category; ------> I am using this field in company search (dentists, garages etc.)
How can I query only those companies which have products with category dentist?
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Company> criteria = criteriaBuilder.createQuery( Company.class );
Root<Company> companyRoot = criteria.from( Company.class );
//criteria.select(companyRoot);
TypedQuery<Company> q = em.createQuery(criteria);
List<Company> results = q.getResultList();
Now I've got every company, how can I select only the companies with the correct category? I think I will need JOIN but how I don't know how to use it.

use join(),
criteriaBuilder.equal(companyRoot.join("products").get("category"), "dentist")
See,
http://en.wikibooks.org/wiki/Java_Persistence/Querying#Joining.2C_querying_on_a_OneToMany_relationship

Related

Crieria API query using criteriabuilder.construct with a non existing relationship

Given this very simple DTO:
#Entity
public class Employee implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
#OneToOne
private Employee boss;
}
I'd like to make a query that gathers all employee names and their boss' id, put in a nice clean POJO:
public class EmployeeInfo {
private String name;
private Long bossId;
public EmployeeInfo(String name, Long bossId) {
this.name = name;
this.bossId = bossId;
}
}
This query should be of use:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<EmployeeInfo> query = cb.createQuery(EmployeeInfo.class);
Root<Employee> root = query.from(Employee.class);
query.select(
cb.construct(EmployeeInfo.class,
root.get("name").as(String.class),
root.get("boss").get("id").as(Long.class)));
result = em.createQuery(query).getResultList();
When a bossId is present in the employee column this works just fine. But when no boss id is set the record will be completly ignored. So how do i treat this non existing boss relation as null or 0 for the construct/multiselect?
In pure SQL it is easy:
SELECT name, COALESCE(boss_id, 0) FROM EMPLOYEE;
But for the love of god i cannot make the criteria api do this.
cb.construct(EmployeeInfo.class,
root.get("name").as(String.class),
cb.coalesce(root.get("boss").get("id").as(Long.class), 0L)));
The problem is that root.get("boss") generate query with cross join like this from Employee employee, Employee boss where employee.boss.id=boss.id. So records where employee.boss.id is null are ignored.
To solve the problem you should use root.join("boss", JoinType.LEFT) instead of root.get("boss")

Creating a query with a one-to-one relationship

I have two tables...a loan table and a customer table. A customer can make multiple loans but I would like to restrict the customer to one active loan at a time. They cannot create a second loan until the first loan is finished (loan.active=false)
I have set up my loan table like this :
#Entity
public class Loan implements Serializable {
private static final long serialVersionUID = 0x62B6DA99AA12AAA8L;
#Column #GeneratedValue(strategy = GenerationType.AUTO) #Id private Integer id;
#OneToOne(fetch = FetchType.LAZY)
private Customer customer;
#Column private String dateLoaned;
#Column private String dateToReturn;
#Column private String dateOfReturn;
#Column private Boolean active=false;
And the customer table like this :
#Entity
public class Customer implements Serializable {
private static final long serialVersionUID = 0x63A6DA99BC12A8A8L;
#Column #GeneratedValue(strategy = GenerationType.AUTO) #Id private Integer id;
#Column private String firstname;
#Column private String surname;
#Column private String address;
#Column private String town;
#Column private String postcode;
#Column (unique=true) private String personalnumber;
#Column (unique=true) private String emailaddress;
#OneToOne(fetch = FetchType.EAGER)
private Loan loan;
This allows me to create a new loan with the same customer. So far so good.
I would like to make a query that allows me to find if a customer already has an active loan.
My loan repository so far is :
#Query("select loan_id from Loan l where l.customer.id = :customerId and l.active = true")
Boolean customerHasActiveLoan(#Param("customerId") Integer customerId);
Is this the correct way to do this?
In spring-data-jpa you can both have #Query or write a method that generates a query. There is nothing wrong to have #Query but because your repository method is quite simple you can use also method name only
For the example the equivalent of:
//Will return the active loan, if exists, or null
#Query("select l from Loan l where l.customer.id = :customerId and l.active = true")
public Loan getActiveLoad(#Param("customerId") Integer customerId)
could be simplified as
public Local findOneByCustomerIdAndActiveIsTrue(Long id)
Sometimes method name approach can generate long method name, and for this reason, if you prefer, you can use #Query annotation

Model Look up table for a JPA Entity Field

I'm trying to find out the appropriate relationship to be used in order to model an JPA Entity Field which needs a Look-up table (Comboxbox equivalent on the UI) to select the value from. An example below:
#Entity
public class Employee {
#Id
private int employeeId;
private String name;
private Department department;
}
#Entity
public class Department {
#Id
private int id;
private String name;
}
The instances of Department could be as follows:
Id | Name
-----------------------
100 | Human Resources
101 | Sales
102 | Finances
For an employee, the department field should get a value from one of the above. What should be the JPA annotations for the corresponding fields in both the entities?
Thanks,
Sandeep Joseph
I think you are looking for a unidirectional ManyToOne relationship from Employee to Department, something like this:
#Entity
public class Employee {
#Id
private int employeeId;
private String name;
#ManyToOne
#JoinColumn(name = "department_id", nullable = false)
private Department department;
}
This means an employee must be associated to a single department and a department can be associated to many employees.
If you need to know the list of employees associated to a department then you can make the relationship bidirectional by adding this:
#Entity
public class Department {
#Id
private int id;
private String name;
#OneToMany(mappedBy = "department")
private Collection<Employee> employees;
}

How to select an attribut that is on the "wrong" side of a OneToOne unilateral relationship with JPA criteria

I've 2 entities:
#Entity
public class Customer{
#Id
#Column(name = "ID")
private Long id;
#OneToOne(mappedBy = "customer")
private Address address;
#Column(name = "FIELD_1")
private String field1;
#Column(name = "FIELD_2")
private String field2;
}
#Entity
public class Address{
#Id
#Column(name = "ID")
private Long id;
#OneToOne
#JoinColumn(name = "CUST_ID")
private Customer customer;
}
If I do a select to retrieve the Customer, the Adress is retrieved as well, all good.
But I want to do a projection and build a DTO, so only select field1 and field2 along with the Adress entity.
public class MyDTO {
private String field1;
private String field2;
private Address address;
public MyDTO (String pField1, String pField2, Adress pAddress){
field1 = pField1;
field2 = pField2;
adress = pAddress;
}
}
So I've coded that DAO method :
public List<MyDTO > getListMyDTO() {
CriteriaQuery<MyDTO > crit = builder.createQuery(MyDTO .class);
Root<Customer> root = crit.from(Customer.class);
// This is to avoid a inner join since some customer may not have an adress and i dont want to exclude them from my select
root.join("address", JoinType.LEFT);
crit.multiselect(root.get("field1"), root.get("field2"), root.get("address"));
return em.createQuery(crit).getResultList();
}
em being the entity manager.
However that doesn't work and the address ends up always null.
I sorta understand this as there is no field from root pointing to the address table, but since it works when i do a select on the entity instead of a DTO, there must be a way to make this work no?

JPA Query Many To One nullable relationship

I have the following entities and would like to seek help on how to query for selected attributes from both side of the relationship. Here is my model. Assume all tables are properly created in the db. JPA provider I am using is Hibernate.
#Entity
public class Book{
#Id
private long id;
#Column(nullable = false)
private String ISBNCode;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY, optional = false)
private Person<Author> author;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY, optional = true)
private Person<Borrower> borrower;
}
#Inheritance
#DiscriminatorColumn(name = "personType")
public abstract class Person<T>{
#Id
private long id;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Info information;
}
#Entity
#DiscriminatorValue(PersonType.Author)
public class Author extends Person<Author> {
private long copiesSold;
}
#Entity
#DiscriminatorValue(PersonType.Borrower)
public class Borrower extends Person<Borrower> {
.....
}
#Entity
public class Info {
#Id
private long id;
#Column(nullable=false)
private String firstName;
#Column(nullable=false)
private String lastName;
......;
}
As you can see, the book table has a many to one relation to Person that is not nullable and Person that is nullable.
I have a requirement to show, the following in a tabular format -
ISBNCode - First Name - Last Name - Person Type
How can I write a JPA query that will allow me to select only attributes that I would want. I would want to get the attributes ISBN Code from Book, and then first and last names from the Info object that is related to Person Object that in turn is related to the Book object. I would not want to get all information from Info object, interested only selected information e.g first and last name in this case.
Please note that the relation between the Borrower and Book is marked with optional=true, meaning there may be a book that may not have been yet borrowed by someone (obviously it has an author).
Example to search for books by the author "Marc":
Criteria JPA Standard
CriteriaQuery<Book> criteria = builder.createQuery( Book.class );
Root<Book> personRoot = criteria.from( Book.class );
Predicate predicate = builder.conjunction();
List<Expression<Boolean>> expressions = predicate.getExpressions();
Path<Object> firtsName = personRoot.get("author").get("information").get("firstName");
expressions.add(builder.equal(firtsName, "Marc"));
criteria.where( predicate );
criteria.select(personRoot);
List<Book> books = em.createQuery( criteria ).getResultList();
Criteria JPA Hibernate
List<Book> books = (List<Book>)sess.createCriteria(Book.class).add( Restrictions.eq("author.information.firstName", "Marc") ).list();
We recommend using hibernate criterias for convenience and possibilities.
Regards,