Spring_boot/Project

[Spring] 다양한 연관관계 처리

달의요정루나 2021. 8. 7. 11:58

- 예제 프로젝트 작성

추가되는 라이브러리

1) application.properties 설계

spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver

spring.datasource.url = jdbc:mysql://localhost/jpa_ex?characterEncoding=utf8&serverTimezone=Asia/Seoul

spring.datasource.username = root

spring.datasource.password = root


spring.jpa.hibernate.ddl-auto=create

spring.jpa.generate-ddl=false

spring.jpa.show-sql=true

spring.jpa.database=mysql

logging.level.org.hibernate=info

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect

2) 각각의 엔티티 클래스 설계

* 연관관계를 맺을 엔티티 클래스가 존재해야 하므로 데이터베이스를 고려해서 생성

- org.zerock.domain.Member.java

package org.zerock.domain;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
@Entity
@Table(name="tbl_members")
@EqualsAndHashCode(of="uid")
public class Member {

	@Id
	private long uid;
	private String upw;
	private String uname;
}

- org.zerock.domain.Profile.java

package org.zerock.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString(exclude = "member") //Member와 관련된 toString()을 호출하지 않게 한 부분
@Entity
@Table(name="tbl_profile")
@EqualsAndHashCode(of="fname")
public class Profile {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
    	//데이터베이스에 맞게 개인정보를 식별키로 처리
	private Long fno;
	
	private String fname;
	
	private boolean current;
	
	@ManyToOne
    	//연관 관계는 다대일
    	//Member와 Profile 일대다
    	//Profile과 Member는 다대일
	private Member member;
}

3) Repository 작성

* Member와 Profile은 Member를 처리하는 Repository를 생성해서 회원 데이터를 처리

* 반면 Profile을 저장할 때는 Member 객체를 통해 Profile을 처리할 수 없어서 Profile을 처리하는 Repository를 설계해야함

- org.zerock.persistence.MemberRepository.java

package org.zerock.persistence;

import org.springframework.data.jpa.repository.JpaRepository;
import org.zerock.domain.Member;

public interface MemberRepository extends JpaRepository<Member, String> {

}

- org.zerock.persistence.ProfileRepository.java

package org.zerock.persistence;

import org.springframework.data.jpa.repository.JpaRepository;
import org.zerock.domain.Profile;

public interface ProfileRepository extends JpaRepository<Profile, Long> {

}

3) pom.xml에 query, maven 코드 삽입

https://github.com/zerockcode/boot06

 

GitHub - zerockcode/boot06: 스타트 스프링 부트 6장

스타트 스프링 부트 6장. Contribute to zerockcode/boot06 development by creating an account on GitHub.

github.com

위의 깃허브에 들어가셔서 파일들을 다운로드 받으셔야 합니다. 그 다음 pom.xml 파일을 엽니다.

<dependency>
	<groupId>com.querydsl</groupId>
	<artifactId>querydsl-apt</artifactId>
	<scope>provided</scope>
</dependency>

<dependency>
	<groupId>com.querydsl</groupId>
	<artifactId>querydsl-jpa</artifactId>
</dependency>

- query 관련 구문

<plugin>
	<groupId>com.mysema.maven</groupId>
	<artifactId>apt-maven-plugin</artifactId>
	<version>1.1.3</version>
		<executions>
			<execution>
				<goals>
					<goal>process</goal>
				</goals>
				<configuration>
					<outputDirectory>target/generated-sources/java</outputDirectory>
					<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
				</configuration>
			</execution>
		</executions>
</plugin>

- maven 관련 구문

 

위에 있는 두 구문을 각각 프로젝트에 있는 pom.xml에  <dependencies></dependencies>와 <plugins></plugins>사이에 추가한다.

 

4) 테스트 검증

- org.zerock.controller.BoardController.java

package org.zerock.controller;

import java.util.stream.IntStream;

import javax.annotation.PostConstruct;
import javax.transaction.Transactional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
import org.zerock.domain.Member;
import org.zerock.domain.Profile;
import org.zerock.persistence.MemberRepository;
import org.zerock.persistence.ProfileRepository;

@RestController
public class BoardController {

	@Autowired
	MemberRepository memberRepo;
	
	@Autowired
	ProfileRepository profileRepo;
	
	@Transactional
	@PostConstruct
	public void init() {
		IntStream.range(1, 101).forEach(i-> {
        		//100명의 회원 데이터 생성
			Member member = new Member();
			member.setUid(Long.valueOf(i));
			member.setUpw("pw"+i);
			member.setUname("사용자"+i);
			
			memberRepo.save(member);
			
		});
		
		//////////////////////////////////////////////////////////////////////
		
		Member member = new Member();
		member.setUid(Long.valueOf(1L));
		for (int i = 1; i < 5; i++) {
        		//4개의 데이터가 생성
			Profile profile1 = new Profile();
			profile1.setFname("face"+i+".jpg");
			
			if (i==1) {
				profile1.setCurrent(true);
			}
			
			profile1.setMember(member);
			profileRepo.save(profile1);
		}
		
	}
	
}

5) 실행후 결과

실행 후 생성된 테이블이다.(pds는 추후에 설명)

1) tbl_members

 2) tbl_profile

- 예제 프로젝트 작성(자료실과 첨부 파일의 관계)

* PDSBoard: 자료를 의미

* PDSFile: 각 자료에 첨부되는 파일을 의미

 

1) 엔티티 클래스 작성

- org.zerock.domain.PDSBoard.java

package org.zerock.domain;

import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString(exclude = "files")
@Entity
@Table(name="tbl_pds")
@EqualsAndHashCode(of="pid")
public class PDSBoard {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long pid;
	private String pname;
	private String pwriter;
	
	@OneToMany(cascade = CascadeType.ALL)
    	//cascade가 없으면 JPA에서 한 번에 여러 엔티티 객체들의 상태를 변경해야 함
        //즉, 한번에 PDSBoard 객체로 보관해야 하고, PDSFile의 상태도 보관해야 함.
        //cascade(영속성 전이)를 입력하고 ALL을 붙여서 모든 변경에 대해 전이가 가능하게 해야함
	@JoinColumn(name="pdsno")
	private List<PDSFile> files;
}

- org.zerock.domain.PDSFile.java

package org.zerock.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
@Entity
@Table(name="tbl_pdsfiles")
@EqualsAndHashCode(of="fno")
public class PDSFile {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long fno;
	
	private String pdsfile;
}

2) 연관관계에 따른 Repository 설계

- org.zerock.persistence.PDSBoardRepository.java

<Update구문>

package org.zerock.persistence;

import javax.transaction.Transactional;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.zerock.domain.PDSBoard;

public interface PDSBoardRepository extends JpaRepository<PDSBoard, Long>{

	@Modifying //이 어노테이션으로 DML(insert, update, delete) 작업을 처리할 수 있음
	@Transactional
    	//스프링에서 트랜잭션 처리를 지원하는 어노테이션(delete, update를 사용할 때 선언)
	@Query("UPDATE FROM PDSFile f set f.pdsfile = ?2 WHERE f.fno = ?1 ")
	public int updatePDSFile(Long fno, String newFileName);

}

<Delete 구문>

package org.zerock.persistence;

import javax.transaction.Transactional;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.zerock.domain.PDSBoard;

public interface PDSBoardRepository extends JpaRepository<PDSBoard, Long>{

	@Modifying
	@Transactional
	@Query("DELETE FROM PDSFile f WHERE f.fno = ?1")
	public int deletePDSFile(Long fno);
}

3) 테스트 검증

package org.zerock.controller;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;

import javax.annotation.PostConstruct;
import javax.transaction.Transactional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
import org.zerock.domain.PDSBoard;
import org.zerock.domain.PDSFile;
import org.zerock.persistence.PDSBoardRepository;

@RestController
public class BoardController {
	
	@Autowired
	PDSBoardRepository pdsBoardRepo;
	
	@Transactional
	@PostConstruct
	public void init() {
		
		PDSBoard pdsBoard = new PDSBoard();
		pdsBoard.setPname("Document");
		
		PDSFile file1 = new PDSFile();
		file1.setPdsfile("file1.doc");
		
		PDSFile file2 = new PDSFile();
		file2.setPdsfile("file2.doc");
		
		pdsBoard.setFiles(Arrays.asList(file1,file2));
		
		pdsBoardRepo.save(pdsBoard);

		//////////////////////////////////////////////////////////////////////
		
		List<PDSBoard> list = new ArrayList<>();
        	//특정 자료의 번호와 자료의 제목, 첨부 파일의 수를 같이 보여주어야 하는 상황
            	//이럴 때 @Query를 이용해 조인을 처리해 해결
		IntStream.range(1, 100).forEach(i-> {
		/* 
                100개의 200개의 파일 데이터 추가
                각각의 데이터를 save 하지 않고 List<PDSBoard>에 데이터들을 보관했다가,
                한 번에 저장
                */
            	
			PDSBoard pds = new PDSBoard();
			pds.setPname("자료 "+i);
			
			
			PDSFile file_1 = new PDSFile();
			file_1.setPdsfile("file_1.doc");
			
			
			PDSFile file_2 = new PDSFile();
			file_2.setPdsfile("file_2.doc");
			
						
			pds.setFiles(Arrays.asList(file_1, file_2));
			list.add(pds);
			
			
		});
		pdsBoardRepo.saveAll(list);
	}
	
}

4) 결과

- tbl_pds

- tbl_pdsfiles