updateUser 메소드를 아래와 같이 생성합니다.

// save 함수는 id를 전달하지 않으면 insert를 해주고
// save 함수는 id를 전달하면 해당 id에 대한 데이터가 있으면 update를 해주고
// save 함수는 id를 전달하면 해당 id에 대한 데이터가 없으면 insert를 해요.
// email, password
@PutMapping("/dummy/user/{id}")
public User updateUser(@PathVariable int id, @RequestBody User requestUser) { // json데이터를 요청 => Java Object(MessageConvertor의 Jackson라이브러리가 변환해서 받아줘요.)
    System.out.println("id : " + id);
    System.out.println("password : " + requestUser.getPassword());
    System.out.println("email : " + requestUser.getEmail());

    User user = userRepository.findById(id).orElseThrow(()->{
        return new IllegalArgumentException("수정에 실패하였습니다.");
    });
    user.setPassword(requestUser.getPassword());
    user.setEmail(requestUser.getEmail());

    userRepository.save(user);
    return null;
}

RequestBody 어노테이션은 json데이터를 요청한것에 대하여 Java Object로 변환하여 받아줍니다.

이때 MessageConverter의 Jackson 라이브러리가 이 역활을 합니다.

 

save 메소드는 update도 되고 insert도 됩니다.

save 메소드를 이용한 update시 아래와 같이 먼저 해당 id에 따른 정보값을 찾은 후 그 정보값으로 다시 update를 해줍니다.

 

 

@Transactional
@PutMapping("/dummy/user/{id}")
public User updateUser(@PathVariable int id, @RequestBody User requestUser) { // json데이터를 요청 => Java Object(MessageConvertor의 Jackson라이브러리가 변환해서 받아줘요.)
    System.out.println("id : " + id);
    System.out.println("password : " + requestUser.getPassword());
    System.out.println("email : " + requestUser.getEmail());

    User user = userRepository.findById(id).orElseThrow(()->{
        return new IllegalArgumentException("수정에 실패하였습니다.");
    });
    user.setPassword(requestUser.getPassword());
    user.setEmail(requestUser.getEmail());


    // 더티 체킹
    return null;
}

Transactional 어노테이션을 붙이고 조회된 user에 수정하고자 하는 정보들을 지정하면, save메소드와 같은 역할을 진행합니다.

이걸 더티체킹이라 합니다.(자세한거든 다음 강의에서...)

 

 

 

강의 주소 : https://youtu.be/oijoJtiGPhI

모든 유저를 조회합니다.

@GetMapping("/dummy/users")
public List<User> list() {
    return userRepository.findAll();
}

 

페이징을 이용한 조회입니다.

// 한 페이지당 2건의 데이터를 리턴받아 볼 예정
@GetMapping("/dummy/user/page")
//public Page<User> pageList(@PageableDefault(size=2, sort = "id", direction = Sort.Direction.DESC) Pageable pageable) {
public List<User> pageList(@PageableDefault(size=2, sort = "id", direction = Sort.Direction.DESC) Pageable pageable) {
    Page<User> pagingUser = userRepository.findAll(pageable);
    List<User> users = pagingUser.getContent();

    return users;
}

PageableDefault 어노테이션을 이용해 한 페이지당 표시되는 수는 2개고, 정렬은 id이며 방향은 역방향으로 한다는 의미입니다.

선언대 Pageaable 객체를 이용하여 페이징을 포함하여 조회시 아래와 같이 결과가 나옵니다.

page에 대한 부분은 queryString으로 조절 가능합니다.

 

 

 

강의 주소 : https://youtu.be/dPfjqBB-T4U

DummyControllerTest 내에 id 조회용 메서드를 생성합니다.

// http://localhost:8000/blog/dummy/user/5
// {id} 주소로 파라메터를 전달 받을 수 있음.
@GetMapping("/dummy/user/{id}")
public User detail(@PathVariable int id) {
    // user/4을 찾으면 내가 데이터베이스에서 못찾아오게 되면 user가 null이 될 것 아냐?
    // 그럼 return null이 리턴이 되자나... 그럼 프로그램에 문제가 있지 않겠니?
    // Optional로 너의 User객체를 감싸서 가져올테니 null인지 아닌지 판단해서 return해!!!

    User user = userRepository.findById(id).orElseThrow(new Supplier<IllegalArgumentException>() {
        @Override
        public IllegalArgumentException get() {
            // TODO Auto-generated method stub
            return new IllegalArgumentException("해당 유저는 없습니다. id : " + id);
        }
    });

    return user;
}

이때 {id}는 주소로 파라메터를 전달 받을 수 있는 규칙입니다.

(단, 메서드 파라메터 부분에 @PathVariable을 추가해줘야 합니다.)

 

JPA를 통해 userRepository.findById 이용시 반환 타입은 Optional입니다.

필자는 User 객체로 반환하기 위해 findById 이후 orElseThrow를 사용합니다.

이때, findById의 상세내용을 봐보면 아래와 같습니다.

/**
 * Retrieves an entity by its id.
 *
 * @param id must not be {@literal null}.
 * @return the entity with the given id or {@literal Optional#empty()} if none found.
 * @throws IllegalArgumentException if {@literal id} is {@literal null}.
 */
Optional<T> findById(ID id);

만약 해당 값이 없을 경우에 대해서는 IllegalArgumentException으로 예외처리하기를 권장합니다.

 

먼저 orElseThrow를 사용하기에 앞서 orElseGet()을 통해 해당 메서드 내에서 Supplier객체를 선언하여 빈 객체를 던지도록 작성 할 수 있습니다.

User user = userRepository.findById(id).orElseGet(new Supplier<User>() {
    @Override
    public User get() {
        return new User();
    }
});

그러나 앞서 말씀드린것처럼 예외처리하는 방향을 선호하기에 orElseThrow를 통해 코드 작성을 마무리 짓습니다.

 

잘못된 id 값을 집어 넣었을 경우 아래와 같은 결과로 표출됩니다.

 

번외로 위의 코드와 같이 어떤 객체를 리턴하며, 어떤 exception을 넣어야하는지에 대한 불편함을 해소하는 방법으로 람다식을 이용 할 수 있습니다.

// 람다식
User user = userRepository.findById(id).orElseThrow(() -> {
    return new IllegalArgumentException("해당 사용자는 없습니다.");
});

 

작성 된 코드를 보게되면 return 타입이 User 객체임을 알 수 있습니다.

그러나 웹브라우저를 통한 요청에서는 해당 User 객체인 자바 오브젝트를 인식 할 수 없습니다.

기존의 스프링에서는 이를 위해서 json으로 변환을 하고자 Gson 라이브러리를 이용하였으나,

스프링 부트에서는 MessageConverter가 Jackson 라이브러리를 호출하여 자동적으로 변환하여 응답해줍니다.

 

 

 

 

 

강의 주소 : https://youtu.be/z_yxfFUX1xI

- 연관관계 만들기
@ManyToOne

@OneToMany

@OneToOne

@ManyToMany

(강사님이 ManyToMany는 잘 사용하지 않는다고 하셨습니다.)

자세한 내용은 아래 주소에서 확인하시면 됩니다.

https://getinthere.tistory.com/23?category=884180 

 

스프링부트 with JPA 블로그 8강 - 테이블 생성하기

1. Blog 테이블 만들기 (User, Board, Reply) 2. 연관관계 만들기 @ManyToOne @OneToMany @OneToOne @ManyToMany ManyToMany는 사용하지 않는다. 그 이유는 서로의 primary key로만 중간 테이블을 생성해주는데,..

getinthere.tistory.com

 

 

package com.cos.blog.test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import com.cos.blog.model.RoleType;
import com.cos.blog.model.User;
import com.cos.blog.repository.UserRepository;

@RestController
public class DummyControllerTest {
	
	@Autowired // 의존성 주입(DI)
	private UserRepository userRepository;

	// http://localhost:8000/blog/dummy/join (요청)
	// http의 body에 username, password, email 데이터를 가지고 (요청)
	@PostMapping("/dummy/join")
//	public String join(String username, String password, String email) { // key=value (약속된 규칙)
	public String join(User user) { // key=value (약속된 규칙)
//		System.out.println("username: " + username);
//		System.out.println("password: " + password);
//		System.out.println("email: " + email);
		System.out.println("user: " + user);
		System.out.println("username: " + user.getUsername());
		System.out.println("password: " + user.getPassword());
		System.out.println("email: " + user.getEmail());
		
		user.setRole(RoleType.USER);
		userRepository.save(user);
		return "회원가입이 완료되었습니다.";
	}
}

DummyControllerTest를 만들고 join이라는 메서드를 만듭니다.

 

postman을 통해 호출시에 body에 x-www-form-urlencoded 방식으로 요청을 합니다.

이렇게 호출을 하면 join메서드에서 파라미터를 통해 받을 때 각 변수별 String으로 받을수도, User 객체를 통해 받을수도 있습니다.

 

package com.cos.blog.repository;

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

import com.cos.blog.model.User;

// DAO
// 자동으로 bean 등록이 된다.
// @Repository // 생략 가능한다.
public interface UserRepository extends JpaRepository<User, Integer> {

}

DB에 Insert하기 위해서는 위와같이 인터페이스를 만들어줘야합니다.

UserRepository 인터페이스에서 JpaRepository를 상속받고 Generic에 User, Integer를 적습니다.

이 말은 해당 JpaRepository는 User Table이 관리하는 Repository이며 Primary Key는 Integer임을 가리킵니다.

JpaRepository 안에는 findAll 등 여러 메소드들을 갖고 있습니다.

회원가입시에는 save를 쓸 예정입니다.

또한 해당 Repository는 자동으로 bean에 등록이 됩니다. 그렇기에 Repository 어노테이션 생략이 가능합니다.

 

DummyControllerTest로 돌아와 해당 Repository를 추가하고 Autowired 어노테이션을 추가합니다.

위에서 사용 된 Autowired 어노테이션은 UserRepository 타입으로 스프링이 관리하는 객체가 있다면 userRepository로 넣어 달라는 의미입니다.

// @autowired 작성 안하고 의존성 주입 방법

@RestController
@RequiredArgsConstructor
public class DummyControllerTest {
	private final UserRepository userRepository;
}

추가로 @RequiredArgsConstructor를 사용하면 final 객체에 대해 생성자를 자동생성하여 생성자 주입을 할 수 있습니다.

 

 

DynamicInsert 어노테이션 : DB insert 과정에서 null인 값에 대해서는 제외처리하는 어노테이션입니다.

<!-- @DynamicInsert 붙이기 전 -->
insert 
    into
        User
        (createDate, email, password, role, username) 
    values
        (?, ?, ?, ?, ?)

<!-- @DynamicInsert 붙인 후 -->
insert 
    into
        User
        (createDate, email, password, username) 
    values
        (?, ?, ?, ?)

 

Enum class 생성으로 넣고자 하는 값을 실수로 넣는 경우를 줄일 수 있습니다.

package com.cos.blog.model;

public enum RoleType {
	USER, ADMIN
}

Enum은 데이터의 도메인을 만들 때 사용합니다. 도메인이라함은 범위라 할 수 있습니다.

 

 

 

강의 주소 : https://youtu.be/w0hF91Xs--4

강의 주소 : https://youtu.be/FhrkWA9hioU

 

JSON은 공용어입니다.

JSON은 중간데이터가 됩니다.

 

 

 

강의 주소 : https://youtu.be/olaeVwjx3J8

연관관계 주인 = FK를 가진 오브젝트

 

SELECT * FROM Board WHERE Id = 1;

한개의 Board를 조회하기 위해서는 Board, User, Reply 객체를 조회해야합니다.

이때 하나의 Board에는 하나의 User와 여러개의 Reply가 존재하게 됩니다.

@ManyToOne(fetch = FetchType.EAGER) // Many = Board, User = One
@JoinColumn(name = "userId")
private User user; //DB는 오브젝트를 저장할 수 없다. FK, 자바는 오브젝트를 저장할 수 있다.

@OneToMany(mappedBy = "board", fetch = FetchType.EAGER) // mappedBy 연관관계의 주인이 아니다 (난 FK가 아니에요) DB에 컬럼을 만들지 마세요.
private List<Reply> reply;

이때 reply는 FK가 아닙니다.

(FK를 설정할때만 JoinColumn 어노테이션이 사용됩니다.)

만약 reply를 FK로 설정하게 되면, 1정규화(원자성)를 어기게 됩니다.

 

mappedBy 속성을 통하여 Reply객체의 board값을 매핑시켜줍니다.

(mappedBy의 의미는 난 연관관계의 주인이 아니며, FK를 만들지 말라는 의미입니다.)

 

 

ManyToOne 어노테이션의 기본 패치 전략은 EAGER입니다.

OneToMany 어노테이션의 기본 패치 전략은 LAZY입니다.

 

바로 보여 줄 필요가 없을때는 LAZY 전략이 맞으나, 바로 보여줘야하는 정보에 대해서는 EAGER전략으로 가야합니다.

(EAGER : 무조건 들고와)

 

 

 

강의 주소 : https://youtu.be/DtMmXQl4_hw

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Reply {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY) // auto_increment
	private int id;
	
	@Column(nullable = false, length = 200)
	private String content;
	
	@ManyToOne
	@JoinColumn(name = "boardId")
	private Board board;
	
	@ManyToOne
	@JoinColumn(name = "userId")
	private User user;
	
	@CreationTimestamp
	private Timestamp createDate;
}

Reply에 대한 테이블을 생성해줍니다.

Entity 어노테이션은 클래스와 가까이 위치하는게 좋습니다.

(이해는 잘 안가지만 강의에서 그렇게 말씀해주셨기때문입니다...)

 

 

강의 주소 : https://youtu.be/u-E9TH4ZaEA

@Entity
public class Board {
	
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY) // auto_increment
	private int id;

	@Column(nullable = false, length = 100)
	private String title;
	
	@Lob // 대용량 데이터
	private String content; // 섬머노트 라이브러리 <html>태그가 섞여서 디자인이 됨.
	
	@ColumnDefault("0")
	private int count; // 조회수
	
	@ManyToOne // Many = Board, User = One
	@JoinColumn(name = "userId")
	private User user; //DB는 오브젝트를 저장할 수 없다. FK, 자바는 오브젝트를 저장할 수 있다.
	
	@CreationTimestamp
	private Timestamp createDate;
}

Board 테이블을 생성합니다.

대용량 데이터에 대해서는 Lob 어노테이션을 선언합니다.

count에서 default값은 int 값이기에 Single quotation은 붙이지 않습니다.

JAVA에서는 ORM방식에 따라 객체를 불러와 Foreign Key를 자동적으로 연결 할 수 있습니다.

이때 JoinColumn을 선언하여 해당 Column의 이름을 userId로 지칭합니다.

또한 ManyToOne 어노테이션을 통해 여러개의 Board에 대해 하나의 사용자만 접근 할 수 있음을 정해줍니다.

 

강의 주소 : https://youtu.be/MJSMvETSE7E

DB 인코딩 타입 문제로 인해 변경하는 강의입니다.

 

 

강의 주소 : https://youtu.be/s1AxIA033jQ

package com.cos.blog.model;

import java.sql.Timestamp;

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

import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.CreationTimestamp;

@Entity // User 클래스가 MySQL에 테이블이 생성이 된다.
public class User {
	
	@Id //Primary key
	@GeneratedValue(strategy = GenerationType.IDENTITY) // 프로젝트에서 연결된 DB의 넘버링 전략을 따라간다.
	private int id; // 시퀀스, auto_increment
	
	@Column(nullable = false, length = 30)
	private String username;
	
	@Column(nullable = false, length = 100) // 123456 => 해쉬(비밀번호 암호화)
	private String password;
	
	@Column(nullable = false, length = 50)
	private String  email;
	
	@ColumnDefault("'user'") // 문자라는 것을 알려주기 위해 싱글쿼테이션을 넣는다.
	private String role; // Enum을 쓰는게 좋다. // admin, user, manager
	
	@CreationTimestamp // 시간이 자동 입력
	private Timestamp createDate;
}

 

Enum 타입은 데이터를 도메인으로 설정 할 수 있습니다.

 

jpa:
    open-in-view: true
    hibernate:
      ddl-auto: create
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
      use-new-id-generator-mappings: false
    show-sql: true
    properties:
      hibernate.format_sql: true

ddl-auto : create -> 생성하고자 하는 테이블이 기존에 있어도 새로 만듭니다( 추후에는 update로 변경해야합니다.)

use-new-id-generator-mappings : JPA가 사용하는 기본 넘버링 전략을 선택 할 수 있습니다.

 

아래 두 속성값은 spring boot를 실행시 콘솔에 쿼리를 보여주며, 깔끔하게 정리하여 보여준다는 의미입니다.

show-sql: true
    properties:
      hibernate.format_sql: true

physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

: Entity 생성시 기입 된 내용대로 컬럼을 생성해 줍니다.

private String myEmail; // myEmail

 

physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy

: Entity 생성시 언더스코어로 컬럼을 생성해 줍니다.

private String myEmail; // my_email

 

위에 대한 자세한 설명은 아래의 주소를 참고해주세요.

https://getinthere.tistory.com/20

 

스프링부트 with JPA 블로그 6강 - yaml 설정

1. yaml 이란 이 분이 설명을 너무 잘해두셔서 ㄷㄷ https://www.inflearn.com/questions/16184 yaml파일 이란 무엇인가요 - 인프런 질문 - yaml파일 이란 무엇인가요 안녕하세요 강사님 너무 질문이 많아서 죄송.

getinthere.tistory.com

 

 

강의 주소 : https://youtu.be/6ynr-XAI-rk

+ Recent posts