Hibernate / Spring / JPA - How to insert with a select subquery - java

Given this hypothetical table structure:
person
id | reference | name
book
id | name | person_id
And this class structure
#Entity(name = "book")
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "id")
Long id;
#Column(name = "name")
String name;
// WHAT GOES HERE?
UUID personReference;
...
}
#Repository
public interface BookRepository extends JpaRepository<Book, Long> {
}
How would one insert the book row while using a select the person.id using the personReference field on the Book class. A query that would usually look like this:
insert into book (name, person_id)
values (?, (select id from person p where p.reference = ?))
Is this something that is possible through annotations or should I look to just implement a query?

Hibernate is an implementation of the Java Persistence API (JPA) which is a Java specific Object Relational Model (ORM). The purpose of ORMs is to handle sql rows as objects & it includes relations as well.
#Entity
public class Book implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
Long id;
String name;
// relational mapping
#ManyToOne // an author can write multiple books. but a book has a single author
Person author; // an author is a Person not an UUID
}
But to store Person object in your db, you need to have a Person Entity as well.
#Entity
public class Person implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
long id; // primitive types can be primary keys too
String name;
}
I strongly advise to learn the basics of JPA (Specification)
Then, in your #Repository classes (Spring specific):
#Repository
public BookRepository implements CrudRepository<Book,Long> {
}
#Repository
public PersonRepository implements CrudRepository<Book,Long> {
}
Finally, in your #Service class (Spring specific):
#Service
public class BookService {
PersonRepository personRepo;
BookRepository bookRepo;
public BookService(PersonRepository personRepo, BookRepository bookRepo) {
this.personRepo = personRepo;
this.bookRepo = bookRepo;
}
public void setBookAuthor(Book book, Person author) {
book.setAuthor(author);
bookRepo.save(book);
}
public void setBookAuthor(long bookId, long personId) {
Book book = bookRepo.findById(bookId);
Person author = userRepo.findById(personId);
setBookAuthor(book, author);
}
}

If Person is not mapped as an entity, you can use native SQL queries.
I'm not sure if this is an error, but the mapping should be:
#Column(name="person_id")
UUID personReference;
If that's a valid native SQL query, you can do the insert using it:
#Modifying
#Query(nativeQuery=true, value="insert into book (name, person_id) values (?1, (select id from person p where p.reference = ?2))")
public int insert(String name, String reference);
Your question doesn't contain enough info to figure out if you can map Person as an entity. If that's the case, this article might help you.

Related

Relationship mapping in Hibernate

I have a two table Company and CompanyRepo like below
COMAPNY
COMPANY_REPO
I have entities for those two tables like this :
#Entity
#Table(name="COMAPNY")
public class Company implements Serializable {
#Id
#Column(name="COMPANY_ID")
private Long companyId;
#Column
private String companyName;
#OneToMany(mappedBy="COMPANY")
private List<CompanyRepo> companyRepo;
}
#Entity
#Table(name="COMAPNY_REPO")
public class CompanyRepo implements Serializable {
#Id
#Column(name="COMPANY_REPO_ID")
private Long companyRepoId;
#Column
private String COMPANY_ID;
#Column
private String DEPT_ID;
#ManyToOne
#JoinColumn(name="COMPANY_ID")
private Company company;
}
Now i want to execute below query using a Hibernate relationship mapping
select Company_name from company as C ,company_repo as cr where c.company_id=cr.company_id and dept_id=10
I wrote a JPA repository like below by using a #OneToMany in Company table and #ManyToOne in CompanyRepo. But in resultant I am getting multiple COMPANYobject inside COMPANY_REPO Object.Does my relationship mapping is correct ?
public interface CompanyRepository extends JpaRepository<CompanyRepo, Long> {
public CompanyRepo findByDeptId(Long deptId);
}
Given your current database design, try something such as the following:
public interface CompanyRepository extends JpaRepository<Company, Long> {
#Query(value="SELECT * from company as C WHERE c.company_id = (SELECT cr.company_id FROM company_repo as cr WHERE cr.dept_id = ?1)", nativeQuery=true)
public Company findByDeptId(Long deptId);
}
The #Query annotation is a very powerful and flexible way to define methods in Repository interface. If you need more complex logic, I would recommend reading about the use and possibilities of the annotation. See here.
Based on your entity class your have multiple
COMPANY_ID that is not a proper way to declare your column name.
#Column
private String COMPANY_ID;
#ManyToOne
#JoinColumn(name="COMPANY_ID")
private Company company;
So please change your #Column
private String COMPANY_ID; to #Column("comp_id)
private String companyId;
If you want to get the CompanyRepo based on Company type then you need to change your query to fetch the dept id.
And your #Column
private String DEPT_ID; is String datatype so pass String to your repository.
#Query(" select companyRepo.company from CompanyRepo as companyRepo where companyRepo.DEPT_ID = :deptId")
public Company findByDeptId(String deptId);

Spring Data JPA - ManyToMany - JPQL - #Query formation in Repository

I have a spring boot project with PostgreSQL RDBMS.
I have #ManyToMany relation between two entities - Customer & Product. They are joined by the customer_product. But while forming JPQL at repository layer, I am facing difficulties. Here is entity:
#Entity
#NamedQuery(name="Customer.findAll", query="SELECT c FROM Customer c")
public class Customer implements Serializable {
... all properties ... getter setter constructor...
//bi-directional many-to-many association to Product
#JsonIgnore
#ManyToMany
#JoinTable(
name="customer_product"
, joinColumns={
#JoinColumn(name="customer_id")
}
, inverseJoinColumns={
#JoinColumn(name="product_id")
}
)
private List<Product> products;
#Entity
#NamedQuery(name="Product.findAll", query="SELECT p FROM Product p")
public class Product implements Serializable {
... all properties ... getter setter ... constructors ...
//bi-directional many-to-many association to Customer
#JsonIgnore
#ManyToMany(mappedBy="products")
private List<Customer> customers;
Now at the repository later:
#Repository
public interface CustomerRepository extends CrudRepository<Customer, Integer> {
//Below mentioned query works perfectly as Customer & Address has OneToMany Relation
#Query("select new com.arindam.springjpa.springdatajpaexamples.vo.CustomerDetailsVO(c.id, c.customerName, a.fullAddress) from Customer c, Address a where c.address.addressId=a.addressId")
List<CustomerDetailsVO> findAllCustomerDetails();
// But I need help on below query
// I want to find out full details of ManyToMany relation between customer & product
#Query("select new com.arindam.springjpa.springdatajpaexamples.vo.CustomerProductAddressDetailsVO(c.id, c.customerName, a.fullAddress, p.productName) from Customer c, Address a, Product p where c.address.addressId=a.addressId and c.products.product.productId=p.productId")
List<CustomerProductAddressDetailsVO> findAllCustomerAddressProductDetails();
To have results in VO here is simple VO class
#Entity
public class CustomerProductAddressDetailsVO {
private Integer id;
private String customerName;
private String fullAddress;
private String productName;
//Like a simple POJO with getter setter constructors
Can you please suggest.
Thanks for your valuable feedback.
I have resolved the issue. Since the entity classes have been generated using Eclipse plugins, so the class for customer_product was not been generated. So I manually generated it and used the query. So the final code is:
#Entity
#Table(name="customer_product")
#NamedQuery(name="CustomerProduct.findAll", query="SELECT c FROM CustomerProduct c")
public class CustomerProduct implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private Integer id;
#Column(name="customer_id")
private Integer customerId;
#Column(name="product_id")
private Integer productId;
and Repository layer:
#Query("select new com.arindam.springjpa.springdatajpaexamples.vo.CustomerProductAddressDetailsVO(cp.id, c.customerName, a.fullAddress, p.productName) from Customer c, Address a, Product p, CustomerProduct cp "
+ "where c.address.addressId=a.addressId and cp.productId=p.productId and cp.customerId=c.id")
List<CustomerProductAddressDetailsVO> findAllCustomerAddressProductDetails();
It works perfectly.

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

Spring Data Projection with OneToMany returns too many results

I have a JPA entity (Person) with onetomany relation (ContactInfo).
#Entity
public class Person {
#Id
#GeneratedValue
private Integer id;
private String name;
private String lastname;
private String sshKey;
#OneToMany(mappedBy = "personId")
private List<ContactInfo> contactInfoList;
}
#Entity
public class ContactInfo {
#Id
#GeneratedValue
private Integer id;
private Integer personId;
private String description;
}
I've defined a projection interface that includes this onetomany relation as described here.
public interface PersonProjection {
Integer getId();
String getName();
String getLastname();
List<ContactInfo> getContactInfoList();
}
public interface PersonRepository extends JpaRepository<Person,Integer> {
List<PersonProjection> findAllProjectedBy();
}
When I retrieve the data with findAllProjectedBy the result contains too many rows. It looks like the returned data is the result of a join query similar to:
select p.id, p.name, p.lastname, ci.id, ci.person_id, ci.description
from person p
join contact_info ci on ci.person_id = p.id
For example for this data set:
insert into person (id,name,lastname,ssh_key) values (1,'John','Wayne','SSH:KEY');
insert into contact_info (id, person_id, description) values (1,1,'+1 123 123 123'), (2,1,'john.wayne#west.com');
The findAllProjectedBy method returns 2 objects (incorrectly) and the standard findAll returns 1 object (correctly).
Full project is here
I've done some debugging and it seems that the problem is with the jpa query.
The findAll method uses this query:
select generatedAlias0 from Person as generatedAlias0
The findAllProjectedBy uses this query:
select contactInfoList, generatedAlias0.id, generatedAlias0.name, generatedAlias0.lastname from Person as generatedAlias0
left join generatedAlias0.contactInfoList as contactInfoList
Does anyone know how to fix this invalid behaviour?
A quick fix for this problem is described here:
https://jira.spring.io/browse/DATAJPA-1173
You need to describe one of the single projection attributes with a #Value annotation. For the example posted above you will end up with:
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
public interface PersonProjection {
#Value("#{target.id}")
Integer getId();
String getName();
String getLastname();
List<ContactInfo> getContactInfoList();
}

How to get child table rows using parent table ID?

I have two tables: Organization(Parent) and Department(Child).
There is One to Many relationship, and is mentioned in Organization table only.
#Entity
#Table(name="TBL_STD_ORGANIZATION")
public class Organization implements Serializable {
#Id
#GeneratedValue
#Column(name="FLD_ORG_ID")
private Long organizationId;
#Column(name="FLD_ORG_NAME")
private String orgName;
#OneToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
private java.util.List<Department> listOfDepartMents = new java.util.ArrayList<Department>();
}
Below is Department Class:
#Entity
#Table(name="TBL_STD_DEPARTMENT")
public class Department implements Serializable {
#Id
#GeneratedValue
#Column(name = "FLD_DEPARTMENT_ID")
private Long departmentId;
#Column(name = "FLD_DEPARTMENT_NAME")
private String departmentName;
}
I wrote relationship in Parent table, because of it hibernate creates third table.
Now, I have to retrieve departments start with "sa" keyword and in specific organization.
So I want the HQL or SQL query query. I am not getting it how to write such complex query.
Any suggestions?
I'm fairly certain the HQL/JPQL would be:
SELECT d FROM Organization o JOIN o.listOfDepartMents d WHERE d.departmentName LIKE "sa%"

Categories