yehey's 공부 노트 \n ο(=•ω<=)ρ⌒☆

[BE/spring boot] JPA ManyToMany 테이블 수정하기 본문

개발/프로젝트

[BE/spring boot] JPA ManyToMany 테이블 수정하기

yehey 2024. 1. 7. 19:09

Background

2023.11.27 - [개발/프로젝트] - [DB/RDBMS] ERD 설계

ERD 설계를 보면 one to many로 내역과 태그 테이블이 있다. 사실은 Many to Many 관계지만, many to many는 분리하는게 좋다고 들어서 one to many로 설계하고 관계 관련 테이블을 따로 만들도록 구성했었다.

현재 spring boot 의 ORM으로 JPA를 사용하고 있다. 그리고 JPA에서 entity를 정의하고 있고 내역 테이블과 태그 테이블을 many to many를 사용해서 관계를 정의해주었다. JPA에서 many to many로 정의하면 두 테이블의 관계 테이블이 자동으로 생성되는 것을 보고 적용해도 괜찮다고 판단해서 many to many로 두었다.

Issue

통계 기능을 추가하는 과정에서, tag 를 기반으로 내역을 가져와야하는 경우와 그 반대 경우로 조회하는 기능이 필요했다. JPA 쿼리를 사용해서 두 테이블의 관계 테이블을 사용하려 했으나, 해당 관계 테이블은 숨겨져 있기 때문에 임의로 사용하는게 불가능(혹은 내가 못찾았을 수도)했다. 또한 전체 합 SUM 과 같은 기능을 제공하려 했고, 필연적으로 쿼리를 사용했어야했다. 그래서 현 상태로는 내가 원하는 쿼리를 날리고 결과를 받을 수 없다.

My Solution

필요한 테이블은 내역과 태그의 연결 관계 테이블이었기 때문에, many to many로 연결하며 숨겨진 테이블로 자동 생성된 테이블을 entity로 명시해서 다루도록 수정할 것이다. ERD 에 그렸던 그대로 테이블을 수정한다.

기존 data는 혹시 모르니 통째로 덤프 뜨고 시작했다.

mysqldump -u root -p household_ledger > test.sql

Contents

기존 테이블에서의 @ManyToMany 를 지워줬다. tag,ledger에서는 @OneToMany 를, 관계 정의 테이블에서는 @ManyToOne을 걸어줬다.

//TagLedger 관계 테이블
public class TagLedgerRelation {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "relation_id")
    private Long id;

    @ManyToOne
    @JoinColumn(name = "tag_id")
    private Tag tag;

    @ManyToOne
    @JoinColumn(name = "ledger_id")
    private Ledger ledger;

}

지금와서 든 생각은 relation_id 는 굳이 없었어도 될거같다. tag_id, ledger_id를 복합키로 구성하는게 더 나았을 듯.

public class Ledger {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ledger_id")
    private Long ledgerID;

    @Column(name="date",nullable = false)
    private LocalDate date;

    @Column(name = "amount",nullable = false)
    private Long amount;

    @Column(name = "title",nullable = false)
    private String title;

    @Column(name="memo")
    private String memo;

    @OneToMany(mappedBy = "ledger") //TagLedgerRelation 테이블과의 관계
    List<TagLedgerRelation> relation;

}
public class Tag {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "tag_id")
    private Long tagID;

    @Column(name = "tag_name",nullable = false)
    private String name;

    @OneToMany(mappedBy = "tag") //TagLedgerRelation 테이블과의 관계
    private List<TagLedgerRelation> relation;

}

관계를 위와 같이 정의해주고, entity 변경으로 인해 서비스 로직 등 다른 곳에서 발생한 오류들을 수정해주면 된다.

기존 덤프데이터가 있다면 반영해주면 된다.

나는 기존 table과 다르게 TABLE name 도 다르고 relation_id 열이 하나 늘어서 이를 수정하고 덤프데이터를 반영해주었다.

test.sql을 dumptest DB 로 불러오고

use dumptest;

create table tag_ledger_relation(
    relation_id bigint NOT NULL auto_increment,
    tag_id bigint,
    ledger_id bigint,
    PRIMARY KEY(relation_id)
); // entity가 된 ledger-tag 관계정의 테이블

// 기존 many to many에서 생성된 관계 테이블의 데이터를 새롭게 생성한 테이블에 insert
insert into tag_ledger_relation(tag_id,ledger_id)
select tag_id, ledger_id from ledger_tag_relation;

 

필요 없는 테이블은 정리하고 dumptest DB를 dump 뜨고, 실제 DB 날려주고 (test.sql 백업본이 있고, 개발환경이니까 가능..), 새롭게 dump 뜬 DB로 복구해준다.

 

잘 생성됐고 다행히도 데이터 손실 없이 수정할 수 있었다. 수정된 코드에서도 기존 코드들은 잘 동작했다.

이제 relation 테이블을 JPA repository에서 쿼리로 사용할 수 있다.

public interface TagLedgerRelationRepository  extends JpaRepository<TagLedgerRelation,Long> {
    @Query(value="select COALESCE(sum(r.ledger.amount),0) from TagLedgerRelation r where r.tag.tagID=:tagID and r.ledger.date>=:start and r.ledger.date<=:end and r.ledger.archiveTypeID.archiveTypeID=:archiveTypeID")
    Long getTotalSumByTagAndDate(@Param(value = "tagID")Long tagID, @Param(value="start") LocalDate start, @Param(value = "end") LocalDate end,@Param(value = "archiveTypeID")Long archiveTypeID);
}

희희... 이제 통계 API 만들어야지......

 


Error Review

DB 설계할 때는 잘 해놓고.. 잘 안읽어서 이 사단을 내다니!!!! 그리고 many to many는 사용하는게 권장되지 않기 때문에 앞으로는 one to many 로 관계를 정의하는 방식으로 진행해야겠다. 

참조

https://ict-nroo.tistory.com/127

https://www.baeldung.com/jpa-many-to-many

 

Comments