[Co Labor] 스프링 - 엔티티, 도메인 정의 및 컨트롤러 기초

2024. 9. 2. 20:45프로젝트: Co Laobr

엔티티, 도메인 정의

domain 패키지 안에 테이블 별 패키지를 생성하였다. 그리고 엔티티 클래스로 테이블을 생성하면 된다. 

 

먼저, 기업 회원 테이블을 생성해 보자.

미리 설계해 둔 ERD 다이어그램을 보면 

enterpirse_user 엔티티는 enterprise와 일대다 관계, 채용 공고와 일대다 관계이다. 처음에는 enterprise_user에 ManyToOne으로 enterprise을 지정해 두고 enterprise에 OneToMany로 enterprise_user를 지정했다. 

 

그 방식으로는 예를 들어 기업회원을 리턴하는 API에서 기업회원은 ManyToOne인 기업을 참조하고, 그 기업에서 OneToMany인 기업회원을 참조하면서 계속 서로를 참조하는 오류가 발생했다. 

 

이 문제를 트랜잭션으로 해결하는 것 같은데, 일단 ManyToOne 관계만 정의하는 방식으로 해결하였다.

package pelican.co_labor.domain.labor_user;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

import java.time.LocalDateTime;

@Getter
@Setter
@Entity
@Table(name = "laber_user", uniqueConstraints = @UniqueConstraint(columnNames = "email"))
public class LaborUser {

    @Id
    private Long labor_user_id;

    @Column(nullable = false)
    private String password;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false, unique = true)
    private String email;

    @Column(nullable = false, updatable = false)
    private LocalDateTime created_at;

    @PrePersist
    protected void onCreate() {
        created_at = LocalDateTime.now();
    }

}

간단하게 코드 설명을 하자면 다음과 같다.

  • @Entity 해당 클래스가 JPA 엔티티임을 나타내고 데이터베이스 테이블에 매핑된다.
  • @Table(name = "laber_user", uniqueConstraints = @UniqueConstraint(columnNames = "email")) 데이터베이스 테이블의 이름과 유니크 제약 조건을 설정한다. 위의 코드에서는 email 칼럼에 유니크 제약 조건을 설정했다.
  • @Id @GeneratedValue(strategy = GenerationType.IDENTITY) 테이블의 기본 키로 해당 칼럼을 설정하고 기본 키 생성 전략을 지정한다.
  • @PrePersist protected void onCreate() { created_at = LocalDateTime.now(); } 엔티티가 영속화되기 전 호출되는 메서드를 지정하여 created_at 필드를 현재 시각으로 설정한다.

다음은 Review 엔티티를 정의하는 코드를 보자. ManyToOne을 어떻게 정의하는지만 보면 된다.

package pelican.co_labor.domain.review;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import pelican.co_labor.domain.enterprise.Enterprise;
import pelican.co_labor.domain.labor_user.LaborUser;

import java.time.LocalDateTime;

@Getter
@Setter
@Entity
public class Review {

    @Id
    private Long review_id;

    @ManyToOne
    @JoinColumn(name = "labor_user_id")
    private LaborUser laborUser;

    @ManyToOne
    @JoinColumn(name = "enterprise_id")
    private Enterprise enterprise;

	  ...
    
}
  • @ManyToOne @JoinColumn(name = "labor_user_id") private LaborUser laborUser; foregin key를 Join문을 통해 가져올 수 있다.

위와 같은 방식으로 엔티티 클래스를 정의했다면 레파지토리 인터페이스를 정의하여 DB를 생성할 수 있다. 레파지토리는 repository 패키지 안에 테이블 별 패키지를 생성해서 레파지토리 인터페이스로 정의하였다.

package pelican.co_labor.repository.labor_user;

import pelican.co_labor.domain.labor_user.LaborUser;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
    public interface LaborUserRepository extends JpaRepository<LaborUser, Long> {
}

컨트롤러

이제 메인페이지를 보고 어떤 데이터가 필요할지 판단한 다음 컨트롤러를 작성해 주자.

위의 메인페이지를 구현하기 위해서는 다음의 사항을 구현해야 한다.

  1. Review, Job 엔티티를 JSON 형태로 넘겨주기
    • JpaRepository를 상속받고 있으면 findAll 메서드를 기본적으로 제공하기에 해당 메서드를 사용해서 넘겨주면 된다.
    • JSON으로 넘겨주려면 Map 형태의 response를 리턴하면 스프링부트가 자동으로 JSON으로 바꿔서 HTTP 응답 메시지에 써주게 된다.
    • Map에 추가할 때도 따로 모델이나 서비스 객체를 만들지 않고 도메인 객체를 재활용해도 된다. 어차피 DB에서 꺼내서 그대로 넘겨주기 때문이다.

코드로 바로 보자.

MainController.java

package pelican.co_labor.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import pelican.co_labor.domain.job.Job;
import pelican.co_labor.domain.review.Review;
import pelican.co_labor.repository.job.JobRepository;
import pelican.co_labor.repository.review.ReviewRepository;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/main")
public class MainController {

    private final JobRepository jobRepository;
    private final ReviewRepository reviewRepository;

    @Autowired
    public MainController(JobRepository jobRepository, ReviewRepository reviewRepository) {
        this.jobRepository = jobRepository;
        this.reviewRepository = reviewRepository;
    }

    @GetMapping
    public Map<String, Object> getMainPageData() {
        Map<String, Object> response = new HashMap<>();

        List<Job> jobs = jobRepository.findAll();
        List<Review> reviews = reviewRepository.findAll();

        response.put("jobs", jobs);
        response.put("reviews", reviews);

        return response;
    }
}
  • @RestController : RESTful API를 구현할 때 사용하는 컨트롤러이다. 일반적인 컨트롤러는 MVC 패턴을 따라 뷰를 리턴 하지만 레스트 컨트롤러는 HTTP 응답을 리턴한다.
    private final JobRepository jobRepository;
    private final ReviewRepository reviewRepository;

    @Autowired
    public MainController(JobRepository jobRepository, ReviewRepository reviewRepository) {
        this.jobRepository = jobRepository;
        this.reviewRepository = reviewRepository;
    }
  • 메인 컨트롤러에서 사용할 테이블의 Repository 객체를 주입해 주었다. 생성자 자동 주입을 사용하였고 이 부분은 롬복을 써도 무관하다.

Test Data 삽입 및 Postman 테스트

원하는 결과는 다음과 같다.

  1. review 리턴, review에는 enterprise, labor_user 포함
  2. job 리턴, job에는 enterprise, enterprise_user 포함

DataLoader에서 테스트 데이터를 삽입하였다.

package pelican.co_labor.config.test;

import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import pelican.co_labor.domain.enterprise.Enterprise;
import pelican.co_labor.domain.enterprise_user.EnterpriseUser;
import pelican.co_labor.domain.job.Job;
import pelican.co_labor.domain.labor_user.LaborUser;
import pelican.co_labor.domain.review.Review;
import pelican.co_labor.repository.enterprise.EnterpriseRepository;
import pelican.co_labor.repository.enterprise_user.EnterpriseUserRepository;
import pelican.co_labor.repository.job.JobRepository;
import pelican.co_labor.repository.labor_user.LaborUserRepository;
import pelican.co_labor.repository.review.ReviewRepository;

import java.time.LocalDateTime;

@Configuration
public class DataLoader {

    @Autowired
    private EnterpriseRepository enterpriseRepository;

    @Autowired
    private EnterpriseUserRepository enterpriseUserRepository;

    @Autowired
    private JobRepository jobRepository;

    @Autowired
    private LaborUserRepository laborUserRepository;

    @Autowired
    private ReviewRepository reviewRepository;

    @PostConstruct
    public void loadData() {
        // Enterprise 더미 데이터 생성
        Enterprise enterprise1 = new Enterprise();
        enterprise1.setEnterprise_id(1L);
        enterprise1.setName("Tech Company");
        enterprise1.setAddress("123 Tech Street");
        enterprise1.setDescription("Leading tech company in the industry.");
        enterpriseRepository.save(enterprise1);

        Enterprise enterprise2 = new Enterprise();
        enterprise2.setEnterprise_id(2L);
        enterprise2.setName("Data Corp");
        enterprise2.setAddress("456 Data Avenue");
        enterprise2.setDescription("Innovative data solutions provider.");
        enterpriseRepository.save(enterprise2);

        // EnterpriseUser 더미 데이터 생성
        EnterpriseUser enterpriseUser1 = new EnterpriseUser();
        enterpriseUser1.setEnterprise_user_id(1L);
        enterpriseUser1.setName("John Doe");
        enterpriseUser1.setEmail("john.doe@techcompany.com");
        enterpriseUser1.setPassword("password123");
        enterpriseUser1.setEnterprise(enterprise1);
        enterpriseUserRepository.save(enterpriseUser1);

        EnterpriseUser enterpriseUser2 = new EnterpriseUser();
        enterpriseUser2.setEnterprise_user_id(2L);
        enterpriseUser2.setName("Jane Smith");
        enterpriseUser2.setEmail("jane.smith@datacorp.com");
        enterpriseUser2.setPassword("password123");
        enterpriseUser2.setEnterprise(enterprise2);
        enterpriseUserRepository.save(enterpriseUser2);

        // LaborUser 더미 데이터 생성
        LaborUser laborUser1 = new LaborUser();
        laborUser1.setLabor_user_id(1L);
        laborUser1.setName("Alice Johnson");
        laborUser1.setEmail("alice.johnson@example.com");
        laborUser1.setPassword("password123");
        laborUserRepository.save(laborUser1);

        LaborUser laborUser2 = new LaborUser();
        laborUser2.setLabor_user_id(2L);
        laborUser2.setName("Bob Brown");
        laborUser2.setEmail("bob.brown@example.com");
        laborUser2.setPassword("password123");
        laborUserRepository.save(laborUser2);

        // Job 더미 데이터 생성
        Job job1 = new Job();
        job1.setJob_id(1L);
        job1.setTitle("Software Engineer");
        job1.setDescription("Develop and maintain software solutions.");
        job1.setGender("Any");
        job1.setAge("Any");
        job1.setViews(100);
        job1.setDead_date(LocalDateTime.now().plusDays(30));
        job1.setCreated_at(LocalDateTime.now());
        job1.setModified_at(LocalDateTime.now());
        job1.setEnterprise(enterprise1);
        job1.setEnterpriseUser(enterpriseUser1);
        jobRepository.save(job1);

        Job job2 = new Job();
        job2.setJob_id(2L);
        job2.setTitle("Data Scientist");
        job2.setDescription("Analyze and interpret complex data sets.");
        job2.setGender("Any");
        job2.setAge("Any");
        job2.setViews(150);
        job2.setDead_date(LocalDateTime.now().plusDays(45));
        job2.setCreated_at(LocalDateTime.now());
        job2.setModified_at(LocalDateTime.now());
        job2.setEnterprise(enterprise2);
        job2.setEnterpriseUser(enterpriseUser2);
        jobRepository.save(job2);

        // Review 더미 데이터 생성
        Review review1 = new Review();
        review1.setReview_id(1L);
        review1.setTitle("Great Place to Work");
        review1.setRating(5);
        review1.setPromotion_rating(4);
        review1.setSalary_rating(3);
        review1.setBalance_rating(5);
        review1.setCulture_rating(4);
        review1.setManagement_rating(4);
        review1.setPros("Great work-life balance and culture.");
        review1.setCons("Salary could be higher.");
        review1.setLike_count(10);
        review1.setCreated_at(LocalDateTime.now());
        review1.setModified_at(LocalDateTime.now());
        review1.setEnterprise(enterprise1);
        review1.setLaborUser(laborUser1);
        reviewRepository.save(review1);

        Review review2 = new Review();
        review2.setReview_id(2L);
        review2.setTitle("Challenging Environment");
        review2.setRating(4);
        review2.setPromotion_rating(3);
        review2.setSalary_rating(4);
        review2.setBalance_rating(3);
        review2.setCulture_rating(4);
        review2.setManagement_rating(3);
        review2.setPros("Great learning opportunities.");
        review2.setCons("Work-life balance can be challenging.");
        review2.setLike_count(8);
        review2.setCreated_at(LocalDateTime.now());
        review2.setModified_at(LocalDateTime.now());
        review2.setEnterprise(enterprise2);
        review2.setLaborUser(laborUser2);
        reviewRepository.save(review2);
    }
}

 

포스트맨으로 테스트한 결과 정상작동을 확인하였다.