본문 바로가기
SpringBoot

톰캣과 서비스 어노테이션

by 김마리님 2020. 7. 17.

다음 글은 다음 세팅을 한 이후에 적용 가능한 코드입니다.

https://itstudy-mary.tistory.com/191

 

Spring Blog Project 1. 기본 세팅

사용할 기본 Dependency STS에서 제공하지 않는 dependency를 추가한다. → dependency 추가 pom.xml 에 추가(jstl, jasper) javax.servlet jstl org.apache.tomcat.embed tomcat-embed-jasper → 데이터베이스..

itstudy-mary.tistory.com

 

1. 개념

왜 굳이 파일을 depth를 깊게 해서 만드는가? 간단히 말하면 톰캣은 WEB-INF 라는 파일 이하의 파일은 접근하지 못하기 때문에, 파일 이름으로 jsp 파일에 접근하지 못하고 반드시 컨트롤러를 통해 페이지에 접근할 수 있도록 하여 완벽한 mvc(모델2) 패턴을 만들기 위해서이다.

 

톰캣이 시작할때 다음과 같이 움직인다.

1) catalina.xml 스캔

이 때, 카타리나는 웹서버를 선택하는 문서이므로 우리가 여기서는 딱히 건드릴 필요가 없다. 실제 개발자가 제일 많이 관여하는 부분은 두번째 이다.

2) web.xml 스캔

web.xml은 선택된 웹서버에 다양한 기능과 필터를 추가할 수 있는데,

①인코딩 방식

인코딩 방식을 utf-8로 지정해두면, 모든 데이터의 request와 response가 utf-8로 인코딩된다.

②welcome-file list

도메인 이름 이후에 별다른 이름을 지정하지 않았을 때 나오는 파일을 지정할 수 있다.

③DB Resource

연결할 데이터베이스 리소스를 지정할 수 있다.

④servlet Mapping

원래라면 서블릿에서 도메인 주소 뒤에 들어오는 url에 따라 서로서로 다른 쪽으로 연결해주어야 한다. 그러나 xml 파일에 이것을 다 부여할 수 없기 때문에 /* 를 붙여 FrontController로 모두 보내버린다. 그럼 FrontController로 온 파일들은 모두 라우터를 통해 외부로 나가게 된다. 

스프링 같은 경우는 Dispatcher Servlet을 통해 FrontController과 라우팅, 두 역할을 동시에 해낸다. 

⑤custom filter

 

스프링 부트는 어노테이션이 붙은 것을 시작 전에 메모리에 올리는데, Mapping 어노테이션은 미리 올려두지 않는다. 왜일까?

 

외부에서 url 요청이 오면 -> Dispatcher Servlet이 FrontController로 접근하던 요청을 가로챔 ->

허용된 컨택스트 루트를 모두 스캔하여 관련 함수를 찾는다. -> 메모리에 띄운다.

 

다시 이야기 하면, 스프링부트는 자바 기반이기 때문에 스레드를 제공한다. DB의 경우 스레드로 처리하지 않고 Quene의 형태로 일을 순차적으로 처리하기 때문에 이런 방식을 이용할 필요가 없지만 일반적으로 자바 내부에서 처리할 수 있는 로직의 경우 Quene 형태로 일을 처리하면 비효율적이다. 따라서, 미리 메모리에 띄워두지 않고 그때그때 메모리에 띄워 스레드의 형태로 처리하는 방식을 택한 것이다.

 

이제, 서비스에 대해서 알아보자.

 

SQLSession이 DataSource를 이용해 (DBCP(database connction pool), 커넥션 풀)을 이용한다.  또, MyBatis를 통해 Persistance를 이용한다.

이렇게 SQLSession은 DB와

Repository를 통해 데이터를 전달받아 호출하게 된다.

 

톰캣은 데몬프로세스로서 계속 돌면서 서블릿에서 요청이 들어올 때 마다 컨트롤러를 생성한다. 컨트롤러에서 필요 서비스를 호출하고, 데이터베이스가 필요하면 SQL 세션을 호출하고, SQL 세션이 일을 처리해준다.

 

2. 예시 코드

간단하게 송금- 출력의 코드를 작성해본다.

윗 포스팅을 통해 미리 세팅을 해두고 시작한다.

먼저 SQL로 간단히 테이블을 만든다.

 

(MySQL)

CREATE TABLE account(
	id int auto_increment primary key,
    username varchar(100) unique not null,
    accountNumber varchar(100) not null,
    money int
) engine=InnoDB default charset=utf8;

insert into account(username,accountNumber,money)
values('홍길동','01011112222',50000);

insert into account(username,accountNumber,money)
values('장보고','01033334444',100000);

commit;

 

다음 모델을 만든다.

 

../model/Account.java

package com.mary.blog.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Account {
	private int id;
	private String username;
	private String accountNumber;
	private int money;
}

 

다음 우리가 만들 일을 만들어보자.

우리는 송금, 인출, 전체 계좌 조회를 할 거니까. 이거에 따른 SQL문을 작성한다.

 

../mapper/account.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mary.blog.repository.AccountRepository">
	<select id="findAll" resultType="com.mary.blog.model.Account">
    select * from account
  </select>
  <select id="findByAccountNumber" resultType="com.mary.blog.model.Account">
    select * from account WHERE accountNumber=#{accountNumber}
  </select>
  
  <update id="update">
    UPDATE account SET money = #{money} WHERE accountNumber = #{accountNumber}
  </update>

</mapper>


 이제 이것을 저장소에 저장한다.

 

../repository/AccountRepository.java

package com.mary.blog.repository;

import java.util.List;
import com.mary.blog.model.Account;

public interface AccountRepository {
	public void update(Account account);
	public List<Account> findAll();
	public Account findByAccountNumber(String accountNumber);
}

 

이제 송금, 인출에 대해 생각해보자. 

송금과 인출은 계좌번호 이외에 '얼마를 보냈어?/얼마를 인출했어?' 의 값이 필요한데, 이것이 모델에는 없어서 파라메터로 넘길수 없다. 따라서 dto를 생성한다.

 

../dto/SendRequestDto.java

package com.mary.blog.dto;

import lombok.Builder;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SendRequestDto {
	private String sendAccountNumber;
	private String receiveAccountNumber;
	private int sendMoney;

}

 

../dto/WithdrawRequestDto.java

package com.mary.blog.dto;

import lombok.Builder;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class WithdrawRequestDto {
	private String accountNumber;
	private int money;

}

 

이것을 함수로 참조할 서비스를 만든다.

../service/AccountService.java

package com.mary.blog.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.mary.blog.dto.SendRequestDto;
import com.mary.blog.dto.WithdrawRequestDto;
import com.mary.blog.model.Account;
import com.mary.blog.repository.AccountRepository;

@Service
public class AccountService {
	
	@Autowired
	private AccountRepository accountRepository;
	
	@Transactional
	public void 송금(SendRequestDto dto) {
		//홍길동 업데이트
		Account 홍길동=accountRepository.findByAccountNumber(dto.getSendAccountNumber());
		홍길동.setMoney(홍길동.getMoney()-dto.getSendMoney());
		accountRepository.update(홍길동);
		
		//장보고
		Account 장보고=accountRepository.findByAccountNumber(dto.getReceiveAccountNumber());
		장보고.setMoney(장보고.getMoney()+dto.getSendMoney());
		accountRepository.update(장보고);
	}
	
	@Transactional
	public void 인출(WithdrawRequestDto dto) {
		Account 홍길동=accountRepository.findByAccountNumber(dto.getAccountNumber());
		홍길동.setMoney(홍길동.getMoney()-dto.getMoney());
		accountRepository.update(홍길동);
	}
	
	@Transactional(readOnly=true) //고립성
	public List<Account> 계좌정보보기(){
		return accountRepository.findAll();
	}
}

 

마지막으로, 서비스를 호출할 컨트롤러를 만든다.

../controller/AccountController.java

package com.mary.blog.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.mary.blog.dto.SendRequestDto;
import com.mary.blog.dto.WithdrawRequestDto;
import com.mary.blog.model.Account;
import com.mary.blog.service.AccountService;

@Controller
public class AccountController {
	
	@Autowired
	private AccountService accountService;
	
	@GetMapping("/account")
	public @ResponseBody List<Account> findAll(){
		return accountService.계좌정보보기();
	}

	@PutMapping("/send")
	public @ResponseBody String send(SendRequestDto dto) {
		accountService.송금(dto);
		return "<h1>송금이 완료되었습니다.</h1>";
	}
	
	@PutMapping("/withdraw")
	public @ResponseBody String withdraw(WithdrawRequestDto dto) {
		accountService.인출(dto);
		return "<h1>인출이 완료되었습니다.</h1>";
	}
	
}

 

결과 :

반응형