관리 메뉴

JAN's History

스프링과 JPA를 이용한 웹개발 프로젝트_데이터 연동(MySQL), 컨트롤러 본문

스프링

스프링과 JPA를 이용한 웹개발 프로젝트_데이터 연동(MySQL), 컨트롤러

JANNNNNN 2024. 4. 7. 11:40

엔티티를 작성했으니 데이터베이스를 MySQL과 연동해봅시다.👍

Helper 메소드 정의하기! 

정의하기에 앞서, Helper 메소드가 무엇인지 알아볼까요?

  • Helper 메소드는 양방향 연관관계에서 사용되는 방법인데요. 두 엔티티 간의 관계를 설정하고 데이터를 전달하는     코드를 캡슐화하여 코드를 더 간결하고 읽기 쉽게 만들어주는 도구랍니다.
  • Helper 메소드를 통해 양방향 연관관계를 설정하고 유지하는 로직을 한 곳에 집중시킬 수 있어 코드의 복잡성이 감소된다는 장점이 있습니다.
  • 즉, Helper 메소드는 양방향 연관관계에서 한 엔티티가 수정되었을 때 다른 엔티티의 정보를 동기화하기 위해 사용됩니다!

➡️ 따라서 양방향 관계에서는 Helper 메서드를 사용하여 관계를 설정하고 유지하는 것이 일반적으로 권장되는 방법입니다.

 

SeedStarter 엔티티

public class SeedStarter {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "SEED_STARTER_ID")
    private Long id;
    ..
    ..
    //---여기서부터 helper method
    public void setFeature(Feature feature){
    	this.features.add(feature);
    	feature.setSeedStarter(this);
    }
    public void setDetail(Detail detail){
        this.details.add(detail);
        detail.setSeedStarter(this);
    }
}

 

SeedStarter 의 양방향인 Feature와 Detail 엔티티에게 한 엔티티가 수정되었을 때 동기화할 수 있도록 helper 메소드를 정의해주도록 합니다.

Helper 메소드 설명 : SeedStarter  Feature엔티티의 정보가 UPDATE되면 SeedStarter 엔티티 속 this.feature에게도 업데이트 해주고,  Feature 객체 속 SeedStrater 객체에게도 UPDATE 해준다는 뜻입니다.

➡️약간.. 어떤 느낌이냐면요.새로운 feature 값이 들어오면 내 엔티티에 일단 그 값을 업데이트하고, 업데이트 된 최신버전 내 엔티티를 (this)로 받아서 상대편 엔티티에도 넣어주는 느낌이랍니다 

➕feature의 타입이 List이기 때문에 this.feature = feature가 아니라 .add(feature)를 사용한 것입니다.

Feature 엔티티

public void setSeedStarter(SeedStarter seedStarter){
    this.seedStarter = seedStarter;
    seedStarter.getFeatures().add(this);
}

Helper 메소드 설명 : SeedStarter메소드가 UPDATE되면 그 Feature엔티티에 SeedStarter 값을 UPDATE하고, SeedStarter 엔티티에 Feature값도 업데이트 하겠다는 뜻입니다.

Detail 엔티티

public void setSeedStarter(SeedStarter seedStarter){
    this.seedStarter = seedStarter;
    seedStarter.getDetails().add(this);
}

SeedStarter 전체

public class SeedStarter {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "SEED_STARTER_ID")
    private Long id;
    
    private LocalDateTime datePlanted;
    private boolean covered;
    @Enumerated(EnumType.STRING)
    private Type type;
    
    @OneToMany(mappedBy = "seedStarter", cascade = CascadeType.PERSIST ,orphanRemoval = true)
    private List<Feature> features = new ArrayList<>();
    
    @OneToMany(mappedBy = "seedStarter", cascade = CascadeType.PERSIST, orphanRemoval = true)
    private List<Detail> details = new ArrayList<>();
    
    public void setFeature(Feature feature){
    this.features.add(feature);
    feature.setSeedStarter(this);
    }
    
    public void setDetail(Detail detail){
    this.details.add(detail);
    detail.setSeedStarter(this);
    }
}
public enum Type {
	PLASTIC, WOOD
}

Feature 전체

public class Feature {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "FEATURE_ID")
    private Long id;

    @Enumerated(EnumType.STRING)
    private FeatureType name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "SEED_STARTER_ID")
    private SeedStarter seedStarter = new SeedStarter();

    public void setSeedStarter(SeedStarter seedStarter){
        this.seedStarter = seedStarter;
        seedStarter.getFeatures().add(this);
    }
}
public enum FeatureType {
	SUBSTRATE, FERTILIZER, PH_CORRECTOR
}

Detail 전체

public class Detail {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "DETAIL_ID")
    private Long id;
    private int rowNum;
    private int seedPerCell;
    private String variety;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "SEED_STARTER_ID")
    private SeedStarter seedStarter;

    public void setSeedStarter(SeedStarter seedStarter){
        this.seedStarter = seedStarter;
        seedStarter.getDetails().add(this);
    }
}

 

중간 정리

Entity를 설계하고 양방향 연관관계까지 설정하고, 동기화를 위한 Helper 메소드까지 선언해주었습니다!


데이터베이스 준비

application.yml

spring:
    datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/jpa_playground?serverTimezone=Asia/Seoul
        username: root
        password: 1234
    jpa:
        database-platform: org.hibernate.dialect.MySQL8Dialect
        hibernate:
        	ddl-auto: create
        show-sql: true
        properties:
     	   hibernate.format_sql: true

1. url에 jpa_playground 부분에는 자신이 만든 파일 명을 적어주면 됩니다.

2. username와 password는 MySQL에서 자신이 설정한 파일에 맞게 적어주시면 됩니다. 

스프링 부트 어플리케이션을 실행하여 테이블 생성 확인

이렇게 실행되면 데이터베이스가 잘 연동된 것입니다!

데이터베이스 준비

INSERT INTO SEED_STARTER(covered,date_planted,type) VALUES(0,'2002-04-05 10:10:10','PLASTIC');
INSERT INTO SEED_STARTER(covered,date_planted,type) VALUES(0,'2001-04-05 10:10:10','WOOD');
INSERT INTO SEED_STARTER(covered,date_planted,type) VALUES(1,'2003-04-05 10:10:10', 'WOOD');

INSERT INTO FEATURE(name,SEED_STARTER_ID) VALUES('SUBSTRATE',1);
INSERT INTO FEATURE(name,SEED_STARTER_ID) VALUES('PH_CORRECTOR',1);
INSERT INTO FEATURE(name,SEED_STARTER_ID) VALUES('FERTILIZER',2);
INSERT INTO FEATURE(name,SEED_STARTER_ID) VALUES('PH_CORRECTOR',3);

INSERT INTO DETAIL(ROW_NUM,SEED_PER_CELL, VARIETY, SEED_STARTER_ID) VALUES(1,10,'Thymus vularis',1);
INSERT INTO DETAIL(ROW_NUM,SEED_PER_CELL, VARIETY, SEED_STARTER_ID) VALUES(2,15,'Thymus pseudolaginosus',1);
INSERT INTO DETAIL(ROW_NUM,SEED_PER_CELL, VARIETY, SEED_STARTER_ID) VALUES(3,20,'x citriodorus',1);
INSERT INTO DETAIL(ROW_NUM,SEED_PER_CELL, VARIETY, SEED_STARTER_ID) VALUES(1,5,'Thymus herba-barona',2);
INSERT INTO DETAIL(ROW_NUM,SEED_PER_CELL, VARIETY, SEED_STARTER_ID) VALUES(2,5,'Thymus serpyllum',2);
INSERT INTO DETAIL(ROW_NUM,SEED_PER_CELL, VARIETY, SEED_STARTER_ID) VALUES(1,20,'New Variety',3);

데이블 확인

SEED_STARTER와 Feature의 정합성 확인

SeedStarter의 PK와 Feature의 FK의 연관관계가 잘 되어있는지 확인하기 위해 JOIN을 통해 잘 가져와지는지 확인하기 위함입니다!

SELECT *
FROM SEED_STARTER AS s
NATURAL JOIN FEATURE AS f;

➡️FeatureType은 SUBSTRATE, FERTILIZER, PH_CORRECTOR로 총 3가지입니다.

 SEED_STARTER와 DETAIL 정합성 확인

SELECT *
FROM SEED_STARTER AS s
NATURAL JOIN DETAIL AS d;

전체 데이터 정합성 확인

SELECT *
FROM SEED_STARTER AS s
NATURAL JOIN FEATURE AS f
NATURAL JOIN DETAIL AS d;

➡️전체적인 그림을 이렇게 생각하면 됩니다


MySQL과 데이터베이스를 연동했으니 이제 그 데이터를 화면에 뿌려줄 컨트롤러를 만들어볼까요?

@Autowired를 활용한 컨트롤러 코드 

@Controller
    public class SeedStarterMngController {
    
    private SeedStarterService seedStarterService;
    
    @Autowired
    public SeedStarterMngController(SeedStarterService seedStarterService) {
        this.seedStarterService = seedStarterService;
    }
    
    @RequestMapping({"/","/seedstartermng"})
    public String showSeedstarters(final SeedStarter seedStarter, Model model) {
    List<SeedStarter> all = seedStarterService.findAll();
    model.addAttribute("allSeedStarters", all);
    return "seedstartermng";
}

➕@Autowired이 뭐더라 ..?

  • @Autowired는 클래스 간의 의존성을 주입할 때 사용되는 어노테이션인데요. 이를 통해 Spring 컨테이너는 프로그램에서 필요로 하는 빈(Bean)을 자동으로 찾아서 해당 클래스나 필드에 주입해줍니다!
  • Bean을 모르신다면 스프링 컨테이너를 공부하고 오시기를 추천드려요

어찌됐든, @Autowired를 사용하면 SeedStarterService를 사용하는 생성자를 주입해주는 코드를 직접 만들고 난 후에, 생성자 위에 어노테이션을 붙여줘야합니다. 

그러나 많은 코드를 작성할 때에는 코드의 복잡성이 생깁니다 

@RequiredArgsConstructor를 사용한 컨트롤러 코드

@Controller
@RequiredArgsConstructor
public class SeedStarterMngController {
	private final SeedStarterService seedStarterService;
    ...

그러나 !

lombok에  @RequiredArgsConstructor를 사용하면 생성자 인젝션을 통해 의존성을 주입할 수 있습니다. 이는 코드를 간결하게 만들어줍니다. 따라서 생성자를 별도로 선언할 필요 없이 클래스의 의존성을 바로 주입할 수 있습니다.

그리고 @RequiredArgsConstructor를 사용하면 final 키워드가 지정된 필드에 대한 생성자가 자동으로 생성되어 final을 사용할 수 있습니다 .