[JPA] Hibernate 기초

merci·2023년 3월 16일
0

JPA

목록 보기
1/5
post-thumbnail

Hibernate

  • 자바 기반의 ORM(Object-Relational Mapping) 프레임워크
  • ORM이란 오브젝트와 관계형 데이터베이스(RDB)간의 데이터를 변환하는 기술이다.
  • JPA는 DB와 자바 객체를 매핑하기 위한 인터페이스, Hibernate는 이를 구현한 라이브러리다
  • 즉 Hibernate는 JPA 표준 구현체로 볼 수 있다.
  • Hibernate를 사용하면 자바 객체를 데이터베이스에 저장하고, 조회, 수정, 삭제할 수 있다.
  • DB에 의존하지 않아서 DB의 종류에 상관없이 동일한 사용법을 가진다.

JPA

  • Java 언어를 위한 ORM 기술 표준
  • JPA는 ORM의 기능을 추상화하여 일관된 방식으로 RDB와의 상호작용을 지원한다.
  • 일관성에 의해 DB 종류에 상관없이 동일한 사용법을 가진다.
  • 개발자는 SQL 쿼리를 직접 작성하지 않고도 객체를 이용하여 데이터를 다룰 수 있으며, 객체 간의 관계를 매핑하여 복잡한 데이터 모델을 처리할 수 있다.


JPA에 사용될 Entity 만들기

모델(엔티티)은 DB의 데이터를 그대로 받아서 데이터를 넣을 모델링에 사용된다.
개발자는 setter를 이용해서 데이터를 오브젝트에 넣지만 JPA의 ORM은 기본생성자를 이용한다.
이때 JPA의 ORM은 리플렉션을 사용해 엔티티 클래스를 찾는다.
예를들어 @Entity 클래스를 엔티티로 인식하고, @Id 필드를 기본키로 인식한다.


로그인에 사용할 유저 엔티티를 만들어 보면

package shop.mtcoding.hiberapp.model;

import java.sql.Timestamp;

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

import org.hibernate.annotations.CreationTimestamp;

import com.fasterxml.jackson.annotation.JsonIgnore;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@NoArgsConstructor
@Getter
@Table(name = "user_tb")
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 생성 전략
    private Long id;
    private String username;
    private String password;
    private String email;
    private Timestamp createdAt;

    // @Setter 를 만들지 않는다 필요한 값만 넣어줄 생성자만 있으면 된다.
    public User(String username, String password, String email){ 
        this.username = username;
        this.password = password;
        this.email = email;
    }

    // 필요한 데이터만 setter 로 바꿔준다.
    public void update(String password, String email){ 
        this.password = password;
        this.email = email;
    }



필요한 데이터만 생성자를 넣어서 만들면 되지만 빌더 패턴을 이용하면 필요한 데이터만 넣을 수 있어서 사용하기 더 좋아진다.

    // 빌더 패턴을 이용한 생성자
    @Builder
    public User(Long id, String username, String password, String email, Timestamp createdAt) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.email = email;
        this.createdAt = createdAt;
    }

우리가 넣고 싶은 데이터는 username, password, email 이지만 혹여나 몇개의 데이터만 넣게 된다면 다음과 같이 넣으면 된다.

	User user = User.builder()
    			.username("ssar")
                .password("1234")
                .build();



yml 설정을 하면 서버 실행시 콘솔에 생성되는 테이블을 확인할 수 있다.

  jpa:
    hibernate:
      ddl-auto: create
    show-sql: true
    properties:
      hibernate:
        '[format_sql]': true 


EntityManager를 이용한 Repository

@RequiredArgsConstructor
@Repository
public class UserRepository {
    private final EntityManager em;

    // 저장후 리턴이 필요할 경우 void -> User // 물론 DTO를 만들어서 리턴하면 더 좋다.
    @Transactional
    public User save(User user){
        em.persist(user);
        return user;
    }
    
    // jpa 의  update는 pk를 주지 않으면 insert를 해버린다 !!!!! 주의
    @Transactional
    public User update(User user){  
        return em.merge(user);
    }
    
    @Transactional
    public void delete(User user){
        em.remove(user);
    }
    
    public User findById(Long id){
        return em.find(User.class, id);
    }
    
    public List<User> findAll(){
        return em.createQuery("select u from User u", User.class)
                    .getResultList();
    }
    
    // 이렇게 사용시 자동적으로 페이징이 된다. 처음은 0페이지 , 5개씩 조회
    public List<User> findAll(int page){
        return em.createQuery("select u from User u", User.class)
                    .setFirstResult(page * 5)
                    .setMaxResults(5)
                    .getResultList();
    }
    
    // 오버로딩해서 한번에 볼 데이터 조절
    public List<User> findAll(int page, int row){
        return em.createQuery("select u from User u", User.class)
                    .setFirstResult(page * row)
                    .setMaxResults(row)
                    .getResultList();
    }
}

EntityManager가 제공하는 여러 메소드를 이용해서 CRUD를 수행할 수 있다.
조회가 아닐때는 @Transactional를 붙여서 트랜잭션 관리를 해야한다.

EntityManager란 ?

  • EntityManager는 JPA의 핵심 개념이다.
  • EntityManager는 JPA의 핵심 인터페이스 중 하나로서, 엔티티의 영속성 컨텍스트를 관리한다.
    영속성 컨텍스트란, JPA에서 엔티티 객체가 관리되는 환경을 말한다.
  • EntityManager는 영속성 컨텍스트에서 CRUD작업을 수행한다.
  • EntityManager를 통해 엔티티 객체를 영속화하면, 해당 객체는 데이터베이스에 저장되고, EntityManager를 통해 조회하거나 수정할 수 있다.
  • EntityManager는 EntityManagerFactory에서 생성된다.
  • EntityManagerFactory는 데이터베이스 연결을 관리, EntityManager를 생성하는 팩토리 역할을 한다.
  • EntityManager는 일반적으로 애플리케이션의 라이프사이클 동안 재사용된다.

Controller

@RequiredArgsConstructor
@RestController
@RequestMapping("/api")
public class UserApiController {

    private final UserRepository userRepository;

    @PostMapping("/users")
    public ResponseEntity<?> addUser(User user){
        User userPS = userRepository.save(user);
        return new ResponseEntity<>(userPS, HttpStatus.CREATED);
    }

    @PutMapping("/users/{id}")
    public ResponseEntity<?> updateUser(@PathVariable Long id, User user){
        User userPS = userRepository.findById(id);
        if(ObjectUtils.isEmpty(userPS)){
            return new ResponseEntity<>("해당 유저가 없습니다.", HttpStatus.BAD_REQUEST);
        }
        userPS.update(user.getPassword(), user.getEmail());
        User updateUserPS = userRepository.update(userPS);
        return new ResponseEntity<>(updateUserPS, HttpStatus.OK);
    }

    @DeleteMapping("/users/{id}")
    public ResponseEntity<?> deleteUser(@PathVariable Long id){
        User userPS = userRepository.findById(id);
        if(ObjectUtils.isEmpty(userPS)){
            return new ResponseEntity<>("해당 유저가 없습니다.", HttpStatus.BAD_REQUEST);
        }
        userRepository.delete(userPS);
        return new ResponseEntity<>(HttpStatus.OK);
    }

    @GetMapping("/users")
    public ResponseEntity<?> findUsers(@RequestParam(defaultValue = "0") int page){
        List<User> userList = userRepository.findAll(page, 2);
        return new ResponseEntity<>(userList, HttpStatus.OK);
    }

    @GetMapping("/users/{id}")
    public ResponseEntity<?> findUserOne(@PathVariable Long id){
        User userPS = userRepository.findById(id);
        if(ObjectUtils.isEmpty(userPS)){
            return new ResponseEntity<>("해당 유저가 없습니다.", HttpStatus.BAD_REQUEST);
        }
        return new ResponseEntity<>(userPS, HttpStatus.OK);
    }
}

postman을 이용한 테스트를 해보면

포스트맨을 날리기 전에 사용자의 비밀번호를 보이지 않게 설정하자

    @JsonIgnore // 설정하면 비밀번호는 리턴되지 않는다.
    private String password;
    
    @CreationTimestamp // 간단하게 시간 나오게 설정
    private Timestamp createdAt;

다음과 같이 요청

get 요청

0번째 페이지에 2명씩 보기로 설정했으므로 2명의 데이터가 조회된다.

    @GetMapping("/users")
    public ResponseEntity<?> findUsers(@RequestParam(defaultValue = "0") int page){
        List<User> userList = userRepository.findAll(page, 2);
        return new ResponseEntity<>(userList, HttpStatus.OK);
    }
[
    {
        "id": 1,
        "username": "ssar1111",
        "email": "ssar@nate.com",
        "createdAt": "2023-03-16T13:21:04.704+00:00"
    },
    {
        "id": 2,
        "username": "ssar2222",
        "email": "ssar@nate.com",
        "createdAt": "2023-03-16T13:21:08.174+00:00"
    }
]


JpaRepository

JpaRepositorySpring Data JPA의 일부이고 Spring Data JPA는 Java Persistence API ( JPA )를 기반으로하기 때문에 JpaRepository는 내부적으로 EntityManager를 사용한다.

JpaRepository는 다음과 같은 인터페이스를 상속하고 CRUD 에 사용될 메소드를 가지고 있다.

상속한 레파지토리를 만들고 컨트롤러에 의존성을 주입한다.
아래의 레파지토리는 User객체를 영속화 하므로 <User, Long>으로 만든다. ( Long -> id )

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

public interface UserJpaRepository extends JpaRepository<User, Long> {
    
}



의존성을 바꾸면 컨트롤러에 오류가 발생하는데 임시방편으로 다음과 같은 방법으로 오류를 없앤다.

private final UserJpaRepository userRepository;
User userPS = userRepository.findById(id).get();

get()java.util.Optional의 메소드로서 값이 존재할경우 리턴해준다.

    public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }



여기서 중요한 포인트는 JpaRepository를 의존했을때는 save() 메소드를 사용하게 되는데 이 save()메소드는 post 와 put 요청에 동시에 사용된다.
즉, save()메소드가 호출될때 전달된 오브젝트의 id가 null 이라면 insert를 수행하고 null이 아니라면 update를 수행하게 된다.

User userPS = userRepository.save(user);


이제 포스트맨을 이용해서 테스트해본다면


기본 페이징 설정을 다음과 같이 했으므로

    @GetMapping("/users")
    public ResponseEntity<?> findUsers(@RequestParam(defaultValue = "0") int page){
        Page<User> userList = userRepository.findAll(PageRequest.of(page, 2));
        return new ResponseEntity<>(userList, HttpStatus.OK);
    }

아래의 리턴결과가 날라온다.

{
    "content": [
        {
            "id": 1,
            "username": "ssar1",
            "email": "ssar@nate.com",
            "createdAt": "2023-03-16T13:41:05.003+00:00"
        },
        {
            "id": 2,
            "username": "ssar2",
            "email": "ssar@nate.com",
            "createdAt": "2023-03-16T13:41:08.279+00:00"
        }
    ],
    "pageable": {
        "sort": {
            "sorted": false,
            "unsorted": true,
            "empty": true
        },
        "pageNumber": 0,
        "pageSize": 2,
        "offset": 0,
        "paged": true,
        "unpaged": false
    },
    "totalPages": 3,
    "totalElements": 6,
    "last": false,
    "sort": {
        "sorted": false,
        "unsorted": true,
        "empty": true
    },
    "numberOfElements": 2,
    "size": 2,
    "number": 0,
    "first": true,
    "empty": false
}

리턴 결과를 보면 페이징에 사용되는 여러 변수들이 보이는데 JPA를 이용하면 페이징처리를 보다 쉽게 할 수 있다.

localhost:8080/api/users?page=2 로 요청을 해보면 "last": true,를 응답받을 수 있는데 마지막 페이지를 의미한다.
총 페이지 수가 "pageSize": 2, 이므로 localhost:8080/api/users?page=3 을 요청하면 "content": [],을 응답받는데 아무런 데이터가 담겨오지 않는걸 볼 수 있다.

profile
작은것부터

0개의 댓글