ORM
Object Relational Mapping의 약자로
객체와 RDBS를 Mapping 해주는 시스템이라고 보면 될 것 같다. 대표적으로 JPA 등이 있다.
Node.js에는 대표적으로 sequalize가 있다.
JPA
Java Persistence API의 약자로
자바에서 제공하는 ORM 기술 표준이다. 대표적으로 Hibernate가 있다.
JPA의 경우 domain 대신 Entity를 활용한다. (domain이 entity라고 봐도 무방할 것 같다.)
전에 유데미에서 맥시밀리언 강사의 Express 강의를 들었을 때 폼 데이터를 클래스를 이용해서 받고
update의 경우도 몽고 db에서 id가 있으면 update를 하는 식으로 진행했었는데 이게 Spring에서 진행되는 방식을 그냥 Express로 구현한 거구나 싶다. 그 덕분에 이해가 좀 쉬웠던 것 같다.
폴더 구성: 이렇게 구성한다.
Entity | DTO | Repository | Service | Controller |
테이블 매핑 | DTO | 데이터베이스 에 접근하는 함수 정의 | 컨트롤러에서 쓰일 함수 정의 | req,res |
Entity: 데이터베이스에 쓰일 필드와 여러 Entity 간의 관계를 설정하는 것.
ORM이 객체, 필드, == table, column 이렇게 mapping되기 때문에 그 정의를 따로 해주는 것이다. (MVC에서 Model이라고 생각하면 될 것 같다.)
Entity 관련 Annotation: @Entity, @Table, @Id, @Column 등
Entity
package codingon.codingonspringbootjpa.entity;
// Entity
// - DB 에서 쓰이는 필드와 매핑이 되는 클래스 (DB 테이블과 대응되는 클래스) - MyBatis의 domain
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
// 빌더, 엔티티 어노테이션 동시에 사용 -> 두개가 충돌해서 생성자가 만들어지지 않는 문제가 생김
// 원래는 Entity, Builder 각각에 맞는 생성자가 자동으로 생김.
// 빌더 -> 모든 필드를 사용하는 생성자 필요
// 엔티티 -> 기본 생성자 필요
// 그래서 ALLArgsConstructor와 NoArgsConstructor가 필요함 (Lombok의 도움을 받음)
@Getter
@Builder // 객체 생성 처리
@AllArgsConstructor // 모든 필드를 사용하는 생성자
@NoArgsConstructor // 매개변수가 없는 생성자 (기본 생성자)
@Entity // 해당 클래스가 Entity 클래스임을 명시 (반드시 추가하기)
@Table(name = "user") // 테이블 명과 클래스 명이 동일한 경우 생략 가능 (대문자 사용하려면 따로 설정 필요)
public class UserEntity {
@Id // PK 지정
@GeneratedValue(strategy = GenerationType.IDENTITY) // auto_increment 사용 옵션
private int id;
@Column(nullable = false, length = 20) // varchar(20), length 안적으면 255
private String name; // 만약 컬럼명이 다르자면 name="text"로 매핑시킬 컬럼명 적어주면 된다.
// ex. @Column(nullable = false, length = 20, name = "another_column_name")
// 타입: text(varchar X)
@Column(columnDefinition = "Text")
private String nickname;
//이렇게 하면 default가 NOW()로 타임스탬프 생성 가능
@CreationTimestamp
private Timestamp registered;
// 참고. Enum 타입 지정 가능
// @Column
// @Enumerated(EnumType.STRING)
// private UserType type;
//
// public enum UserType {
// STUDENT, TEACHER
// }
}
DTO
package codingon.codingonspringbootjpa.dto;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class UserDTO {
private int id;
private String name;
private String nickname;
private int no;
}
Repository
정해진 컨벤션에 맞게 이름만 정하면 따로 커스텀으로 함수를 만들지 않아도 된다. (만들 수도 있다.)
package codingon.codingonspringbootjpa.repository;
import codingon.codingonspringbootjpa.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
// JpaRepository
// - 인터페이스
// - JpaRepository<Type, ID>
// - T: 테이블에 매핑될 엔티티 클래스
// - ID: 엔티티의 기본 키 (primary key) 타입
// Repository:
// - Entity 에 의해 생성된 DB 에 접근하는 메소드를 사용하기 위한 인터페이스
@Repository // Repository 계층임을 명시하는 클래스
public interface UserRepository extends JpaRepository<UserEntity, Integer> {
// 1)
// existsByXXX()
// - XXX: 컬럼명 UPPERCASE
// - XXX 과 일치하는 컬럼이 있는지 boolean 반환
boolean existsByName(String name);
// 2)
// findByXXX()
// - XXX: 컬럼명 UPPERCASE
List<UserEntity> findByName(String name);
List<UserEntity> findByNameOrNickname(String name, String nickname);
// 반환 형식은 Entity, List<Entity> 모두 가능
// 단, 반환 된 값이 1개를 넘는데 Entity 로 넣으려고 하면 문제 발생
Optional<UserEntity> findById(int id);
// Query 어노테이션
// - JpaRepository 인터페이스에 내장된 메소드만으로 해결이 안되는 경우, raw query 작성하게 해주는 어노테이션
// - sql 문이 조금 다르다!
// JPA 는 테이블이 아닌 객체 위주로 돌아가기 때문에 객체 (Entity) 이름으로 사용해야 함
// ex. @Query("SELECT u FROM UserEntity u WHERE u.name=:name and u.nickname=:nickname")
// - nativeQuery 옵션을 사용하면 찐 raw sql 사용 가능 -> DBMS 종속적이지 않은 ORM 특징을 살릴 수 없음
// ex. @Query(nativeQuery = true, value="SELECT * from user WHERE u.name=:name and u.nickname=:nickname")
@Query("select u from UserEntity u where u.name = :name") // UserEntity == u. 즉 UserEntity의 모든 필드 가져오겠다는 말
UserEntity findName(String name);
}
Service
findAll()이나 findById(), DeleteById(), Save()같은 default로 제공하는 함수가 있음을 참고
의존성 주입을 생성자로 하면 클래스가 생성됨과 동시에 의존성을 주입해 줄 수 있어서 권장된다고 한다.
package codingon.codingonspringbootjpa.service;
import codingon.codingonspringbootjpa.dto.UserDTO;
import codingon.codingonspringbootjpa.entity.UserEntity;
import codingon.codingonspringbootjpa.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Service
public class UserService {
private final UserRepository userRepository;
// 생성자 주입. 이게 권장된다고 한다.
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public List<UserDTO> getUserList() {
// repository 에서 전체 조회가 가능하도록
// JPA에서 정의되어있는 메소드 사용
List<UserEntity> userEntityList = userRepository.findAll();
List<UserDTO> result = new ArrayList<>();
for(UserEntity userEntity: userEntityList) {
UserDTO userDTO = UserDTO.builder()
.id(userEntity.getId())
.name(userEntity.getName())
.nickname(userEntity.getNickname())
.no(userEntity.getId()+100)
.build();
result.add(userDTO);
}
return result;
}
public boolean checkName(String name) {
boolean result = userRepository.existsByName(name);
return result;
}
public String searchId(int id) {
Optional<UserEntity> result = userRepository.findById(id);
if(result.isPresent()) {
return result.get().getName();
} else {
return "no user founded";
}
}
public String insertUser(UserEntity user) {
// jpa save(T) 메소드 : T 는 Entity
// - insert 할 때 사용
// - 기존 entity를 업데이트 할 때도 사용
// => 기본값(pk) 상태에 따라 다르게 동작
// pk 가 존재한다면 pk 와 연결된 entity update
// pk 가 없는 경우, 새로운 entity insert
// 그래서 업데이트도 save 메소드를 쓴다.
UserEntity newUser = userRepository.save(user);
// save를 했을 때 반환되는 객체는 Entity 객체
return newUser.getName();
}
public List<UserDTO> searchName(String name) {
List<UserEntity> users = userRepository.findByName(name);
List<UserDTO> result = new ArrayList<>();
for(UserEntity userEntity: users) {
UserDTO userDTO = UserDTO.builder()
.id(userEntity.getId())
.name(userEntity.getName())
.nickname(userEntity.getNickname())
.no(userEntity.getId()+100)
.build();
result.add(userDTO);
}
return result;
}
public List<UserDTO> searchNameOrNickname(String name) {
List<UserEntity> users = userRepository.findByNameOrNickname(name, name);
List<UserDTO> result = new ArrayList<>();
for(UserEntity userEntity: users) {
UserDTO userDTO = UserDTO.builder()
.id(userEntity.getId())
.name(userEntity.getName())
.nickname(userEntity.getNickname())
.no(userEntity.getId()+100)
.build();
result.add(userDTO);
}
return result;
}
}
Controller
package codingon.codingonspringbootjpa.controller;
import codingon.codingonspringbootjpa.dto.UserDTO;
import codingon.codingonspringbootjpa.entity.UserEntity;
import codingon.codingonspringbootjpa.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Controller
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService
}
@GetMapping("/")
public String getUsers(Model model){
List<UserDTO> users = userService.getUserList();
model.addAttribute("list", users);
return "user";
}
@GetMapping("/check")
@ResponseBody
public boolean checkName(@RequestParam String name, Model model) {
boolean result = userService.checkName(name);
return result;
}
@PostMapping("/")
@ResponseBody
public String insertUser(@RequestBody UserEntity user) {
String newName = userService.insertUser(user);
return newName + " Success";
}
@GetMapping("/search/name")
public String searchName(String name, Model model) {
List<UserDTO> users = userService.searchName(name);
model.addAttribute("list", users);
return "user";
}
@GetMapping("/search/nameornickname")
public String searchNameOrNickname(String name, Model model) {
List<UserDTO> users = userService.searchNameOrNickname(name);
model.addAttribute("list", users);
return "user";
}
}
실습하다가 오류가 좀 났었는데. 확실히 폼이 조금 헷갈린다.
RequestBody는 json or XML데이터로 보내줘야 하기 때문에 axios로 보낼때 필요함을 잊지 말자.
그리고 Get 이외의 요청 시에는 @ModelAttribute가 인식하지 못함도 잊지 말자.
@RequestParam는 하나하나씩 매핑하는 거고 (Get이외의 동적폼 전달시 인식 불가, urlencoded 가능)
@ModelAttribute 는 setter를 이용해서 매핑해주는 거다. (Get이외의 동적폼 전달시 인식 불가, urlencoded 가능)
근데 동적 폼의 경우 json이나 xml 형식으로 보내기 때문에 @RequestBody로 받아야 UserDTO 객체로 변환할 수 있다.
//문제는 HTTP POST 요청을 처리할 때 Spring이 요청 바디의 데이터를 UserDTO 객체로 변환하지 못하고 있다는 것으로 보입니다.
// 이 문제는 HTTP POST 요청의 바디를 읽어 UserDTO 객체로 변환하는 방법을 정확하게 설정하지 않았기 때문에 발생할 수 있습니다.
'포스코x코딩온' 카테고리의 다른 글
[포스코x코딩온] 풀스택 부트캠프 4차 프로젝트 -2 (3) | 2024.03.08 |
---|---|
[포스코x코딩온] 풀스택 부트캠프 4차 프로젝트 -1 (0) | 2024.03.05 |
[포스코x코딩온] 풀스택 부트캠프 18주차 정리 Java(API(Get, Post), SQL Mapper(MyBatis)) (1) | 2024.02.24 |
[포스코x코딩온] 풀스택 부트캠프 18주차 정리 Collection, Spring boot (0) | 2024.02.22 |
[포스코x코딩온] 풀스택 부트캠프 18주차 정리 Java(Wrapper, generic) (0) | 2024.02.19 |