Just Do IT!

SpringBoot, JPA 구조 이해하기 본문

개발 공부/Spring

SpringBoot, JPA 구조 이해하기

MOON달 2024. 8. 21. 14:34
728x90
반응형

JPA package 구조는 위의 그림과 같다.

dao 역할을 repository가 하는 것이다.

 

 

 

 

 

 

 

MyBatis package

이전에 배웠던 mybatis에서는 mapper를 사용하기 때문에 따로 dao를 만들지 않고 mapper package를 생성했었다.

 

package com.kosta.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

import com.kosta.dto.User;

@Mapper
public interface UserMapper {
	void save(User user) throws Exception;
	
	void deleteById(int id) throws Exception;
	
	User findById(int id) throws Exception;
	
	List<User> findAll() throws Exception;
	
	void updateUser(User user) throws Exception;
}

 

실습 프로젝트의 일부인데, 이처럼 mapper interface를 만들었고,

resources 폴더 내에 mapper 폴더를 만들어 sql문을 작성했었다.

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.kosta.mapper.UserMapper">
    <insert id="save" parameterType="com.kosta.dto.User">
        INSERT INTO users_tbl (name, email) VALUES (#{name}, #{email})
    </insert>
    
    <update id="deleteById" parameterType="int">
        UPDATE users_tbl SET is_deleted = 'y' WHERE id = #{id}
    </update>
    
    <select id="findById" parameterType="int" resultType="com.kosta.dto.User">
        SELECT * FROM users_tbl WHERE id = #{id} AND is_deleted = 'n'
    </select>
    
    <select id="findAll" resultType="com.kosta.dto.User">
        SELECT * FROM users_tbl WHERE is_deleted = 'n'
    </select>
    
    <update id="updateUser" parameterType="com.kosta.dto.User">
        UPDATE users_tbl SET name = #{name}, email = #{email} WHERE id = #{id}
    </update>

</mapper>

 

이렇게 mapper 인터페이스와 xml 파일을 통해 mybatis는 dao 역할을 했었다.

그러나 이렇게 mybatis package 형식을 사용하다보면 sql문이 중복되는 경우도 있기 때문에 한 눈에 파악하기 어렵다.

mybatis는 데이터베이스와 상호작용할 때 개발자가 직접 sql 쿼리를 작성하고, 데이터 매핑을 위해 xml 또는 어노테이션을 사용한다.

 

반면에 JPA는 객체와 관계형 데이터베이스 간의 매핑을 제공하기 때문에 간결하게 API를 제공한다.

JPA를 사용하면 개발을 간소화하고 애플리케이션 성능을 개선할 수 있다.

아직 JPA를 배우는 초기라서 이 정도밖에 설명할수 없다(ㅋㅋㅋ)

추후에 조금 더 공부하면서 보충해야겠다.

 

 

그렇다면,

앞의 이미지로 돌아가서 JPA 구조에 대해서 정리해보려고 한다.

 

 

 

 

 

 

 

 

Controller

  • 클라이언트의 요청을 처리하고 적절한 응답을 반환하는 역할
  • HTTP 요청을 받고, 비즈니스 로직을 처리하기 위해 서비스 레이어를 호출하며, 결과를 클라이언트에게 전달
  • 해당 요청 url에 따라 적절한 view와 mapping을 처리한다.
  • @Autowired Service를 통해 service의 method를 이용한다.
  • 사용자의 입력을 받고 서비스를 전달하는 역할
  • 적절한 DTO를 담아서 client에 전달한다.

 

실습에서 한 코드로 예시를 들어보면

package com.kosta.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import com.kosta.entity.Member;
import com.kosta.service.MemberService;

@Controller
public class MemberController {
	@Autowired
	private MemberService ms;

	// 전체 멤버 리스트
	@GetMapping("/list")
	public ModelAndView mainPage() throws Exception {
		ModelAndView mav = new ModelAndView("member/list");
		List<Member> memberList = ms.getAllMembers();
		mav.addObject("list", memberList);
		return mav;
	}

	// 멤버 추가
	@GetMapping("/add")
	public ModelAndView addPage() throws Exception {
		ModelAndView mav = new ModelAndView("member/add");
		return mav;
	}

	@PostMapping("/add")
	public String addMember(Member member) throws Exception {
		ms.insertMember(member);
		return "redirect:/list";
	}

	// 멤버 삭제
	@GetMapping("/delete/{id}")
	public String deleteMember(@PathVariable("id") int id) throws Exception {
		ms.deleteMemberById(id);
		return "redirect:/list";
	}

	// 멤버 수정
	@GetMapping("/modify/{id}")
	public String modifyPage(@PathVariable("id") int id, Model model) throws Exception {
		Member member = ms.getMemberById(id);
		model.addAttribute("member", member);
		return "member/add";
	}

	@PostMapping("/modify")
	public String modifyMemberName(Member member) throws Exception {
		ms.updateMemberName(member);
		return "redirect:/list";
	}

	// 멤버 검색하기
	@GetMapping("/search")
	public String searchName(@RequestParam("keyword") String keyword, Model model) throws Exception {
		List<Member> searchResult = ms.searchMember(keyword);
		model.addAttribute("list", searchResult);
		return "member/list";
	}

	// 멤버 정보 보여주기
	@GetMapping("/detail/{id}")
	public ModelAndView detailPage(@PathVariable("id") int id) throws Exception {
		ModelAndView mav = new ModelAndView("member/detail");
		Member member = ms.getMemberById(id);
		mav.addObject("member", member);
		return mav;
	}
}

 

이런식으로 해당 url에서 어떤 동작을 하는지 지정하는 것이 controller의 역할이다.

 

 

 

Service

  • 비즈니스 로직을 구현하는 클래스가 포함
  • controller와 repository 사이의 중개 역할을 하며 데이터 처리와 비즈니스 규칙을 정의한다
  • DAO로 DB가 접근하고 DTO로 데이터를 전달받은 다음, 비즈니스 로직을 처리해 적절한 데이터를 반환한다.
  • @Autowired Repository를 통해 repository의 method를 사용한다. (Hibernate JPA 사용)
  • Service Interface를 만들고, 해당 interface를 구현한 class를 만든다.

 

MemberService (interface)

package com.kosta.service;

import java.util.List;

import com.kosta.entity.Member;

public interface MemberService {
	List<Member> getAllMembers() throws Exception;

	void insertMember(Member member) throws Exception;

	void deleteMemberById(int id) throws Exception;

	Member getMemberById(int id) throws Exception;

	void updateMemberName(Member member) throws Exception;

	List<Member> searchMember(String keyword) throws Exception;

}

 

IMemberService

package com.kosta.service;

import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.kosta.entity.Member;
import com.kosta.repository.MemberRepository;

@Service
public class IMemberService implements MemberService {
	@Autowired
	private MemberRepository memberRepository;

	@Override
	public List<Member> getAllMembers() throws Exception {
		return memberRepository.findAll();
	}

	@Override
	public void insertMember(Member member) throws Exception {
		memberRepository.save(member);
	}

	@Override
	public void deleteMemberById(int id) throws Exception {
		memberRepository.deleteById(id);
	}

	@Override
	public Member getMemberById(int id) throws Exception {
		Optional<Member> optMember = memberRepository.findById(id);
		Member member = optMember.orElseThrow(() -> new Exception("없는 아이디입니다."));
		return member;
	}

	@Override
	public void updateMemberName(Member member) throws Exception {
		Member originMember = getMemberById(member.getId()); // 기존 멤버를 가져오기
		originMember.setName(member.getName());
		memberRepository.save(originMember);

	}

	@Override
	public List<Member> searchMember(String keyword) throws Exception {
		return memberRepository.findByNameContains(keyword);
	}

}

 

IUserService는 UserService 인터페이스를 구현한 것이므로 @Autowired를 통해 controller에서 이용할 수 있다.

@Autowired Repository를 통해 repository의 method를 이용하는 것을 볼 수 있다.

 

 

 

 

 

DAO

  • 실제로 DB에 접근하는 객체
  • Service와 DB를 연결하는 고리 역할을 한다.

 

위에 mapper 예시처럼 mybatis에서는 dao 대신 mapper를 사용한다.

그렇지만 JPA에서는 dao로 repository를 사용한다.

 

  • 실제로 DB에 접근하는 객체이다.
  • Service와 DB를 연결하는 고리의 역할을 한다.
  • SQL을 직접 사용하여 DB에 접근한 후 적절한 CRUD API를 제공한다.
    • JPA 대부분의 기본적인 CRUD method를 제공하고 있다.
    • extends JpaRespoitory<T, ID>

 

package com.kosta.repository;

import java.util.List;
import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.kosta.entity.Member;

@Repository
public interface MemberRepository extends JpaRepository<Member, Integer> {
	// name으로 해당 회원이 추가되는지 확인
	Optional<Member> findByName(String name);

	// 검색어를 입력해서 검색되는지 확인
	List<Member> findByNameContains(String keyword);
}

 

이 예시를 보면, 기존에 존재하는 메소드들이 많기 때문에 새로 추가하는 것들만 해당 클래스에 추가해주었다.

위의 IUserService에 보면 save, deleteById() 등 다양한 method를 제공해주기 때문에 자동으로 사용 가능하다.

 

 

 

 

 

DTO

  • 계층 간 데이터 교환을 위한 객체
  • DB에서 데이터를 얻어 Service나 Controller 등으로 보낼 때 사용한다.
  • 로직이 없고 getter/setter만 존재한다.
  • lombok을 사용하면 편리하게 사용할 수 있다.

 

VO(Value Object)

DTO와 동일한 개념이지만, read only 속성을 가지고 있다.

 

 

 

 

 

Entity Class

  • 데이터베이스의 테이블과 매핑되는 클래스
    • @Entity, @Column, @Id 등을 이용해 데이터베이스 테이블을 생성한다.
  • JPA entity는 데이터베이스와의 상호작용을 위해 객체와 테이블 간의 매핑을 정의한다.

 

Entity class와 DTO class를 분리하는 이유?

  • View Layer와 DB Layer의 역할을 분리하기 위해서 사용
  • 테이블과 매핑되는 Entity 클래스가 변경되면 여러 클래스에 영향을 준다.
  • View와 통신하는 DTO 클래스(Request, Response 클래스)는 자주 변경되므로 분리해야 한다.

 

 

실습에서는 dto package를 생성하지 않고 entity package만 생성했었다.

package com.kosta.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity(name = "member_tbl")
@NoArgsConstructor
@Data
public class Member {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;

	@Column(nullable = false)
	private String name;
}

 

이런식으로 작성하면, sql문을 작성하지 않아도 테이블이 생성된다.

 

 

 

 

 

 


이외에도 볼 것들이 많고, 정리해야 할 부분이 많은데 우선은 실습에서 배운 위주로 구조를 정리해보았다.

이렇게 정리하지 않으면 헷갈릴 것 같아서....(내가)

 

추후 추가하거나 수정할 부분이 있으면 더 추가할 예정이다.

 

 

참고한 블로그

https://velog.io/@chang626/SpringBoot-JPA-%EB%94%94%EB%A0%89%ED%84%B0%EB%A6%AC-%EA%B5%AC%EC%A1%B0

 

SpringBoot, JPA 디렉터리 구조

https://gmlwjd9405.github.io/2018/12/25/difference-dao-dto-entity.html 를 참고하여 정리했습니다. 📝 시작하기전나 같은 경우 도메인형 디렉터리 구조를 사용한다. ✏️ 전체 구조 (package 구조)(1

velog.io

 

https://lealea.tistory.com/239

 

MyBatis와 JPA의 차이, JPA를 선택한 이유는

현재 면접 스터디를 진행 중에 있습니다.개인 프로젝트 관련해서 받았던 질문들 중 대답을 잘하지 못했던 부분들은 따로 정리하고자 합니다.    질문  이력서에 기재된 두 포트폴리오를 통

lealea.tistory.com

https://gmlwjd9405.github.io/2018/12/25/difference-dao-dto-entity.html

 

[DAO] DAO, DTO, Entity Class의 차이 - Heee's Development Blog

Step by step goes a long way.

gmlwjd9405.github.io

 

728x90