개발 메모장

[JPA] JPA의 정의와 사용 예제 본문

ORM

[JPA] JPA의 정의와 사용 예제

yyyyMMdd 2024. 1. 10. 17:35
728x90

#. JPA 란 무엇인가?


- ORM(Object-Relational Mapping) 기술 표준으로 사용되는 인터페이스의 모음으로 실제 구현된 것이 아니라 구현된 클래스와 매핑을 해주기 위해 사용되는 프레임워크입니다.


- JPA를 구현한 대표적인 오픈소스로는 Hibernate가 있습니다.

 

- 최초엔 javax로 제공되었지만 Eclipse Foundation으로 이전되면서 jakarta로 변경되었기에 추후 QueryDSL 등 jpa 관련된 Dependency를 추가할 때 확인해야 합니다.

 


#. JPA의 장점

 

- 반복적인 CRUD SQL을 처리해 줍니다.

- 개발자가 SQL에 대한 생각을 최소화하여 다른 개발에 집중할 수 있게 합니다.

- JPA로 구현하기 어려운 SQL의 경우 직접 작성할 수 있도록 네이티브 SQL을 지원합니다.

(@Query를 이용해 쿼리를 작성할 수 있습니다.)

@Query("SELECT u FROM User u WHERE u.age > :age")
    List<User> findByAgeGreaterThan(int age);

 


#. JPA의 단점

 

- 디버깅이 어렵고 JPA를 이용한 개발이 복잡하게 느낄 수 있습니다.

 

- DBMS별 구현의 차이가 있을 수 있기에 이식성 문제가 발생할 수 있습니다.

 

- 객체지향 모델과 RDB 간 추상화 계층으로 인한 성능 오버헤드가 발생할 수 있기에 서비스의 처리량 및 성능에 따라 큰 차이를 발생시킬 수 있습니다.

 


#. Application.yml 설정

 

  • JPA 사용을 원활히 하기 위해 가상 DB인 H2, 쿼리 내용을 console에 출력해 주는 설정을 해주도록 하겠습니다.
spring:
  # H2 세팅하기
  h2:
    console:
      enabled: true 
      path: /h2-console
      
  # DB 세팅하기
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:~/test
    username: sa
    password:
    
  # JPA 세팅
  jpa:
    hibernate:
      ddl-auto: update  # option type: create, create-drop, update, validate, none
    properties:
      hibernate:
        dialect: org.hibernate.dialect.H2Dialect  # DB 엔진 설정
        show_sql: true  # sql 쿼리를 보여줌
        format_sql: true
        use_sql_comments: true  # 쿼리의 추가정보

# JPA 사용 시 파라미터 확인을 위한 세팅
logging:
  level:
    org:
      hibernate:
        orm:
          jdbc:
            bind: trace
        type: trace
        SQL : debug

 

 


#. pom.xml 설정

 

  • 프로젝트 생성 시 추가한 의존성으로 중요한 것은 JPA와 DB로 사용할 H2라고 보면 되겠습니다.
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <scope>provided</scope>
</dependency>

 


#. Entity 생성

 

  • @NoArgsConstructor을 이용해 기본 생성자를 생성할 수 있으며, 아래와 같이 직접 생성도 가능합니다.

  • @JsonIgnoreProperties는 Jackson 라이브러리에서 제공하며 JSON 직렬화 및 역직렬화 시 무시할 속성 지정을 할 때 사용합니다. 인자로 있는 것은 Hibernate나 JPA를 사용하는 경우 엔티티 객체를 JSON으로 변환할 때 불필요한 루프를 방지하기 위해 사용합니다.

  • hibernateLazyInitializer는 Hibernate의 프록시 객체가 초기화되는 데 사용되는 속성이며, handler는 자바 Proxy Handler 객체입니다.

  • 롬복의 @builder를 사용해 입력받은 데이터를 처리할 수 있으며, 어노테이션이 안될 경우 아래와 같이 builder를 직접 만들어 사용할 수 있습니다.

  • 롬복의 @getter, @setter로 처리도 가능하나 getter, setter도 만들었습니다.

  • getter, setter는 Alt + Shift + s를 눌러 중단부 generate getters and setters로 생성합니다.
@NoArgsConstructor
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
@Entity
@Table(name = "MEMBER")
public class MemberEntity {
	
    // 기본 생성자
    MemberEntity() {
    }

    // 빌더
    public static MemberEntity builder() {
        return new MemberEntity();
    }
	
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID")
    private Long id;
	
    @Column(name = "NAME")
    private String name;
	
    @Column(name = "email")
    private String email;
	
    @Column(name = "NICKNAME")
    private String nickname;
	
    @Column(name = "AGE")
    private int age;
	
    @Column(name = "BIRTHDAY")
    private Date birthday;
	
    public Long getId() {
    	return id;
    }

    public void setId(Long id) {
    	this.id = id;
    }

    public String getName() {
    	return name;
    }

    public void setName(String name) {
    	this.name = name;
    }

    public String getEmail() {
    	return email;
    }

    public void setEmail(String email) {
    	this.email = email;
    }

    public String getNickname() {
    	return nickname;
    }

    public void setNickname(String nickname) {
    	this.nickname = nickname;
    }

    public int getAge() {
    	return age;
    }

    public void setAge(int age) {
    	this.age = age;
    }

    public Date getBirthday() {
    	return birthday;
    }

    public void setBirthday(Date birthday) {
    	this.birthday = birthday;
    }

	
    public void MemberEntityBuilder() {
    	// 기본 생성자
    }

    public MemberEntity id(Long id) {
    	this.id = id;
    	return this;
    }

    public MemberEntity name(String name) {
    	this.name = name;
    	return this;
    }

    public MemberEntity email(String email) {
    	this.email = email;
    	return this;
    }

    public MemberEntity nickname(String nickname) {
    	this.nickname = nickname;
    	return this;
    }

    public MemberEntity age(int age) {
    	this.age = age;
    	return this;
    }

    public MemberEntity birthday(Date birthday) {
    	this.birthday = birthday;
    	return this;
    }

    public MemberEntity build() {
    	MemberEntity member = new MemberEntity();
    	member.setId(this.id);
    	member.setName(this.name);
    	member.setEmail(this.email);
    	member.setNickname(this.nickname);
    	member.setAge(this.age);
    	member.setBirthday(this.birthday);
    	return member;
    }

    @Override
    public String toString() {
    	return "MemberEntity [id=" + id + ", name=" + name + ", email=" + email + ", nickname=" + nickname + ", age="
				+ age + ", birthday=" + birthday + "]";
    }
}

 


#. Repository 생성

 

  • @Repository를 추가해 줍니다.

  • extends로 JpaRepository를 추가해 jpa를 사용할 수 있게 합니다.
@Repository
public interface MemberRepository extends JpaRepository<MemberEntity, Long> {
}

 


#. Service 생성

  • @RequiredArgsConstructor를 추가하여 final 필드를 가진 생성자를 생성하고 필드를 초기화하는 생성자를 컴파일 시점에 생성해 줍니다.
  • 총 5가지의 기능(데이터 생성, 데이터 수정, 전체 조회, id로 조회, 삭제)을 구현합니다.
@Service
@RequiredArgsConstructor
public class MemberService {

	@Autowired
	private final MemberRepository memberRepository = null;
	
	// member 생성
	public MemberEntity createMember(MemberEntity memberEntity) {
		MemberEntity saveMember = memberRepository.save(memberEntity);
		return null;
	}
	
	// member 수정
	public MemberEntity updateMember(MemberEntity memberEntity) {
        MemberEntity updatedMember = null;
        try {
            // member id로 유무 확인
            MemberEntity existMember = getMember(memberEntity.getId());
            if (!ObjectUtils.isEmpty(existMember)) {
                updatedMember = memberRepository.save(memberEntity);
            }
        } catch (Exception e) {
        	System.err.println(e.toString());
        } finally {
            return updatedMember;
        }
    }

    // member 전체 조회
    public List<MemberEntity> getMembers() {
        return memberRepository.findAll();  
    }

    // member id로 조회
    public MemberEntity getMember(Long id) {
        return memberRepository.getReferenceById(id); 
    }

    // member id로 삭제
    public void deleteMember(Long id) {
        memberRepository.deleteById(id);
    }
}

 


#. Controller 생성

 

  • MemberEntity에서 만들어둔 builder를 이용해 입력받은 파라미터를 처리합니다.
@RestController
@RequiredArgsConstructor
public class MemberController {

    @Autowired
    private final MemberService memberService = new MemberService();
	
    @PostMapping("/create")
    public ResponseEntity<MemberEntity> create(String name, String email, String nickname, int age, String birthday) throws ParseException {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        Date date = format.parse(birthday);
        MemberEntity member = MemberEntity.builder()
                .name(name)
                .email(email)
                .nickname(nickname)
                .age(age)
                .birthday(date)
                .build();
        MemberEntity savedMember = memberService.createMember(member);
        return new ResponseEntity<>(savedMember, HttpStatus.OK);
    }

    @PutMapping("/update")
    public ResponseEntity<MemberEntity> update() throws ParseException {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        Date date = format.parse("1999-08-02");
        MemberEntity member = MemberEntity.builder()
                .id(3l)
                .name("김홀란")
                .email("holransloveda@good.com")
                .nickname("king scorer")
                .age(23)
                .birthday(date)
                .build();
        MemberEntity updatedMember = memberService.updateMember(member);
        if (!ObjectUtils.isEmpty(updatedMember)) {
            return new ResponseEntity<>(updatedMember, HttpStatus.OK);
        } else {
            return new ResponseEntity<>(member, HttpStatus.NOT_FOUND);
        }
    }

    @GetMapping("/list")
    public ResponseEntity<List<MemberEntity>> getAll() {
        List<MemberEntity> members = memberService.getMembers();
        return new ResponseEntity<>(members, HttpStatus.OK);
    }

    @GetMapping("/{id}")
    public ResponseEntity<MemberEntity> get(@PathVariable("id") Long id) {
        MemberEntity member = memberService.getMember(id);
        return new ResponseEntity<>(member, HttpStatus.OK);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Long> delete(@PathVariable("id") Long id) {
        memberService.deleteMember(id);
        return new ResponseEntity<>(id, HttpStatus.OK);
    }

 


#. 데이터 생성하기

 

  • create 메서드의 경우 post로 받기 때문에 postman을 이용해 처리해 보도록 하겠습니다.


  • 실질적으로 jpa가 처리한 sql와 받은 파라미터에 대해 console을 확인해 볼 수 있습니다.


  • H2 Console에 접속하여 확인해 보면 아래와 같이 조회가 가능합니다.

  • H2 Console은 localhost:포트에 application.yml > h2 > console > path에 지정한 /h2-console 붙여 사용하면 됩니다.

 


#. 위와 같은 방법으로 URI만 변경하여 처리해 보면 예상하는 결과를 가져올 수 있습니다.

 

#. JPA에 대한 기본을 간략하게 알아보았습니다.

 

#. 처음 또는 오랜만에 접하시는 분들께 도움이 되었으면 좋겠습니다.

 

 

 

 

===========================================================
틀린 내용이 있거나 이견 있으시면 언제든 가감 없이 말씀 부탁드립니다!
===========================================================

728x90

'ORM' 카테고리의 다른 글

[QueryDSL] QueryDSL Projection  (0) 2024.02.01
[QueryDSL] QueryDSL 사용방법(2)  (1) 2024.01.31
[QueryDSL] QueryDSL 사용방법(1)  (1) 2024.01.30
[QueryDSL] QueryDSL 정의 및 설정방법  (0) 2024.01.08
[myBatis] myBatis 사용방법  (1) 2023.12.26