Is there a way to join two tables in Spring JPA without using association class.
I have two MySQL DB tables :
employees(id,.....,department_id)
departments(id,.....)
And I'm searching for a way to join these tables using only my employee and department classes.
Currently, I managed to join two tables but with the third association class.
My current implementation is:
Employee class:
#Entity(name = "Employee")
#Table(name = "employees")
#JsonInclude(Include.NON_NULL)
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonIgnore
#Column(name = "employee_id")
private Long employeeId;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "phone_number")
private String phoneNumber;
#Column(name = "hire_date")
private Double hireDate;
#Column(name = "job_id")
private Long jobId;
#Column(name = "salary")
private Double salary;
#Column(name = "commission_pct")
private Double commissionPct;
#Column(name = "employees")
private Long employees;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id", insertable = false, updatable = false)
#Fetch(FetchMode.JOIN)
private Department department;
}
Department class:
#Entity(name = "Department")
#Table(name = "departments")
#JsonInclude(Include.NON_NULL)
public class Department implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "department_name")
private String departmentName;
#Column(name = "department_id")
private long departmentId;
#Column(name = "manager_id")
private Double managerId;
#Column(name = "location_id")
private Double locationId;
}
Association class:
public class DeptEmpDto {
private long departmentId;
private String departmentName;
private Double managerId;
private Double locationId;
private long employeeId;
private String firstName;
private String lastName;
private String phoneNumber;
private Double hireDate;
private Long jobId;
private Double salary;
private Double commissionPct;
}
Repository:
public interface IEmployeeRepository extends JpaRepository<Employee, Long> {
#Query("SELECT new com.concretepage.entity.DeptEmpDto(d.departmentId,d.departmentName,d.managerId,d.locationId,e.employeeId,e.firstName,e.lastName,e.phoneNumber,e.hireDate,e.jobId,e.salary,e.commissionPct FROM Employee e INNER JOIN Department d ON d.id = e.jobId")
List<DeptEmpDto> fetchEmpDeptDataInnerJoin();
You can use it in Employee class
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "DeptEmp",
joinColumns = #JoinColumn(name = "emp_id",referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "dep_id",referencedColumnName = "id")
)
private Set<Departments> departments = new HashSet<>();
Look at this about JPA
Logically an employee can't work in two departements so your relationship is correct
But you can do that with a #ManyToMany annotation.
Related
I am having a faq entity as below. Here createdBy field is having a manyToOne relationship with the user entity. Below joinColumns shows the association.
In the User entity, i have OneToMany relationship with UserRoles and UsersUnit which is EAGER load for User and not for faq. So i added #JsonIgnoreProperties
for UsersUnit and UsersRole and the corresponding User entity is shown below.
#Entity
#Table(name = "FAQ", catalog="abc")
public class Faq implements Serializable {
public Faq() {
super();
}
#Column(name = "CREATE_DATE")
private Timestamp createDate;
#Where(clause = "DELETE_DATE is null")
#Column(name = "DELETE_DATE")
private Timestamp deleteDate;
#Column(name = "DELETED_BY")
private BigDecimal deletedBy;
#Column(name = "DOC_BLOB", nullable = false)
#JsonIgnore
private byte[] docBlob;
#Column(name = "DOC_NAME", nullable = false, length = 100)
private String docName;
#Id
private BigDecimal id;
#Column(name = "ORDER_BY")
private BigDecimal orderBy;
#Column(name = "UPDATE_DATE")
private Timestamp updateDate;
#Column(name = "UPDATED_BY")
private BigDecimal updatedBy;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumns({
#JoinColumn(name="created_by", referencedColumnName="id")
})
private User faqCreatedBy;
}
User entity:
#Entity
#Table(name = "USERS", catalog="abc")
#JsonInclude(Include.NON_NULL)
public class User extends EntityLog{
private BigDecimal id;
private BigDecimal edipi;
private String firstname;
private String lastname;
private String email;
..///
private Set<UsersRoles> userRoles;
private Set<UsersUnit> usersUnit;
#Id
#Column(name="id")
public BigDecimal getId() {
return id;
}
...///
#Column
#JsonIgnoreProperties({ "faqCreatedBy" })
#OneToMany(fetch = FetchType.EAGER,mappedBy = "user",cascade = {CascadeType.ALL})
#JsonManagedReference
public Set<UsersRoles> getUserRoles() {
return userRoles;
}
...///
#Column
#JsonIgnoreProperties({ "faqCreatedBy" })
#OneToMany(fetch = FetchType.EAGER,mappedBy = "user",cascade = {CascadeType.ALL})
#JsonManagedReference
public Set<UsersUnit> getUsersUnit() {
return usersUnit;
}
////...
}
With this change I am expecting the faq to load with User entity but I am not execting UsersRoles and UsersUnit to load.
But that is not what i see. When faq loads it loads User and UsersRoles and UsersUnit. I am using Spring JPA fyi. Any leads what is wrong ? Appreciate any inputs.
I have two tables, user and transaction. Where one user can have many transactions. So, everytime I create new user, they automatically make new transaction and the transaction type is SEND MONEY. But I don't understand how to write it in Spring JPA. Please take a look on my code and help me.
User.java
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long id;
#Column(name = "name")
private String name;
#Column(name = "email")
private String email;
#Column(name = "password")
private String password;
#Column(name = "money")
private int money;
//Getter Setter Constructor
}
Transaction.java
#Entity
#Table(name = "transaction")
public class Transaction {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id_trans")
private long id_trans;
#Column(name = "id_user")
private long id_user;
#Column(name = "transaction_date")
private Timestamp transaction_date;
#Column(name = "type") //Default set as "SEND MONEY"
private String type;
#Column(name = "trans_money") //From money in User.class
private int trans_money;
//Getter Setter Constructor
}
I know I should do something in my UserDAO.java, but I still don't know how to send data from body and split(?) it into two object (user and transaction, so I can persist it in UserDAO).
First of all, you have to write the relationship between User and Transaction.
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long id;
#Column(name = "name")
private String name;
#Column(name = "email")
private String email;
#Column(name = "password")
private String password;
#Column(name = "money")
private int money;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private Set<Transaction> transactions = new HashSet<>();
//Getter Setter Constructor
}
#Entity
#Table(name = "transaction")
public class Transaction {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id_trans")
private long id_trans;
#Column(name = "id_user")
private long id_user;
#Column(name = "transaction_date")
private Timestamp transaction_date;
#Column(name = "type") //Default set as "SEND MONEY"
private String type;
#Column(name = "trans_money") //From money in User.class
private int trans_money;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(referencedColumnName = "id")
private User user;
//Getter Setter Constructor
}
Then you have to create the JPA repository interface for the User
#Repository
public interface UserRepo extends CrudRepository <User, Long> {
}
Then in a service, you can do the following
#Service
public class UserService {
private UserRepo userRepo;
#Autowired
MealService(UserRepo userRepo){
this.userRepo = userRepo;
}
public void CreateNewUser(){
User user = new User();
// set its values
Transaction transaction = new Transaction();
transaction.setType("SEND MONEY");
// set other values
user.getTransactions().add(transaction);
userRepo.save(user);
}
}
Looking at the #Entity classes mentioned it seems to me that there exists an #ManyToOne association between Transaction and User which is not captured in the entity relationship modeling/mapping.
Please consider modeling that in your Transaction entity as follows,
#Entity
#Table(name = "transaction")
public class Transaction {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id_trans")
private long id_trans;
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE}, fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
#Column(name = "transaction_date")
private Timestamp transaction_date;
#Column(name = "type") //Default set as "SEND MONEY"
private String type;
#Column(name = "trans_money") //From money in User.class
private int trans_money;
//Getter Setter Constructor
}
Once you do that you can create a JPA repository class for Transaction and User and simply use the save method to do what you want in a transaction after constructing your instances. More on transactions in Spring Data JPA here
public interface TransactionRepository extends JpaRepository<Transaction, Long> {
}
I have some entity classes and i have a question because I want to clear it in my mind. In case of #ManyToOne relationship where I insert for example #JoinColumn(name = "cardHolderId"), shall I remove the primitive private Long cardHolderId? Because I had a discussion and a programmer explained to me that I can avoid declaring. But if I avoid it I can not use it in test cases like services.
#Entity
#Getter
#Setter
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "card")
public class Card {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "code")
private String code;
#Column(name = "number")
private String number;
#Column(name = "issueDate")
private Date issueDate;
#Column(name = "expireDate")
private Date expireDate;
#Column(name = "elasticDate")
private Date elasticDate;
#Column(name = "pin")
private Long pin;
#Column(name = "isValid")
private Boolean isValid;
#Column(name = "isUsed")
private Boolean isUsed;
#Column(name = "isPin")
private Boolean isPin;
#Column(name = "cardCategoryId")
private Long cardCategoryId;
#Column(name = "hasNumberOfVisits")
private Boolean hasNumberOfVisits;
#Column(name = "numberOfVisits")
private Long numberOfVisits;
#Column(name = "isBlackListed")
private Boolean isBlackListed;
#Column(name = "cardHolderId")
private Long cardHolderId;
//Check Relationships
#ManyToOne
#JoinColumn(name = "cardCategoryId")
private CardCategory cardCategory;
#ManyToOne
#JoinColumn(name = "cardHolderId")
private CardHolder cardHolder;
#Column(name = "companyGroupId")
private Long companyGroupId;
#ManyToOne
#JoinColumn(name = "companyGroupId")
private CompanyGroup companyGroup;
#OneToMany(mappedBy = "card")
private List<AccessControlSubject> accessControlSubjects = new ArrayList<>();
#OneToMany(mappedBy = "card")
private List<Card2Role> card2Roles = new ArrayList<>();
#ManyToOne
#JoinColumn(name = "cardHistoryId")
private CardHistory cardHistory;
#OneToMany(mappedBy = "vehicleCard")
private List<Vehicle> vehicles = new ArrayList<>();
#ManyToOne
#JoinColumn(name = "sharedCardId")
private PatrolSharedCard sharedCard;
#ManyToOne
#JoinColumn(name = "productionWorkId")
private PrdWork productionWork;
#ManyToOne
#JoinColumn(name = "productionWorkHitsId")
private PrdWorkHits productionWorkHits;
#OneToMany(mappedBy = "card")
private List<VisitorCardHistory> visitorCardHistories = new ArrayList<>();
You can remove it and use the id from the referred entity :
this.getCardHolder().getId()
Here are my entity classes.
JobPost.java
#Entity
#Table(name = "job_post")
public class JobPost {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "job_post_id")
private Long jobPostId;
#Column(name = "job_title")
private String jobTitle;
#Column(name = "job_description")
private String jobDescription;
#Column(name = "vacancy")
private int vacancy;
#Column(name = "posted_date")
#JsonFormat(pattern = "yyyy-MM-dd")
private Date postedDate;
#Column(name = "total_applicants")
private int totalApplicants;
}
JobApplication.java
#Entity
#Table(name = "job_application")
public class JobApplication {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "job_application_id")
private Long jobApplicationId;
#Column(name = "job_post_id")
private Long jobPostId;
#Column(name = "applicant_id")
private Long applicantId;
}
Applicant.java
#Entity
#Table(name = "applicant")
public class Applicant {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "applicant_id")
private Long applicantId;
#Column(name = "applicant_name")
private String applicantName;
#Column(name = "applicant_mobile_no")
private String applicantMobileNo;
#Column(name = "applicant_email")
private String applicantEmail;
}
My main goal is to listing the ApplicantList on JobPostId. I am totally new in Spring data JPA. Is JPA mappings are correct?. I don't know which query I should fire in order to fetch the applicantList based on jobPostId.
I would recommend to use JpaMappings and use SpringData instead of using native query.
Steps to follow:
Many-To-Many:
Use JoinTable to directly map JobPost and Applicant instead of creating a separate class.
Link for help:
https://attacomsian.com/blog/spring-data-jpa-many-to-many-mapping
Use SpringData JPA findOne or findById method (depends on spring version). If you use EAGER fetch then it will give you all Applicants associated with the JobPost Id.
One-To-Many
Keep JobApplication class and use OneToMany annotation.
Link for help:
https://attacomsian.com/blog/spring-data-jpa-one-to-many-mapping
Query:
#Query("select a from JobPost j inner join j.jobApplicantList ja inner join ja.applicant a where j.jobPostId=:jobPostId")
List<String> findAllJobApplicants(#Param("jobPostId") Long jobPostId);
I think that you should configure the mappings in such a way.To do this, you only need two entities
JobPost.java
#Entity
#Table(name = "job_post")
public class JobPost {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "job_title")
private String jobTitle;
#Column(name = "job_description")
private String jobDescription;
#Column(name = "vacancy")
private int vacancy;
#Column(name = "posted_date")
#JsonFormat(pattern = "yyyy-MM-dd")
private Date postedDate;
#Column(name = "total_applicants")
private int totalApplicants;
#ManyToMany
#JoinTable(name = "applicant_job_post",
joinColumns = {
#JoinColumn(name = "job_post_id", referencedColumnName = "id")
}, inverseJoinColumns = {
#JoinColumn(name = "applicant_id", referencedColumnName = "id")
})
private Set<Applicant> applicants;
public JobPost() {
}
public void addApplicant(Applicant applicant) {
applicants.add(applicant);
applicant.getJobPosts().add(this);
}
public void removeApplicant(Applicant applicant) {
applicants.remove(applicant);
applicant.getJobPosts().remove(this);
}
}
Applicant.java
#Entity
#Table(name = "applicant")
public class Applicant {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "applicant_name")
private String applicantName;
#Column(name = "applicant_mobile_no")
private String applicantMobileNo;
#Column(name = "applicant_email")
private String applicantEmail;
#ManyToMany(mappedBy = "applicants")
private Set<JobPost> jobPosts;
public Applicant() {
}
public void addJobPost(JobPost jobPost) {
jobPosts.add(jobPost);
jobPost.getApplicants().add(this);
}
public void removeJobPost(JobPost jobPost) {
jobPosts.remove(jobPost);
jobPost.getApplicants().remove(this);
}
}
I'm trying to achieve something like sql command below by using HQL and JPA.
Instead of "SELECT user_id..." I need SELECT OBJECT(o).
SELECT user_id FROM posix_user o INNER JOIN postgre_user n ON n.id=o.user_id WHERE n.name='USERNAME2'
I have some problems with this part of the code in JPA DAO:
public List<PosixUserEntity> listPosixUsers(final String uid_number) {
final StringBuilder queryString = new StringBuilder("SELECT OBJECT(o) FROM ");
queryString.append(this.entityClass.getSimpleName());
queryString.append(" o JOIN com.services.dao.user.jpa.UserEntity n ON (n.id=o.user_id) WHERE n.name LIKE :uid_number");
final Query findByNameQuery = entityManager.createQuery(queryString.toString()).setParameter("uid_number", uid_number);
return findByNameQuery.getResultList();
}
JOIN ON is not allowet here and I did not know how to replace it.
Also how can I replace com.services.dao.user.jpa.UserEntity by something cleaner.
There is my Entites, they may need to be improved:
#Entity
#Table(name = "posix_user")
public class PosixUserEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
//#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private String user_id;
#Column(name = "uid_number")
private String uid_number;
#Column(name = "home_directory")
private String home_directory;
#Column(name = "login_shell")
private String login_shell;
#Column(name = "group_id")
private String group_id;
//getters,setters....
#Entity
#Table(name = "postgre_user")
#SQLDelete(sql = "update postgre_user set status = 'removed' where id = ?")
public class UserEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name", unique = true, nullable = false)
private String name;
#Column(name = "password")
private String password;
#Enumerated(EnumType.STRING)
#Column(name = "status")
private UserStatus status;
#Column(name = "firstname")
private String firstName;
#Column(name = "lastname")
private String lastName;
#Column(name = "email")
private String email;
#Column(name = "usertype")
private String userType;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
private List<UserRoleTargetGroupEntity> userRoleTargetGroupEntity;
#Column(name = "last_login")
private String lastLogin;
#Column(name = "previous_login")
private String previousLogin;
#JsonIgnore
#Column(name = "change_password_flag")
private Boolean userPasswordResetFlag;
#OneToOne(cascade=CascadeType.ALL)
#PrimaryKeyJoinColumn
private PosixUserEntity posixUserEntity;
You may also need to know that FOREIGN KEY (user_id) REFERENCES postgre_user (id) - it should look like that
Can you know how can I modify my SELECT?
I've tested a simplified version of your classes
#Entity
#Table(name = "posix_user")
public class PosixUserEntity {
#Id
#Column(name = "user_id")
private Long user_id;
// getter + setter
}
#Entity
#Table(name = "postgre_user")
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToOne(cascade=CascadeType.ALL)
#PrimaryKeyJoinColumn
private PosixUser posixUserEntity;
// getter + setter
}
And this JPQL query works as expected
String jpql = "SELECT p "
+ "FROM UserEntity n JOIN n.posixUserEntity p "
+ "WHERE n.name LIKE :uid_number)";
JOIN is allowed because you have mapped the relationship in UserEntity.
and you don't need to specify the complete name of your entity class.
Check if it has been included when you define your persistence unit.
Hope this helps.