본문 바로가기

프로그래밍/JPA

[Tutorial] JPA With Postgres and SpringBoot

* 해당 내용들은 백기선님의 JPA 강의를 정리하면서 작성되었습니다. 



1. Postgres 셋팅


# postgres docker 

docker run -d -p 5432:5432 --name postgres_boot -e POSTGRES_USER=kkwonsy -e POSTGRES_PASSWORD=pass postgres



# into bash

docker exec -i -t postgres_boot bash 

    -i : interactive

    -t : target



# access

# psql -U kkwonsy



# database 생성

postgres=# CREATE DATABASE springdata;



# 확인 

\list

\l



# database 선택

\c springdata;






2. ORM


# JDBC 대신 도메인 모델 사용하려는 이유?


객체 지향 프로그래밍의 장점을 활용하기 좋으니까.
각종 디자인 패턴
코드 재사용
비즈니스 로직 구현 및 테스트 편함.


# Definition

ORM은 애플리케이션의 클래스와 SQL 데이터베이스의 테이블 사이의 맵핑 정보를 기술한 메타데이터를 사용하여, 

자바 애플리케이션의 객체를 SQL 데이터베이스의 테이블에 자동으로 (또 깨끗하게) 영속화 해주는 기술



# Adventage


생산성

유지보수성

성능

밴더 독립성



# Disadventage

학습비용



# 학습이 어려운 이유


ORM: 패러다임 불일치


상속 구조

다형성 vs 관계를 표현할 방법이 없음


레퍼런스 동일성

인스턴스 동일성 vs primary key


근본적으로 ‘방향'이 존재 vs 외래키(foreign key)로 관계 표현(‘방향'이라는 의미가 없음)

다대다 표현 가능 여부


레퍼런스를 이용해서 다른 객체로 이동 가능 vs Join





3. Spring Boot 실습 1



<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>



@Entity
public class Account {

@Id
@GeneratedValue
private Long id;

private String username;

private String password;

public Long getId() {
return id;
}

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

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}
}

@Component
@Transactional
public class JpaRunner implements ApplicationRunner {

@PersistenceContext
EntityManager entityManager;

@Override
public void run(ApplicationArguments args) throws Exception {
Account account = new Account();
account.setUsername("kkwonsy");
account.setPassword("pass");

entityManager.persist(account);
}
}


spring.datasource.url=jdbc:postgresql://localhost:5432/springdata
spring.datasource.username=kkwonsy
spring.datasource.password=pass

# create: 매번 스키마 생성, 테스트용
# validate: 검증만 함, 운영용
spring.jpa.hibernate.ddl-auto=create

# warning 제거용
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true



# run 결과

springdata=# select * from account;

 id | password | username 

----+----------+----------

  1 | pass     | kkwonsy

(1 row)






@Override
public void run(ApplicationArguments args) throws Exception {
Account account = new Account();
account.setUsername("hibernate");
account.setPassword("passHibernate");

// JPA가 Hibernate도 사용하므로 그 API를 쓸 수 있음
Session session = entityManager.unwrap(Session.class);
session.save(account);
}

springdata=# select * from account;

 id |   password    | username  

----+---------------+-----------

  1 | passHibernate | hibernate

(1 row)





# 사라진 데이터를 남기고 싶으면 ddl-auto를 update로 바꾸면 된다.
  • update를 사용했을 때는 Entity를 바꾸게 되면 스키마가 지저분해 질 수 도 있으므로 테스트를 할때는 깔끔하게 create를 하는게 나을 것 같다.




4. 팁




# @Transient

컬럼으로 맵핑하고 싶지 않은 멤버 변수에 사용.



# sql을 이쁘게 콘솔로 보고싶으면 아래 속성을 추가하자 
application.properties
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true







5. Value 타


# 엔티티 타입은 테이블 대상 객체로 보면 되는 듯

# Value 타입은 그 안의 요소들


# Composite Value 타입은 객체로 되어 있는 그 안의 요소들이라고 보면 됨

  • 가령 Account 엔티티 타입안의 Address address 가 될 수 있음

@Embeddable
public class Address {

private String street;

private String city;

private String state;

private String zipCode;

}


# @Embeddable annotation을 통해 잘게 쪼개서 컨트롤 할 수가 있음

  • Address안의 street 정보만 가져와서 home_street Column으로 생성된

@Entity
public class Account {

@Id
@GeneratedValue
private Long id;

private String username;

private String password;

@Embedded
@AttributeOverrides({
@AttributeOverride(name = "street", column = @Column(name = "home_street"))
})
private Address address;






6. 1:N Mapping


@Entity
public class Study {

@Id
@GeneratedValue
private Long id;

private String name;

// Study를 만드는 사람은 여럿일 수 있다.
@ManyToOne
private Account owner;



Study study1 = new Study();
study1.setName("Spring Data JPA");
study1.setOwner(account);







Account 에서 Study를 관리하는 것으로 변경해보자. 

Study에서는 onwer를 삭제하자. 


그리고 Account에 Set을 하나 생성. 


@Entity
public class Account {

@Id
@GeneratedValue
private Long id;

private String username;

private String password;

@Embedded
@AttributeOverrides({
@AttributeOverride(name = "street", column = @Column(name = "home_street"))
})
private Address address;

@OneToMany
private Set<Study> studies = new HashSet<>();


@Override
public void run(ApplicationArguments args) throws Exception {
Account account = new Account();
account.setUsername("kkwonsy");
account.setPassword("pass");

Study study1 = new Study();
study1.setName("Spring Data JPA");

account.getStudies().add(study1);


// JPA가 Hibernate도 사용하므로 그 API를 쓸 수 있음
Session session = entityManager.unwrap(Session.class);
session.save(account);
session.save(study1);
}

아래처럼 새로운 테이블이 하나 생성된다. 


Hibernate: 

    

    create table account_studies (

       account_id int8 not null,

        studies_id int8 not null,

        primary key (account_id, studies_id)

    )




springdata=# select * from account_studies;

 account_id | studies_id 

------------+------------

          1 |          2

(1 row)








양방향 관계는 어떻게 할까?


@Entity
public class Study {

@ManyToOne
private Account owner;



@Entity
public class Account {

@OneToMany(mappedBy = "owner")
private Set<Study> studies = new HashSet<>();


위와 같이 mappedBy를 이용해서 어떤 녀석인지를 지정해줘야 한다. 

기본적인 매핑 방법이다. 

이때, 관계의 주인은 Study가 된다. 


그러므로 아래처럼 주인이 아닌 account로 작성을하게 되면 테이블에 실제 데이터가 들어가지 않는다. 주의해야 한다. 



account.getStudies().add(study1);

springdata=# SELECT * FROM study; id | name | owner_id ----+-----------------+---------- 2 | Spring Data JPA | (1 row)


즉, study에서 owner 설정은 필수이다. 

(account로 설정하는건 안해도 된다. 다만 객체지향적으로 생각하면 필요하므로 작성해주는게 좋다.)

account.getStudies().add(study1);
study1.setOwner(account);


// 또는 이런 방법으로 메서드를 뺴주는 것도 좋을 듯.
public void addStudy(Study study1) {
this.getStudies().add(study1);
study1.setOwner(this);
}









JPA는 방대한 학습 내용이 존재하는데, 다음 포스팅으로 정리를 해야할 것 같다.










'프로그래밍 > JPA' 카테고리의 다른 글

N+1 (블로그글 요약함)  (0) 2020.05.22
SpringBoot JPA (feat. h2 db)  (0) 2019.07.03