Contents
1. 유저 테이블 만들었음1.1 코드2. 보드(Board) 수정2.1 추가 해주자2.2 생성자도 만든 User추가해주자3. 프로퍼티로 이동 3.1 properties로 이동조회1. 더미 만들자(회원가입 로직 필요없음 지금은) data.sql파일로 이동번외@설명 한번 더ORM@해야할 것해결방법2. Board클래스로 Lazy전략Lazy 로 선택3. 테스트 해보면만약 테스트를 0번 4번을 찾는다면실제로 로직 어떻게 바꿔야 할까4. 보드 레파지토리로 이동5. join으로 시작 보드 레파지토리로 이동6. 테스트 진행 findById6. findById메서드 변경7. findAll 도 JPQL로 변경8. detail 머스테치 이동옛 것을 배워야 하는 이유아까 queryNativecreate 안 된 것 하려면하이버네이트를 배워보자
1. 유저 테이블 만들었음

1.1 코드
package shop.mtcoding.blog.user;
import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.sql.Timestamp;
@Setter
@Getter
@Table(name = "user_tb")
@NoArgsConstructor
@Entity
public class User {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Integer id;
//유니크 해야함
@Column(unique = true, nullable = false)
private String username; //아이디
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String email;
private Timestamp createdAt;
@Builder
public User(Integer id, String username, String password, String email, Timestamp createdAt) {
this.id = id;
this.username = username;
this.password = password;
this.email = email;
this.createdAt = createdAt;
}
}
- 유저, 보드가 있는데
1대n 관계다(유저 1명이 여러 보드 적을 수 있다) 앤포드(보드에 포린키가 있어야 한다)
2. 보드(Board) 수정
private Integer userId
이렇게 가능하지만 JPA는 다르게 사용한다
2.1 추가 해주자
//fk 없어서 어노테이션 걸어줘야 한다
@ManyToOne
private User user;
왼쪽 Many = board 오른쪽이 User
2.2 생성자도 만든 User추가해주자
package shop.mtcoding.blog2.board;
import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.lang.NonNull;
import shop.mtcoding.blog2.user.User;
import java.sql.Timestamp;
@NoArgsConstructor
@Table(name = "board_tb")
@Setter
@Getter
@Entity
public class Board {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Integer id;
@Column(nullable = false)
private String title;
@Column(nullable = false)
private String content;
@CreationTimestamp
private Timestamp createdAt;
@ManyToOne
private User user;
@Builder
public Board(Integer id, String title, String content, Timestamp createdAt, User user) {
this.id = id;
this.title = title;
this.content = content;
this.createdAt = createdAt;
this.user = user;
}
}

이렇게 만들어짐
3. 프로퍼티로 이동
3.1 properties로 이동
- 쿼리 이쁘게 만들어보자
spring.jpa.properties.hibernate.format_sql=true
이후 실행 테이블 이쁘게 보인다

자바 세상에서는 Object만들면 알아서 포린키로 들어간다는 것을 알자

조회
1. 더미 만들자(회원가입 로직 필요없음 지금은) data.sql파일로 이동
insert into user_tb(username, password, email, created_at)
values ('ssar', '1234', 'ssar@nate.com', now());
insert into user_tb(username, password, email, created_at)
values ('cos', '1234', 'cos@nate.com', now());
insert into user_tb(username, password, email, created_at)
values ('love', '1234', 'love@nate.com', now());
기존 더미 수정 포린키 넣어야 해서 넣기!
insert into board_tb(title, content, created_at, user_id)
values ('제목1', '내용1', now(),1);
insert into board_tb(title, content, created_at, user_id)
values ('제목2', '내용2', now(),1);
insert into board_tb(title, content, created_at, user_id)
values ('제목3', '내용3', now(),2);
insert into board_tb(title, content, created_at, user_id)
values ('제목4', '내용4', now(),2);
insert into board_tb(title, content, created_at, user_id)
values ('제목5', '내용5', now(),2);
쌀, 코스, 러브 회원가입하고 쌀은 글 2개 적고 코스 2개적고 러브는 0개 적음

- board_tb 테이블

- user_tb 테이블

궁금증 이 정보가 들어가면 이전에 만든 것이 잘 돌아갈까?
test페이지로 이동
- save 실행

실행은 되지만 user_id 추가 해야한다
- findAll 실행

private User user 만들었기에 포린키는 1이 나온다 → 보드 객체로 하이버 네이트가 만들어야하는데
쿼리문 실행 후 User user는 오브젝트
DB에서 조회된 1은 board객체에 넣을 수 없다 1은 Integer타입이지 board객체가 아니니까
DB에서는 조회하면

한 줄씩 해석한다
now보드 해서 빈 생성자 때린다
userId 일단 빼고 id, content, title넣었는데
userid넣으려고 하니까 Integer타입이 아니네?(User타입이다) 타입이 달라 못 넣겠네?
→ 이거를 유저 객체에 넣으려면 select한번 더 때림 (User 테이블에 1번 찾는다)
- findAll 한번 찾고
- User_id를 한번 더 찾는다
- 다른 줄을 찾을 때 userid를 이미 한번 찾았다면 다시 Select해서 찾지 않고 찾은 것 그대로 넣어준다(아마 userid로 찾은 것은 해쉬가 같을거다) (가까이 있는 거를 사용하는게 캐싱이라 한다)
- 고객이 정보를 받으면 하이버 네이트안에 있는 정보들 다 삭제된다
번외@
캐싱 설명설명 한번 더

select * from user_tb where id = 1
오브젝트 릴레이션 메핑 하려고 찾는거임

이 객체를 넣어준다
ORM@
ORM해야할 것
ORM하면 내가 의도하지 않은 필요 없는 쿼리 날려서 비지니스 맞게 돌려야 한다
- 예시
만약 100개를 찾으려고 하는데 유저 40명이 다 적었다면 userSelect 40번 한다. 아주 별로다.
해결방법
→ 조인쿼리 해야한다!
→ 게시글 목록보기에 화면에 타이틀, ID뿌림 애초에 userid를 찾을 필요가 없다 이거를 튜닝할 방법을 배울 것이다!
- 목록 페이지

2. Board클래스로 Lazy전략
board 클래스

- EAGER
즉시 로딩(앵간하면 사용하지 않는 것을 추천함) 그냥 다 가지고 오는 것
Lazy 로 선택

lazy전략임
나중에 할께
왜 not전략이 아닌가?
3. 테스트 해보면

지연(Lazy) 로딩 테스트
즉 지연 로딩을 하겠다는 말임
@Test
public void findAll_test() {
//given findall할때 필요한 매개변수를 적어준다
//when
System.out.println("1. 첫번째 조회");
List<Board> boardList = boardRepository.findAll();
System.out.println("userId : " + boardList.get(0).getUser().getId());
System.out.println("=============");
//eye
//싸이즈 부터 보자
System.out.println("2. 레이지 로딩");
System.out.println("title: " + boardList.get(0).getUser().getUsername());
}
결과

설명
- 시스아웃(1. 첫번째 조회) 뿌리고 findAll요청 하면 DB에서 5건 찾아서 하이버 네이트로 들고옴
- 그래서 board객체 5번 다다다 넣음, 이후 하나씩 옮겨 담음 → 전략이 lazy면 이러면 유저 조회(select)를 안 하고 유저아이디가 1이니까 new(빈 객체 생성)만 해서 아이디만 들고 있다
- ===============
- 시스아웃 (2. 레이지 로딩 )
- 보드 리스트 0번지에 게시글5번이 있음(반대로 있어서) 여기서 유저 객체.getUsername()하면 없기에 이때 select요청을 날린다
왜 유저 셀랙트를 한번만 했을까? 0번지만 레이지 했으니까
만약 0,1을 찾는다면 몇 번 조회할까?
0,1은 둘 다 userid가 2여서 1번만 조회한다
만약 테스트를 0번 4번을 찾는다면
@Test
public void findAll_test() {
//given findall할때 필요한 매개변수를 적어준다
//when
System.out.println("1. 첫번째 조회");
List<Board> boardList = boardRepository.findAll();
System.out.println("userId : " + boardList.get(0).getUser().getId());
System.out.println("=============");
//eye
//싸이즈 부터 보자
System.out.println("2. 레이지 로딩");
System.out.println("title: " + boardList.get(0).getUser().getUsername());
System.out.println("title: " + boardList.get(4).getUser().getUsername());
}
결과

실제로 로직 어떻게 바꿔야 할까
4. 보드 레파지토리로 이동
상세보기에 작성자 명이 나와야 한다

주제 선정 → 화면 설계를 해야 한다!

여기에는 userId없는데 select 2번날라가는데 통신 2번하면 IO 2번 일어난다 DB가 하드디스크다
왜 이거를 ORM해서 lazy를 넣으면 안될까? → 목록 보기에서는 userid가 필요가 없기 때문이다!
전략을 어떻게 하면 좋을까?
방법 2가지
- 레이지 로딩을 한다
- join한다
join으로 하는게 더 좋다 select2번 날리는 것 보다
5. join으로 시작 보드 레파지토리로 이동

이제 조인하자 게시글 적었는데 사용자 join할 때는 없을 수가 없어서 inner해야한다
bt는 별칭이다 버전3에서는 JPQL할거임
on 절이 있다 board_tb, user_tb의 userid가 필요하다 어떻게 합칠 지를 결정함
- 메모리상에 board_tb, user_tb 전부 퍼올림
- select * 모든거 다함
- where 행
원래는 userid로 받으면 DTO를 따로 만들어야 하는데 Board.class 해서 자동ORM해줘서 만들 필요가 없어진다!! 너무 좋아!
//상세보기할 때 사용
//resultset해서 하나씩 파싱해서 받아야 하는데 Board.class 하면 안해도 됨
public Board findById(int id) {
Query query = em.createNativeQuery("select * from board_tb bt inner join user_tb ut on bt.user_id = ut.id where bt.id = ?", Board.class);
query.setParameter(1, id);
//여러건이 아니니까 single, 빨간줄 뜨는 이유는 다운케스팅만 해주면 됨
try {
Board board = (Board) query.getSingleResult();
return board;
} catch (Exception e) {
//익셉션을 내가 잡은 것 까지 배운 - 처리방법은 v2에서 배우기
//터트리기는 해야 함 throw
throw new RuntimeException("게시글 id를 찾을 수 없습니다");
}
}
6. 테스트 진행 findById
오류 찾을 때 사용
printStackTrace();

6.2 오류

오류가 보드와 유저 보드 컬럼 명이 같아서 오류가 났다??
6. findById메서드 변경
하나 하나 다 바꿔주기 귀찮으니 일단
- 그냥 객체 지향 쿼리로 (createQuery) JPQL임
Board 이거 엔티티 객체이름으로 해줘야 함
아우터 조인하고 싶으면 아우터 안적고 left join fetch 사용한다
이너조인 → join fetch
b가 유저 객체 들고 있어서 b.user 사용
on 필요 없음
물음표 안하고 키 바인딩 사용함
query.setParameter(1, id); 대신에 query.setParameter(“id”, id)
public Board findById(int id) {
Query query = em.createQuery("select b from Board b join fetch b.user U where b.id =:id", Board.class);
query.setParameter("id", id);
//여러건이 아니니까 single, 빨간줄 뜨는 이유는 다운케스팅만 해주면 됨
try {
Board board = (Board) query.getSingleResult();
return board;
} catch (Exception e) {
e.printStackTrace();
//익셉션을 내가 잡은 것 까지 배운 - 처리방법은 v2에서 배우기
//터트리기는 해야 함 throw
throw new RuntimeException("게시글 id를 찾을 수 없습니다");
}
}
7. findAll 도 JPQL로 변경
public List<Board> findAll() {
Query query = em.createNativeQuery("select * from board_tb order by id desc", Board.class);
List<Board> boardList = query.getResultList();
return boardList;
}
JPQL 사용한 것
public List<Board> findAll() {
Query query = em.createQuery("select b from Board b order by b.id desc", Board.class);
List<Board> boardList = query.getResultList();
return boardList;
}
JPQL 장점
- 데이터 베이스 마이브레이션(기존 쓰던 sql을 변경할 때 다른 sql 사용하면 퀴리문 다 바꿔야 함) 할 때 편하다
오라클은 +해서 조인한다
mysql은 표준조인 사용한다
ms도 각자만의 join이 조금씩 다른 문법이 있따
하지만 JPQL사용하면 각자의 sql에 맞게 네이티브 쿼리가 변경돼서 날린다!! 전부 호환 가능
8. detail 머스테치 이동

8.2 결과

수정, 삭제는 로그인 후에 한다
되는 이유

board 객체로 이동

user테이블 가서

옛 것을 배워야 하는 이유
JSP에서는
${model.user.id ! = session.user.id} 이런 로직을 짠다
머스테치에서는 이런 로직 못 짠다
→ controller에서 정보를 만들어 와야 한다 DTO를 해야함
UserRepository클래스 만들기( 옛날 것 배워야 하는 이유 설명)
UserRepository 클래스 만들고 Test에 user폴더 만들고 UserReopsitoryTest 클래스 만들어서 테스트 시행
UserRepository 클래스
package shop.mtcoding.blog.user;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.sql.Timestamp;
@Repository
public class UserRepository {
@Autowired
private EntityManager em;
public User findById() {
Query query = em.createNativeQuery("select * from user_tb where id = 1");
//테이블 형태의 데이터고 1개다
//object배열로 받는다
Object[] obs = (Object[]) query.getSingleResult();
System.out.println(obs[0]);
System.out.println(obs[1]);
System.out.println(obs[2]);
System.out.println(obs[3]);
System.out.println(obs[4]);
User user = new User();
user.setId((Integer) obs[0]);
user.setCreatedAt((Timestamp) obs[1]);
user.setEmail((String) obs[2]);
user.setPassword((String) obs[3]);
user.setUsername((String) obs[4]);
return user;
}
}
한 행을 Object배열이라 한다
Collection이라면 list의 배열이라 함 이거를 바로 매핑해줌
직접 매핑하려면
User user = new User();
user.setId((Integer)obs[0]);
user.setCreatedAt((Timestamp) obs[1]) ;
이런 식으로
void를 User로 바꾸기
return User
테스트 클래스
package shop.mtcoding.blog.user;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.Import;
@DataJpaTest
@Import(UserRepository.class)
public class UserReopsitoryTest {
@Autowired
private UserRepository userRepository;
@Test
public void findById_test() {
User user = userRepository.findById();
System.out.println(user.getId());
System.out.println(user.getUsername());
System.out.println(user.getPassword());
}
}
- 하고싶은 이야기는
힘들게 직접 짜서 매핑할래 아니면 User.class 해서 RM하고 return(User) query.getSingleResult();로 할래?
JPQL이 편해서 ORM을 한다
아까 queryNativecreate 안 된 것 하려면
원래는 바로 매핑해줬는데 지금은 매핑을 한번에 할 수 없으니
정보 하나 하나 다 받아서 직접 매핑을 해줘야 한다
public Board findByIdV2(int id) {
Query query = em.createNativeQuery("select bt.id, bt.title, bt.content, bt.user_id, bt.created_at, ut.id u_id, ut.username, ut.password, ut.email, ut.created_at u_created_at from board_tb bt inner join user_tb ut on bt.user_id = ut.id where bt.id = ?");
query.setParameter(1, id);
Object[] obs = (Object[]) query.getSingleResult();
System.out.println(obs[0]);
System.out.println(obs[1]);
System.out.println(obs[2]);
System.out.println(obs[3]);
System.out.println(obs[4]);
System.out.println(obs[5]);
System.out.println(obs[6]);
System.out.println(obs[7]);
System.out.println(obs[8]);
System.out.println(obs[9]);
// 1
// 제목1
// 내용1
// 1
// 2024-08-21 12:49:35.197432
// 1
// ssar
// 1234
// ssar@nate.com
// 2024-08-21 12:49:35.194432
Board board = new Board();
User user = new User();
board.setId((Integer) obs[0]);
board.setTitle((String) obs[1]);
board.setContent((String) obs[2]);
board.setCreatedAt((Timestamp) obs[4]);
user.setId((Integer) obs[3]);
user.setUsername((String) obs[6]);
user.setPassword((String) obs[7]);
user.setEmail((String) obs[8]);
user.setCreatedAt((Timestamp) obs[9]);
board.setUser(user);
return board;
}
v2는 인증이다
- user테이블 만들고 board테이블에 연관관계로 걸었다(유저 객체로(하이버네이트))
- 유저 생겨서 Dummy만들어서 forminkey로 걸음 문제가 기존 findAll했을 때 board가 들고있는 유저의 id (포린키)가 있다. board조회하면 board+ user도 같이 찾아진다. 문제 (숫자는 매핑할 수 없다) 그래서 ORM함 레이지 로딩? 객체의 getter로
- 문제는 findById에서 (게시글 상세보기) 화면에 유저정보 필요함 땡겨올랬드만 레이지 로딩이여서 바로 못땡김 (getter로 레이지 로딩함 하지만 이렇게 하면 2번 호출됨) 해결 → 직접 join한다 보드로 매핑하면 단축어? 로 하면 이해 못한다
- 0번지에 들어가는 값은 랜덤으로 들어간다. 1이 유저 아이딘지 board아이딘지 모른다 직접 유저 객체 , 보드 객체 빈것 만들어서 쭉 집어 넣고 보드 객체 안에 유저객체 넣어줘야 한다 → ORM 직접 한거임(어려운게 아니고 귀찮은 것이다.) ORM안하면 플랫하게 받는다 플랫하게 받는다? → BoardAndUser 이딴 클래스 만들어서 보드의 맴버변수, 유저의 맴버변수 다 넣어서 db에서 조회한 것 넣어준다 즉 일자로 플랫하게 만들어 준다 이것 보다는 private User user이 더 좋지 않는가?
여기까지 함
이제 인증
Share article