How to create a nested object by joining tables with Spring JPA? - spring-boot

I'm trying to join multiple tables with Spring JPA in order to receive a nested object, but something seems to be wrong with my query that I've set in my repository interface.
This is the desired output (it is only a simplified example)
[departmentId: 1
departmentName: A
employees:[
[employeeId: 100
employeeName: John],
[employeeId: 200
employeeName: Carl]],
[departmentId: 2
departmentName: B
employees:[
[employeeId: 300
employeeName: Nancy]]
I have the following tables in Postgres:
department: department_id, department_name
employees: employee_id, employee_name, department_id
These are my classes:
Department
#Entity
#Table(name="department")
public class Department implements Serializable {
#Id
#Column(name="department_id")
private Integer departmentId;
#Column(name="department_name")
private String departmentName;
#OneToMany(mappedBy = "department")
private List<Employee> employees;
}
//getters, setters, etc.
Employee
#Entity
#Table(name="employees")
public class Employee implements Serializable {
#Id
#Column(name="employee_id")
private Integer employeeId;
#Column(name="employee_name")
private String employeeName;
#ManyToOne
#JoinColumn(name="department_id")
private Department department;
}
//getters, setters, etc.
Repository
#Repository
public interface Repository extends JpaRepository<Department, Integer> {
#Query(value = "SELECT department.department_id, department_name, employee_id, employee_name FROM department " +
"JOIN employees ON employees.department_id = department.department_id", nativeQuery = true)
List<Department> findDeps();
}
But I'm getting a LazyInitializationException. I've read that setting FetchType.EAGER is not recommended, but even that is throwing a StackOverFlow error. Any help is appreciated.

Related

JPA merge table

I have table employee
#Entity
#Table(name = "employee")
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="emp_id")
private Long id;
#Column(name="emp_name")
private String empName;
#Column(name="emp_mail")
private String empMail;
#Column(name="emp_no")
private String empNo;
#Column(name="emp_salary")
private Long empSalary;
#Column(name="type_id")
private Long typeId;
}
In addition, I have table employeetype whose primary key type id is in employee table
How i am able to get response containg type_name not type_id in java data jpa for api using jparepository.
how to join table data.

JPA JoinTable with additional columns

Spring Boot
Spring Data
JPA Hibernate
Came across a requirement where JPA ManyToMany relationship table with an extra column. Have looked at StackOverflow and found several questions related to same requirement. Most of the answers on the forums ask for EmbeddedId field with a composite primary key with two columns. I tried the solutions from the forums, here is the code snippet.
#Data
#Entity
#Table (name = "TABLE_A")
public class TableA {
#Id
#Column (name = "table_a_id")
private Integer id;
...
#OneToMany (mappedBy = "pk.tableA")
private List<TableABMapping> mappingTable;
}
#Data
#Entity
#Table (name = "TABLE_B")
public class TableB {
#Id
#Column (name = "table_b_id")
private Integer id;
...
#OneToMany (mappedBy = "pk.tableB")
private List<TableABMapping> mappingTable;
}
#Data
#Entity
#Table (name = "TABLE_A_TABLE_B_MAPPING")
public class TableABMapping implements Serializable {
#EmbeddedId
private MappingKey pk = new MappingKey();
#Column(name = "addon_field")
private Double additionalField;
#Transient
public TableA getTableA() {
return getPk().getTableA();
}
public void setTableA(TableA tableA) {
getPk().setTableA(tableA);
}
#Transient
public TableB getTableB() {
return getPk().getTableB();
}
public void setTableB(TableB tableB) {
getPk().setTableB(tableB);
}
// equals() & hashCode() method override
}
#Data
#Embeddable
public class MappingKey implements Serializable {
#ManyToOne
#JoinColumn(name = "table_a_id", referencedColumnName = "table_a_id")
private TableA tableA;
#ManyToOne
#JoinColumn(name = "table_b_id", referencedColumnName = "table_b_id")
private TableB tableB;
// No argument constructor, two arguments constructor.
// equals() & hashCode() method override
}
Trying save operation from service class like this:
for (TableB tabB : tableA.getTableB()) {
TableABMapping abMapping = new TableABMapping();
abMapping.setTableA(tableA);
abMapping.setProduct(tabB);
abMapping.setAdditionalField(tabB.getAddonField());
if (tableA.getMappingTable() == null) {
tableA.setMappingTable(new ArrayList<TableABMapping>());
}
tableA.getMappingTable().add(abMapping);
}
TableA ta = tableARepository.save(tableA);
System.out.println("TableA.save(): " + ta);
Getting this error on save operation.
Unable to find TableABMapping with id MappingKey(tableA = TableA( ... ), tableB = TableB ( ... ))
Both the entities have proper ids at the time of saving the entity. But still it throws this error. Where I am making mistake?

Many to one relationship without a join table in spring

I'm trying to build the relationship between two tables using spring-data jpa. I have read many SO articles like 1, 2 but they are pretty old and don't seem to apply to my specific use case. Hence this question:
There are 2 tables user_client_scopes and scopes listed below.
user_client_scopes:
user_id (long),
client_id (string)
last_updated (timestamp)
scope_id (Foreign key to scopes table),
primary key (user_id, client_id, scope_id)
scopes:
id (int, primary key)
name (string)
A <user_id, client_id> can have multiple scopes. Similarly, the same scope can be held by many <user_id, client_id>s. Hence the many-to-many relationship. The join table (as defined by spring-data-jpa) is kind of embedded within user_client_scope table.
Here is a half-written-code:
#Entity
#Table(name = "user_client_scopes")
#RequiredArgsConstructor
#IdClass(UserClientScopesPK.class)
public class UserClientScopes implements Serializable {
#Id
#Column(name = "user_id")
private long userId;
#Id
#Column(name = "client_id")
private String clientId;
#Column(name = "last_updated")
private Timestamp lastUpdated;
#Id
#Column(name = "scope_id")
private int scopeId;
#ManyToMany // <- how to complete this definition?
private Set<Scope> scopes;
getters and setters.
Here are 2 other classes (for the sake of completion).
#Data
#RequiredArgsConstructor
public class UserClientScopesPK implements Serializable {
private long userId;
private String clientId;
private int scopeId;
}
#Entity
#Table(name = "scopes")
#RequiredArgsConstructor
public class Scope implements Serializable {
#Id
#GeneratedValue
private long id;
private String name;
}
How do I complete the user_client_scopes entity such that we can:
Find all scopes for a given <user_id, client_id>. i.e. execute the following SQL:
select user_id, client_id, scope
from scopes
join user_client_scopes ucs on ucs.scope_id = scopes.id
where ucs.user_id = ? and ucs.client_id = ?
Save new scopes for a given <user_id, client_id>. i.e. execute the following SQL:
insert into user_client_scopes (user_id, client_id, scope_id, last_updated)
select ?, ?, id, now()
from scopes
where scopes.name = ?
UPDATE 1:
Changing title to Many to one instead of Many to many relationship.
That's not a many-to-many because the association scope is mapped by the column scope_id in user_client_scopes. This means that if I take a single row in the table user_client_scopes, it will be associated to only a single row in the table scopes. Therefore, this is a many-to-one.
If the three columns <user_id, client_id, scope_id> form the key for user_client_scopes, then the mapping for the table should look like:
Entity
#Table(name = "user_client_scopes")
#RequiredArgsConstructor
#IdClass(UserClientScopesPK.class)
public class UserClientScopes implements Serializable {
#Id
#Column(name = "user_id")
private long userId;
#Id
#Column(name = "client_id")
private String clientId;
#Column(name = "last_updated")
private Timestamp lastUpdated;
#Id
#ManyToOne
#JoinedColumn(name = "scope_id")
private Scope scope;
getters and setters.
}
class UserClientScopesPK implements Serializable {
private long userId;
private String clientId;
private Scope scope;
// getters,setters, equals and hascode
}
With this mapping you can run the following HQL:
select ucs
from UserClientScopes ucs join ucs.scope
where ucs.userId = :userId and ucs.clientId = :clientId
It will return all UserClientScopes entities matching the selected pair <userId, clientId>. Each one with a different scope.
Or, if you only care about the scope:
select s
from UserClientScopes ucs join ucs.scope s
where ucs.userId = :userId and ucs.clientId = :clientId
With Spring Data JPA, it will look like this:
#Query("select s from UserClientScopes ucs join ucs.scope swhere ucs.userId = ?1 and ucs.clientId = ?2")
public List<Scope> findScopesByUserIdAndClientId(long userId, String clientId);
or
#Query("select s.name from UserClientScopes ucs join ucs.scope swhere ucs.userId = ?1 and ucs.clientId = ?2")
public List<String> findScopesNameByUserIdAndClientId(long userId, String clientId);
You can also run the insert query as native SQL (you can probably run something similar as HQL, but I don't remember the right syntax now. I will update the answer later).
One last thing, to keep track of the last updated time, you could use Spring Entity callback listener:
#Entity
...
#EntityListeners(AuditingEntityListener.class)
public class UserClientScopes implements Serializable {
#LastModifiedDate
#Column(name = "last_updated")
private Date lastUpdated;
}

How to use Spring Boot CRUD API to insert data in multiple tables using one POST endpoint

How can a data be inserted using single POST endpoint in multiple tables. For example there are two tables
1. Employee
2. Department
These two tables have a primary key and foreign key relationship.
How to achieve data insertion in two tables using a single POST endpoint ?
Ok I see what you want.... your entities have to look like this...
You have to create a one to one relationship something like this:
Department entity:
#Entity
#Table
#Data
public class Department {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String description;
}
Employee entity:
#Entity
#Table
#Data
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String email;
private String address;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "department_id", referencedColumnName = "id")
private Department department;
}
And than you can add Data on Startup like this:
#Component
public class DBSeeder implements CommandLineRunner {
#Autowired
private EmployeeRepository repository;
#Override
public void run(String... args) throws Exception {
Department dep1 = new Department();
dep1.setName("Demolition");
dep1.setDescription("Do demo");
Employee emp1 = new Employee();
emp1.setName("John Rambo");
emp1.setEmail("john.rambo#demolition.com");
emp1.setAddress("Demolition Av. 5");
emp1.setDepartment(dep1);
this.repository.save(emp1);
}
}
#Repository
public interface EmployeeRepository extends CrudRepository<Employee, Long> {
Employee save(Employee employee);
}
Do you also ask how the entity objects have to look like?

#OneToMany relationship issue while saving multiple values in Hibernate and Spring

I have two entities with #OneToMany bidirectional relationship as below:
#Entity
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer companyId;
private String name;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy="company")
Set<Employee> employees = new LinkedHashSet<>();
Employee class
#Entity
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer empId;
private String name;
private String address;
private String email;
#ManyToOne
#JoinColumn(name="companyId")
Company company;
Intially i saved 2 employees as below:
Employee emp= new Employee();
emp.setName("John");
emp.setEmail("John#gmail.com");
employeeRepository.save(emp);
Employee emp2= new Employee();
emp2.setName("Smith");
emp2.setEmail("smith#gmail.com");
employeeRepository.save(emp2);
Now I want to save one employee working for 2 different companies like below:
Company company =new Company();
company.setName("Google");
Employee emp = employeeRepository.findOne(1);
company.getEmployees().add(emp);
emp.setCompany(company);
companyRepository.save(company);
Company company2 =new Company();
company2.setName("Microsoft");
company2.getEmployees().add(emp);
emp.setCompany(company2);
companyRepository.save(company2);
It is updating only second company id into employee table. I want both the companies to be assigned to that employee. How can I do that?
The issue here is that you have a one-to-many when obviously it should be a many-to-many, viz. an employee can work for more than one company and a company has many employees.
You can either change the relationship to a #ManyToMany (and use #JoinTable if required) or you can create another entity, say, CompanyEmployee to which both Employee and Company have a one-to-many-relationship. The latter approach is probably preferable as you can then record additional information about the association e.g. start_date, end-date etc.
#Entity
#Table(name = "company_employees")
public class CompanyEmployee {
#ManyToOne
private Employee employee;
#ManyToOne
private Company company;
private Date startDate;
private Date endDate;
}

Resources