Skip to the content.

비즈니스 계층

비즈니스 계층은 고객의 요구사항을 반영하는 계층으로 프레젠테이션 계층과 영속 계층의 중간 다리 역할을 하게 된다.

영속 예층은 데이터베이스를 기준으로 해서 처리하게 된다.

예컨데,’쇼핑몰에서 상품을 구매한다’고 가정해 보자.해당 쇼핑몰의 로직이 ‘물건을 구매한 회원에게는 포인트를 올려준다’고 하면 영속 계층의 설계는 ‘삼품’과 ‘회원’으로 나누어서 설계하게 된다.반면에 비즈니스 계층은 상품 영역과 회원 영역을 동시에 사용해서 하나의 로직을 처리하게 되므로 다음과 같은 구조로 만들게 된다.

현재 예제는 단일한 테이블을 이용하고 있기 때문에 위와 같은 구조는 아니지만

설계를 할때는 원칙적으로 영역을 구분해서 작성해야 한다.일반적으로 비즈니스 영역에 있는 객체들은 서비르사는 용어를 많이 사용한다.

비즈니스 계층의 설정

비즈니스 계층을 위해서 프로젝트 내 com.osk2090.service 라는 패키지를 작성한다.

설계를 할 때는 각 계층 간의 연결을 인터페이스를 이용해서 느슨한 연결을 한다.

게시물은 BoardService 인터페이스와 인터페이스를 구현한 BoardServiceImpl 클래스를 선언한다.

package com.osk2090.service;

import com.osk2090.domain.BoardVO;

import java.util.List;

public interface BoardService {
    public void register(BoardVO board);

    public BoardVO get(Long bno);

    public boolean modify(BoardVO board);

    public boolean remove(Long bno);

    public List<BoardVO> getList();
}

BoardService 메서드를 설계할 때 메서드 이름은 현실적인 로직의 이름을 붙이는 것이 관례이다.

명백하게 반환해야 할 데이터가 있는 ‘select’를 해야 하는 메서드는 리턴 타입을 지정할 수 있다.

게시물은 특정한 게시물을 가져오는 get() 메서드와 전체 리스트를 구하는 getList()의 경우 처음부터 메서드의 리턴 타입을 결정해서 진행할 수 있다.

BoardService 인터페이스를 구현하는 구현체는 BoardServiceImpl이라는 클래스로 작성한다.

클래스의 상세 내용보다 약간의 로그를 기록할 수 있는 정도를 코드를 작성한다.

BoardServiceImpl.class

package com.osk2090.service;

import com.osk2090.domain.BoardVO;
import com.osk2090.mapper.BoardMapper;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j;
import org.springframework.stereotype.Service;

import java.util.List;

@Log4j
@Service
@AllArgsConstructor
public class BoardServiceImpl implements BoardService {
    //spring 4.3 이상에서 자동 처리
    private BoardMapper mapper;

    @Override
    public void register(BoardVO board) {
        
    }

    @Override
    public BoardVO get(Long bno) {
        return null;
    }

    @Override
    public boolean modify(BoardVO board) {
        return false;
    }

    @Override
    public boolean remove(Long bno) {
        return false;
    }

    @Override
    public List<BoardVO> getList() {
        return null;
    }
}

BoardServiceImpl 클래스에 가장 중요한 부분은 @Service라는 어노테이션이다.

@Service는 계층 구조상 주로 비즈니스 영역을 담당하는 객체임을 표시하기 위해 사용한다.

작성된 어노테이션은 패키지를 읽어 들이는 동안 처리된다.

BoardServiceImple가 정상적으로 동작하기 위해서는 BoardMapper 객체가 필요하다.

이는 @AutoWired와 같이 직접 설정해 줄 수 있고, Setter를 이용해서 처리할 수도 있다.

Lombok을 이용한다면 아래와 같은 방식으로 만들수도 있다.

@Log4j
@Service
public class BoardServiceImpl implements BoardService {
    @Setter(onMethod = @Autowired)
    private BoardMapper mapper;
}

스프링 4.3부터는 단일 파라미터를 받는 생성자의 경우에는 필요한 파라미터를 자동으로 주입할 수 있다.

@AllArgsConstructor는 모든 파라미터를 이용하는 생성자를 만들기 때문에 실제 코드는 아래와 같이 BoardMapper를 주입받는 생성자가 만들어지게 된다.

스프링의 서비스 객체 설정

RootConfig.class

@Configuration
@ComponentScan(basePackages = "com.osk2090.service")
@MapperScan(basePackages = {"com.osk2090.mapper"})
public class RootConfig {
...

비즈니스 계층의 구현과 테스트

BoardMapper와 BoardService,BoardServiceImpl에 대한 구조 설정이 완료되었으므로

‘src/test/java’ 밑에 com.osk2090.service.BoardServiceTests 클래스를 작성해 테스트 작업을 진행한다.

INFO : com.osk2090.service.BoardServiceTests - com.osk2090.service.BoardServiceImpl@30c0ccff

등록 작업의 구현과 테스트

등록 작업은 BoardServiceImpl에서 파라미터로 전달되는 BaordVO 타입의 객체를 BoardMapper를 통해서 처리한다.

BoardServiceImpl.class

@Override
public void register(BoardVO board) {
    log.info("register....." + board);
    mapper.insertSelectKey(board);
}

BoardServiceImple는 void 타입으로 설계되었으므로 mapper.insertSelectKey()의 반환값인 int를 사용하지 않고 있지만.

필요하다면 예외 처리나 void 대신에 int 타입을 이용해서 사용할 수 있다.

mapper의 insertSelectKey()를 이용해서 나중에 생성된 게시물의 번호를 확인할 수 있다.

BoardServiceImpl.class

@Test
public void testRegister() {
    BoardVO board = new BoardVO();
    board.setTitle("새로 작성하는 글");
    board.setContent("새로 작성하는 내용");
    board.setWriter("newbie");

    service.register(board);
    log.info("생성된 게시물의 번호: " + board.getBno());
}
INFO : jdbc.sqltiming - insert into tbl_board (bno, title, content, writer) values (32, '새로 작성하는 글', '새로 작성하는 내용', 
'newbie') 
 {executed in 2 msec}
...
INFO : com.osk2090.service.BoardServiceTests - 생성된 게시물의 번호: 32

리스트 작업의 구현과 테스트

BoardServiceImple 클래스에서 현재 테이블에 저장된 모든 데이터를 가져오는 getList()는 아래와 같이 구현된다.

BoardServiceImpl.class

@Override
public List<BoardVO> getList() {
    log.info("getList");
    return mapper.getList();
}

테스트 코드

@Test
    public void testGetList() {
    service.getList().forEach(board -> log.info(board));
}
INFO : jdbc.resultsettable - 
|----|---------------------|----------------------|-------|----------------------|----------------------|
|bno |title                |content               |writer |regdate               |updatedate            |
|----|---------------------|----------------------|-------|----------------------|----------------------|
|1   |테스트 제목               |테스트 내용                |user00 |2021-07-04 16:24:09.0 |2021-07-04 16:24:09.0 |
|21  |새로 작성하는 글            |새로 작성하는 내용            |newbie |2021-07-05 16:33:16.0 |2021-07-05 16:33:16.0 |
|22  |새로 작성하는 글            |새로 작성하는 내용            |newbie |2021-07-05 16:36:17.0 |2021-07-05 16:36:17.0 |
|23  |수정된 제목               |수정된 내용                |user00 |2021-07-05 19:11:19.0 |2021-07-06 12:01:07.0 |
|24  |새로 작성하는 글 select key |새로 작성하는 내용 select key |newbie |2021-07-06 11:12:27.0 |2021-07-06 11:12:27.0 |
|25  |새로 작성하는 글            |새로 작성하는 내용            |newbie |2021-07-06 11:12:27.0 |2021-07-06 11:12:27.0 |
|26  |새로 작성하는 글 select key |새로 작성하는 내용 select key |newbie |2021-07-06 11:16:39.0 |2021-07-06 11:16:39.0 |
|27  |새로 작성하는 글            |새로 작성하는 내용            |newbie |2021-07-06 11:16:39.0 |2021-07-06 11:16:39.0 |
|29  |새로 작성하는 글            |새로 작성하는 내용            |newbie |2021-07-06 11:17:05.0 |2021-07-06 11:17:05.0 |
|30  |새로 작성하는 글 select key |새로 작성하는 내용 select key |newbie |2021-07-06 11:17:28.0 |2021-07-06 11:17:28.0 |
|31  |새로 작성하는 글            |새로 작성하는 내용            |newbie |2021-07-06 11:17:28.0 |2021-07-06 11:17:28.0 |
|32  |새로 작성하는 글            |새로 작성하는 내용            |newbie |2021-07-06 15:09:57.0 |2021-07-06 15:09:57.0 |
|----|---------------------|----------------------|-------|----------------------|----------------------|

조회 작업의 구현과 테스트

조회는 게시물의 번호가 파라미터이고 BoardVO의 인스턴스가 리턴이 된다.

BoardServiceImpl.class

@Override
public BoardVO get(Long bno) {
    log.info("get....." + bno);
    return mapper.read(bno);
}

테스트 코드

@Test
public void testGet() {
	log.info(service.get(32L));
}
INFO : com.osk2090.service.BoardServiceImpl - get.....32
...
|----|----------|-----------|-------|----------------------|----------------------|
|bno |title     |content    |writer |regdate               |updatedate            |
|----|----------|-----------|-------|----------------------|----------------------|
|32  |새로 작성하는  |새로 작성하는 내용 |newbie |2021-07-06 15:09:57.0 |2021-07-06 15:09:57.0 |
|----|----------|-----------|-------|----------------------|----------------------|

삭제/수정 구현과 테스트

삭제/수정은 메서드의 리턴 타입을 void로 설계할 수도 있지만 엄격하게 처리하기 위해서 Boolean 타입으로 처리한다.

BoardServiceImpl.class

@Override
public boolean modify(BoardVO board) {
    log.info("modify....." + board);
    return mapper.update(board) == 1;
}

@Override
public boolean remove(Long bno) {
    log.info("remove....." + bno);
    return mapper.delete(bno) == 1;
}

정상적으로 수정과 삭제가 이루어지면 1이라는 값이 반환되기 때문에 ‘==’ 연산자를 이용해서 true/false를 처리할 수 있다.

테스트 코드

@Test
public void testDelete() {
    //게시물 번호의 존재 여부를 확인하고 테스트할 것
    log.info("REMOVE RESULT: " + service.remove(32L));
}
    
@Test
public void testUpdate() {
    BoardVO board = service.get(32L);
    if (board == null) {
        return;
    }
    board.setTitle("제목 수정합니다.");
    log.info("MODIFY RESULT: " + service.modify(board));
}
INFO : com.osk2090.service.BoardServiceImpl - 
modify.....BoardVO(bno=32, title=제목 수정합니다., content=새로 작성하는 내용, writer=newbie, regdate=Tue Jul 06 15:09:57 KST 2021, updatedate=Tue Jul 06 15:09:57 KST 2021)
INFO : com.osk2090.service.BoardServiceTests - REMOVE RESULT: true

testDelete()의 경우에는 해당 게시물이 존재할 때는 true를 반환하는 것을 확인할 수 있다.

testUpdate()의 경우에는 특정한 게시물을 먼저 조회하고 title 값을 수정한 후 이를 업데이트 한다.