스프링 부트 기초 - 5(JPA의 select,insert,update,delete)
Board
@Entity
public class Board {
@Id // primary key
@GeneratedValue(strategy = GenerationType.IDENTITY) // auto_increment
private int id;
@Column(nullable = false, length = 100)
private String title;
@Lob // 대용량 데이터
private String content; // 썸머노트 라이브러리 사용
@ColumnDefault("0")
private int count; // 조회수
@ManyToOne // Many = Board, User = One -> 한명의 User는 여러개의 Board를 쓸수 있다.
@JoinColumn(name ="userId")
private User user; // DB는 오브젝트를 저장할 수 없다. FK, 자바는 오브젝트를 저장할 수 있다.
@CreationTimestamp
private Timestamp createDate;
}
ManyToOne의 FK는 User의 Primarykey를 참조한다(따라간다)
▶ 연관관계 매핑
엔티티들은 서로 관계를 맺고 있다. 그래서 엔티티들이 어떠한 연관관계를 맺는지 파악하는 것이 매우 중요하다.
연관관계 매핑이란 객체의 참조와 테이블의 외래키를 매핑하는 것을 의미한다.
JPA에서는 JDBC(MyBatis)를 사용했을 때와 달리 연관 관계에 있는 상대 테이블의 PK를 멤버변수로 갖지 않고, 엔티티 객체 자체를 통째로 참조한다(private User user)
1) 예시
// MyBatis
private Integer userId;
// JPA
private Board board;
2) 방향
단방향 관계 : 두 엔티티가 관계를 맺을 때, 한 쪽의 엔티티만 참조하고 있는 것을 의미한다.
양방향 관계 : 두 엔티티가 관계를 맺을 때, 양 쪽이 서로 참조하고 있는 것을 의미한다.
3) 다중성
관계에 있는 두 엔티티는 다음 중 하나의 관계를 갖는다.
Many To One - 다대일( N : 1 )
One To Many - 일대다( 1 : N )
One To One - 일대일( 1 : 1 )
Many To Many - 다대다( N : N )
예를들어 Board(게시판)는 많은 User(사용자)를 갖고 있으므로 User(사용자) 입장에서는 Board(게시판)와 일대다 관계이며, Board(게시판)의 입장에서는 User(사용자)에 속하므로 다대일 관계입니다.
즉, 어떤 엔티티를 중심으로 상대 엔티티를 바라 보느냐에 따라 다중성이 다릅니다.
▶ @ManyToOne
@ManyToOne 어노테이션은 이름 그대로 다대일( N : 1 ) 관계 매핑 정보이다
Board(게시판) 입장에서는 User(사용자)와 다대일 관계이므로 @ManyToOne이 된다.
연관관계를 매핑할 때 이렇게 다중성을 나타내는 어노테이션(@ManyToMany, @OneToOne 등…)은 필수로 사용해야 하며, 엔티티 자신을 기준으로 다중성을 생각해야 한다.
▶ @JoinColumn
@JoinColumn 어노테이션은 외래 키를 매핑 할 때 사용한다.
name 속성에는 매핑 할 외래 키 이름을 지정한다.
Board(게시판) 엔티티의 경우 User(사용자)엔티티의 id필드를 외래 키로 가지므로, userId를 작성하면 됩니다.
JPA로 데이터 Insert
▶ DummyControllerTest
@RestController
public class DummyControllerTest {
@Autowired // 의존성 주입(DI)
private UserRepository userRepository;
@PostMapping(value ="/dummy/join")
public String join(User user) {
System.out.println(user.getUsername());
System.out.println(user.getPassword());
System.out.println(user.getEmail());
userRepository.save(user);
return"회원가입이 완료되었습니다.";
}
}
▶ UserRepository(interface)
// DAO
// 자동으로 Bean 등록이 된다. -> @Repository 생략 가능
public interface UserRepository extends JpaRepository<User, Integer>{
// User라는 클래스, Integer는 User의 primary key의 타입명시
}
해당 링크로 데이터를 입력하여 MySQL에 데이터를 insert 하는 것 까지는 성공했다. 근데 role에는 설정해 놓은 default 값이 안들어 가고 null이 들어갔다. default 값 대신에 insert 작동시 계속 null로 들어가기 때문이다.
즉 저기서 role을 없애면 null이 들어갈 일이 없다. 그래서 사용하는 것이 @DynamicInsert이다.
@DynamicInsert는 insert시 null인 필드를 제외시켜준다.
▶ User
@DynamicInsert
public class User {
@Id // primary key
@GeneratedValue(strategy = GenerationType.IDENTITY) // 프로젝트에서 연결된 DB의 넘버링 전략을 따라간다.
private int id; // 시퀀스, auto_increment -> 비워두어도 자동으로 들어감
@Column(nullable = false, length = 20) // null 불가, 길이는 20
private String username; // 아이디
@Column(nullable = false, length = 100) // null 불가, 길이는 100
private String password; // 패스워드
@Column(nullable = false, length = 50)
private String email; // 이메일
@ColumnDefault(" 'user' ")
private String role; // Enum을 쓰는게 좋다 // admin, user, manager
@CreationTimestamp // 시간이 자동 입력
private Timestamp createDate; // 비어두어도 자동으로 들어감
}
@DynamicInsert 사용후
정상적으로 잘 들어오는 것을 확인할 수 있다.
❗ 하지만 진짜 Null이 필요할 때는 사용을 못하기 때문에 비효율성을 야기할 수 있음 그래서 Enum을 사용한다.
▶ User
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity // User 클래스가 MySql에 자동으로 테이블에 생성이 되게 도와줌
// @DynamicInsert
public class User {
@Id // primary key
@GeneratedValue(strategy = GenerationType.IDENTITY) // 프로젝트에서 연결된 DB의 넘버링 전략을 따라간다.
private int id; // 시퀀스, auto_increment -> 비워두어도 자동으로 들어감
@Column(nullable = false, length = 20) // null 불가, 길이는 20
private String username; // 아이디
@Column(nullable = false, length = 100) // null 불가, 길이는 100
private String password; // 패스워드
@Column(nullable = false, length = 50)
private String email; // 이메일
//@ColumnDefault(" 'user' ")
//private String role; // Enum을 쓰는게 좋다 // admin, user, manager
@Enumerated(EnumType.STRING) // Enum의 type이 String이라는 걸 명시해준다.
private RoleType role;
@CreationTimestamp // 시간이 자동 입력
private Timestamp createDate; // 비어두어도 자동으로 들어감
}
▶ RoleType
public enum RoleType {
USER,ADMIN
}
role에 USER라고 잘들어온 것을 확인할 수 있다.
JPA로 데이터 조회 (select)
▶ 1번
// http://localhost:8088/dummy/user/2
@GetMapping(value = "/dummy/user{id}") // {id} 주소로 파라미터를 전달 받을 수 있음.
public User detail(@PathVariable int id) {
// user/3을 찾으면 DB에는 id가 3인값이 없으므로 null이 된다
// return 으로 null이 되버리니까 문제가 생김 -> 그래서 JpaRepository에서 Optional로 이것을 해결 해줌
User user = userRepository.findById(id).orElseGet(new Supplier<User>() { // findById해서 없다면 new Supplier를 통해 User라는 빈객체 반환
@Override
public User get() {
return new User();
}
});
return user;
}
- orElseGet 대신 get 사용 가능하지만 orElseGet이나 orElseThrow를 사용하는 것이 좋음
▶ 2번
// http://localhost:8088/dummy/user/2
@GetMapping(value = "/dummy/user{id}") // {id} 주소로 파라미터를 전달 받을 수 있음.
public User detail(@PathVariable int id) {
// user/3을 찾으면 DB에는 id가 3인값이 없으므로 null이 된다
// return 으로 null이 되버리니까 문제가 생김 -> 그래서 JpaRepository에서 Optional로 이것을 해결 해줌
User user = userRepository.findById(id).orElseThrow(new Supplier<IllegalArgumentException>() { // findById해서 없다면 new Supplier를 통해 User라는 빈객체 반환
@Override
public IllegalArgumentException get() {
return new IllegalArgumentException("해당 유저는 없습니다.id : " + id);
}
});
// 람다식
// User user = userRepository.findById(id).orElseThrow(()->{
// return new IllegalArgumentException("해당 사용자는 없습니다");
// }));
return user;
}
성공시
실패시
JAP Pageable(페이징)
// 전체 데이터 조회
@GetMapping(value = "/dummy/users")
public List<User> list() {
return userRepository.findAll();
}
// 한 페이지당 2건의 데이터를 리턴받아 볼 예정
@GetMapping(value = "/dummy/user")
public Page<User> pageList(@PageableDefault(size=2, sort="id", direction = Sort.Direction.DESC) Pageable pageable) {
// 2건씩 가져오고 id를 기준으로, 최신순(DESC)으로 보여주기
Page<User> users = userRepository.findAll(pageable);
return users;
}
// 위의 코드를 조금 변경
@GetMapping(value = "/dummy/user")
public List<User> pageList(@PageableDefault(size=2, sort="id", direction = Sort.Direction.DESC) Pageable pageable) {
// 2건씩 가져오고 id를 기준으로, 최신순(DESC)으로 보여주기
Page<User> pagingUser = userRepository.findAll(pageable);
List<User> users = pagingUser.getContent();
return users;
}
JPA로 데이터 업데이트(update)
save 이용
- save 함수는 id를 전달하지 않으면 insert를 해주고
- save 함수는 id를 전달하고 해당 id에 대한 데이터가 있으면 update를 해주고
- save 함수는 id를 전달하고 해당 id에 대한 데이터가 없으면 insert를 해준다.
@PostMapping(value ="/dummy/user/{id}")
public User updateUser(@PathVariable int id, @RequestBody User requestUser) {
// JSON을 받기 위해 @RequestBody 사용 -> JSON으로 요청해도 스프링이 Java Object로 변환
// MesseageConverter의 Jackson 라이브러리가 받아줌
System.out.println("id : " + id);
System.out.println("password : " + requestUser.getPassword());
System.out.println("email : " + requestUser.getEmail());
requestUser.setId(id); // id를 셋
requestUser.setUsername("lovelove");
userRepository.save(requestUser); // 원래는 insert할 때 사용
return null;
}
현재 위에서 password와 email을 update 시켜주려고 한다.
하지만 에러가 발생 -> username이 not null로 잡혀있기 때문이다.
그래서 강제로 username에 값을 넣어 update를 해보았다. 하지만 다른 값들이 null로 변경되버리는 상황이 발생 (username, password, email 제외) 따라서 update에는 save를 잘 사용하지 않음
▶ 그럼 어떻게 해야 할까?
@PostMapping(value ="/dummy/user/{id}")
public User updateUser(@PathVariable int id, @RequestBody User requestUser) {
// JSON을 받기 위해 @RequestBody 사용 -> JSON으로 요청해도 스프링이 Java Object로 변환
// MesseageConverter의 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;
}
findById를 통해서 존재한 DB에 존재하는 데이터만 Update를 가능하게 먼저 판별을 해준다.
데이터가 존재한다면 해당 데이터의 Password와 Email만 변경되게 한다(set을 통해서).
첫번째 방식의 문제를 해결 했다고 하지만 위의 방법들은 update를 하기에는 좋지 않은 방법들이다.
그래서 사용하는 것이 @Transactional을 사용한다.
@Transactional // 함수 종료시에 자동 commit이 됨
@RestController
public class DummyControllerTest {
@Autowired // 의존성 주입(DI)
private UserRepository userRepository;
// 업데이트
@PostMapping(value ="/dummy/user/{id}")
public User updateUser(@PathVariable int id, @RequestBody User requestUser) {
// JSON을 받기 위해 @RequestBody 사용 -> JSON으로 요청해도 스프링이 Java Object로 변환
// MesseageConverter의 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 user;
}
JPA로 데이터 삭제(delete)
// 삭제
@DeleteMapping(value = "/dummy/user/{id}")
public String deleteUser(@PathVariable int id) {
try {
userRepository.deleteById(id);
}catch(Exception e) {
return "삭제에 실패하였습니다. 해당 id는 DB에 없습니다!";
}
return "삭제되었습니다. id : " + id;
}
deleteById를 이용하여 삭제
Leave a comment