SpringBoot, JPA 디렉터리 구조

LeeKyoungChang·2022년 12월 2일
1
post-thumbnail

https://gmlwjd9405.github.io/2018/12/25/difference-dao-dto-entity.html 를 참고하여 정리했습니다.

 

📝 시작하기전
나 같은 경우 도메인형 디렉터리 구조를 사용한다.

 

✏️ 전체 구조 (package 구조)

(1) JPA package 구조

  • respository가 결국 dao이다.

 

(2) MyBatis package 구조

Client <-> Controller <-> Service <-> Mapper <-> DB

  • MyBatis를 사용할 때는 mapper를 사용하면 일일이 DAO를 만들지 않아도 된다. (mapper 인터페이스 생성)
  • Spring에서는 프록시 등의 기능을(트랜잭션 처리 등등) 사용하기 위해 service 인터페이스가 필요하지만, boot에서는 따로 필요없다. (service 인터페이스 => service, serviceImpl 인 것 같다.)

책에서 나온 디렉터리 구조

 

📖 A. Controller(web)

  • 해당 요청 url에 따라 적절한 view와 mapping을 처리한다.
  • @Autowired Service를 통해 service의 method를 이용한다. (사용자의 입력을 받고 서비스로 전달하는 역할)
  • 적절한 DTO를 담아서 Client에게 전달한다.

 

✔️ @Controller vs @RestController

@Controller

@Controller
@RequestMapping("/")
public class HomeController {
  @GetMapping
  public String home(HttpSession session) {
      if (!SessionUtil.getUser(session).isPresent()) {
          return "login";
      }
      return "index";
  }
}
  • API와 view를 동시에 사용하는 경우 사용한다.
  • 만약, API 서비스로 사용하는 경우 @ResponseBody를 사용하여 객체를 반환한다.
  • view(화면) return이 주 목적이다.

 

@RestController

@RestController
@RequestMapping("/api/users")
public class ApiUserController {
  @Autowired
  private UserService userService;

  @PostMapping("/login")
  public ResponseEntity login(@RequestBody @Valid LoginDto loginDto, HttpSession session) {
      SessionUtil.setUser(session, userService.login(loginDto));
      return new ResponseEntity(HttpStatus.OK);
  }
}
  • view가 필요없고 API만 지원하는 서비스에서 주로 사용한다.
  • @RequestMapping 메서드가 기본적으로 @ResponseBody 의미를 가정한다.
  • data (json, xml 등) return 이 주목적일 때는 return 타입이 ResponseEntity이다.
  • 결국 : @RestController = @Controller + @ResponseBody

 

 

📖 B. service (SpringBoot, JPA)

✔️ service

  • 비즈니스 로직을 수행 ( ex 중복 아이디가 있는지 없는지를 검사하기 위한 일련의 과정들)
    • 어떻게 데이터가 생성, 저장, 수정, 삭제, 조회 등 되는 지를 정의한 것
    • DAO로 DB에 접근하고 DTO로 데이터를 전달받은 다음, 비즈니스 로직을 처리해 적절한 데이터를 반환한다.
  • @Autowired Repository를 통해 repository의 method를 이용한다. (Hibernate JPA를 사용하는 경우)
  • Service를 구현할 때는? (Spring 일 경우)
    • Service Interface를 만들기
    • Service Interface 구현한 Class 만들기
  • SpringBoot 에서는 Interface를 추가할 필요가 없다. (라고 okky 한 분이 말씀해주셨다.)

🧐 service하나만 사용하면 되지 왜 service, serviceImlp을 사용해야하는걸까?
참고하기 좋은 블로그 을 읽으면 설명이 잘되어 있다!

  • 대부분 프로젝트는 인터페이스와 구현체가 1:1로 만들어져 사용되고 있기에 service 하나만 구현해도 된다.
  • service를 interface로 만드는 목적은 하나의 역할을 여러 방식으로 구현하는데 있다.

✔️ Spring 환경에서 controller, service 예시

참고하기 좋은 블로그 (https://linked2ev.github.io/spring/2019/08/14/Spring-2-%EC%8A%A4%ED%94%84%EB%A7%81-MVC-%ED%8C%A8%ED%84%B4/)

@Controller 
public class UserController { 
	@Autowired 
	private UserSerivce userService;
	
	@RequestMapping(value="/getUsers") 
	public String getUsers(Model model) {
		model.addAttribute("users",userService.getUsers()); 
		return "user"; 
	} 
}
public interface UserSerivce { 
	public ArrayList getUsers(); 
}
@Service 
public class UserServiceImpl implements UserSerivce{ 
	@Override 
	public ArrayList getUsers() { 
		// TODO Auto-generated method stub 
		ArrayList arrayListOfUser = new ArrayList<>(); 
		for(int indexOfUser = 0; indexOfUser < 100; indexOfUser++) {
			User user = new User(); 
			user.setUserId(indexOfUser);
			user.setUserName("UserName"+indexOfUser);
			arrayListOfUser.add(user); 
		} 
	return arrayListOfUser; 
	} 
}

UserServiceImpl은 UserService라는 인터페이스의 구현체이므로, @Autowired를 Controller에서 이용시 UserService라는 인터페이스를 상속한 Class 자동으로 등록시켜준다.

 

✔️ SpringBoot 환경에서 service

controller

@RestController
public class Controller{
	@Autowried
	UserService service;


	@PostMapping(value = "/login")  
	public String login(@RequestBody HashMap<String, String> map) throws Exception {  
	   UserDto userDto = service.login(map);  
	   if (userDto != null) {  
	      String token = createToken(userDto);  
	      return token;          
	    } else {  
	      return "0";  
	    }
	}

}

service

@Service
public class Service{

	@Autowired
	UserMapper mapper;

	public UserDto login(HashMap<String, String> map){return mapper.login(map);}
}

 

✔️ JPA 구조를 사용한 경우 service

위와 같이 Controller에서는 Service를 호출한다. 📖 A. Controller(web)

@Service
public class UserService {
  @Autowired
  private UserRepository userRepository;
  @Resource(name = "bCryptPasswordEncoder")
  private PasswordEncoder bCryptPasswordEncoder;
  @Autowired
  private MessageSourceAccessor msa;

  public User save(UserDto userDto) {
      if (isExistUser(userDto.getEmail())) {
          throw new UserDuplicatedException(msa.getMessage("email.duplicate.message"));
      }
      return userRepository.save(userDto.toEntityWithPasswordEncode(bCryptPasswordEncoder);
  }
}
  • @Autowired Repository 를 통해 repository의 method를 이용한다.
  • Business Logic을 처리한다.
  • DAO로(repository) DB에 접근하고 DTO로 데이터를 전달 받은 다음, 비즈니스 로직을 처리해 적절한 데이터를 반환한다.

 

 

📖 C. DAO(SpringBoot, JPA)

  • 실제로 DB에 접근하는 객체
  • Service와 DB를 연결하는 고리의 역할을 한다.

✔️ SpringBoot MyBatis에서는 DAO대신 Mapper을 사용한다.

mapper

@Mapper
public interface Mapper{
	UserDto login(HashMap<String, String> map);
}
<mapper namespace="com.ssaffy.happyapi.User.UserMapper">  
   <resultMap type="com.ssaffy.happyapi.User.UserDto" id="userDto">  
      <result column="id" property="id" />  
      <result column="pw" property="pw" />  
      <result column="name" property="name" />
   </resultMap>  
      <select id="login" parameterType="map" resultMap="userDto">  
      select id, pw, name from user where id = #{id} and pw = #{pw}  
   </select>
</mapper>

 

✔️ JPA에서는 DAO로 repository를 사용한다.

public interface UserRepository extends JpaRepository<User, Long> {
}
  • 실제로 DB에 접근하는 객체이다.
  • Service와 DB를 연결하는 고리의 역할을 한다.
  • SQL을 직접 사용하여 DB에 접근한 후 적절한 CRUD API를 제공한다.
    • JPA 대부분의 기본적인 CRUD method를 제공하고 있다.
    • extends JpaRespoitory<User, Long>

 

 

📖 D. DTO

  • 계층간 데이터 교환을 위한 객체(Java Beans)
    • DB에서 데이터를 얻어 Service나 Controller 등으로 보낼 때 사용하는 객체
    • 로직을 갖고 있지 않은 순수한 데이터 객체, getter/setter 메서드만을 갖는다.
    • 하지만, DB에서 꺼낸 값을 임의로 변경할 필요가 없기 때문에 DTO클래스에는 setter가 없다. (대신, 생성자에서 값을 할당한다.)
  • VO(Value Object) vs DTO
    • VO는 DTO와 동일한 개념이지만 read only 속성을 갖는다.
    • VO는 특정한 비즈니스 값을 담는 객체, DTO는 Layer간의 통신 용도로 오고가는 객체

 

 

📖 E. Entity Class (domain package)

  • 실제 DB의 테이블과 매칭될 클래스

    • 테이블과 링크될(매핑될) 클래스임을 나타낸다.
    • Entity 클래스 또한 가장 Core한 클래스라고 부른다.
    • @Entity, @Column, @Id 등을 이용한다.
  • Entity 클래스와 DTO 클래스를 분리하는 이유

    • View Layer와 DB Layer의 역할을 분리하기 위해서 사용
    • 테이블과 매핑되는 Entity 클래스가 변경되면 여러 클래스에 영향을 준다.
    • View와 통신하는 DTO 클래스(Request, Response 클래스)는 자주 변경되므로 분리해야 한다.
@Entity
@Getter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@ToString
public class User implements Serializable {
  private static final long serialVersionUID = 7342736640368461848L;

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @JsonProperty
  private Long id;

  @Column(nullable = false)
  @JsonProperty
  private String email;

  @Column(nullable = false)
  @JsonIgnore
  private String password;

  // @Override 
  // public boolean equals(Object o) { ... }
  // @Override
  // public int hashCode() { ... }
  // @Override
  // public String toString() { ... }
}

 

🔔 이외로

  • common 디렉터리에는 유틸러티/전역변수 관련 클래스를 넣는게 보통이다. (아직까지는 모르겠음)
  • config 디렉터리에는 @Configuration 에너테이션이 붙는 클래스를 주로 넣는다. 수동으로 스프링 컨테이너에 빈을 등록하는 방법 (swagger와 같은 것?)
  • web 디렉터리에는 @Controller@RestController 에너테이션이 붙는 컨트롤러 클래스가 들어간다.

 

profile
"야, (오류 만났어?) 너두 (해결) 할 수 있어"

0개의 댓글