Web/Spring

Spring boot - 게시판 만들기 3 ( Mapper 구현 및 테스트)

dev_sr 2020. 10. 12. 21:29

1. DTO 만들기

 

패키지를 이렇게 구성해주고 도메인에 BoardDTO를 추가해준다.

 

domain package

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

Entity 클래스, Core 클래스라고도 부른다.

@Entity, @Column, @Id 등을 이용하기도 한다.

 

 

VO ( Value Object )

데이터 그 자체로 의미를 담고 있는 객체

특정한 비즈니스 값을 담고 있다

데이터 베이스의 필드들을 속성으로 구성하고 Getter와 Setter 가 있음

DTO와 동일한 개념이지만

⭐Read-Only, 불변성

 

 

DTO ( Data Transfer Object )

전송되는 데이터를 담은 컨테이너 객체

Layer간 통신 용도로 오가는 객체 

비지니스 로직까지 담아서 작업을 처리하는 용도로 사용하기도 함 

( 로직을 아예 담지 않는다고도 나온다.

계속 검색해보니깐 사람마다 정의하거나 사용하는 방법이 아예 다른 듯.. )

ex) DB 데이터 ---DTO----> Service or Controller

 

 

DAO ( Data Access Object )

실제로 DB에 접근하는 객체

Service와 DB를 연결하는 고리

 

 

 

Lombok이 설치되어 있다면 이렇게 코드를 작성해줌

아니라면 Generate Getters and Setters 

package com.board2.domain;

import java.time.LocalDateTime;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class BoardDTO {

	private Long idx;			//글번호(PK)
	private String title;		//제목
	private String content;		//내용
	private String writer;		//작성자
	private int viewCnt;		//조회수
	private String noticeYn;	//공지 여부
	private String secretYn;	//비밀글 여부
	private String deleteYn;	//삭제 여부
	private LocalDateTime insertTime;	//등록일
	private LocalDateTime updateTime;	//수정일
	private LocalDateTime deleteTime;	//삭제일
}

 

Getter, Setter를 Import 했을 때 Outline에 이렇게 Get, Set 메서드가 나와야함

속성만 나온다면 매칭이 되지 않기 때문에 Lombok을 직접 설치하고 STS4를 재실행 해줘야함

 

 

gradle이 버전 5이상이라면 lombok이 제대로 적용안되는 이슈도 있기 때문에 

gradle 디펜던시 lombok부분에 lombok:1.18.10 이렇게 추가해서 써주는 게 좋음

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.3'
	implementation 'org.projectlombok:lombok'
	compileOnly 'org.projectlombok:lombok:1.18.10' //gradle 5.0 이상이면 1.18.10을 추가해줘야 제대로 적용
	testCompileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'mysql:mysql-connector-java'
	annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
	annotationProcessor 'org.projectlombok:lombok:1.18.10' //1.18.10을 추가해줘야 제대로 적용
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
	testAnnotationProcessor 'org.projectlombok:lombok'
}

 

 

 

 

2. Mapper 인터페이스 만들기

 

package com.board2.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

import com.board2.domain.BoardDTO;

@Mapper
public interface BoardMapper {

	public int insertBoard(BoardDTO params);
	public BoardDTO selectBoardDetail(Long idx);
	public int updateBoard(BoardDTO params);
	public int deleteBoard(Long idx);
	public List<BoardDTO> selectBoardList();
	public int selectBoardTotalCount();
	public boolean cntPlus(Long idx);
}

 

@Mapper 

인터페이스를 마이바티스용 mapper로 만들어줌

XML Mapper에서 이름과 일치하는 SQL문을 찾아서 실행해준다

 

insertBoard 

게시글 등록

INSERT 쿼리를 호출하는 메서드 

params 에 게시글 정보가 담김

 

selectBoardDetail

게시글 하나 조회

SELECT 쿼리를 호출하는 메서드

idx 에 게시글번호(pk)를 받고 WHERE 로 조회

 

updateBoard

게시글 수정

UPDATE  쿼리 호출

params에 게시글 정보가 담김

 

deleteBoard

삭제여부를 Y로 만들어서 노출되지 않게 하는 역할 (DB에서 안지움)

idx를 파라미터로 전달받고 WHERE로 검색한 후 Y값으로 변경

 

selectBoardList

게시글 목록 조회

SELECT 쿼리 호출

 

selectBoardTotalCount

삭제가 안된 글의 개수를 조회

나중에 페이지 처리용

 

cntPlus 

조회수 카운터 용

 

 

결과값을 제대로 전달받기 위해 int로 리턴받음

 

 

 

3. XML Mapper 만들기

 

BoardMapper와 SQL문을 연결해줄거임

 

마이바티스 플러그인 설치하면 나오는 MyBatis XML Mapper 추가

( parameterType 같은 거 컨트롤 + 스페이스 자동완성 가능 )

밑에 source를 클릭해서 코드를 작성해줌

 

<?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.board2.mapper.BoardMapper">

	<sql id="boardColumns">
		idx
		,title
		,content
		,writer
		,view_cnt
		,notice_yn
		,secret_yn
		,delete_yn
		,insert_time
		,update_time
		,delete_time
	</sql>

	<insert id="insertBoard" parameterType="BoardDTO">
		INSERT INTO tb_board(
		<include refid="boardColumns" />
		) VALUES (
		 #{idx}
		,#{title}
		,#{content}
		,#{writer}
		,0
		,IFNULL(#{noticeYn}, 'N')
		,IFNULL(#{secretYn}, 'N')
		,'N'
		,NOW()
		,NULL
		,NULL
		)
	</insert>
	
	<select id="selectBoardDetail" parameterType="long" resultType="BoardDTO">
		SELECT 
			<include refid="boardColumns"/>
		FROM
			tb_board
		WHERE
			delete_yn = 'N'
		AND
			idx = #{idx}		
	</select>
	
	<update id="updateBoard" parameterType="BoardDTO">
		UPDATE tb_board
		SET
			 update_time=NOW()
			,title = #{title}
			,content = #{content}
			,writer = #{writer}
			,notice_yn = IFNULL(#{noticeYn}, 'N')
			,secret_yn = IFNULL(#{secretYn}, 'N')
		WHERE
			idx=#{idx}	
	</update>
	
	<update id="deleteBoard" parameterType="long">
		UPDATE tb_board
		SET 
			 delete_yn = 'Y'
			,delete_time = NOW()
		WHERE
			idx = #{idx}
	</update>

	<select id="selectBoardList" parameterType="BoardDTO" resultType="BoardDTO">
		SELECT
			<include refid="boardColumns"/>
		FROM
			tb_board
		WHERE
			delete_yn ='N'
		ORDER BY
			notice_yn ASC,
			idx DESC,
			insert_time DESC
	</select>
	
	<select id ="selectBoardTotalCount" parameterType="BoardDTO" resultType="int">
		SELECT
			COUNT(*)
		FROM
			tb_board
		WHERE
			delete_yn='N'		
	</select>
	
	<update id="cntPlus" parameterType="long">
		UPDATE tb_board
		SET
			view_cnt = view_cnt+1
		WHERE
			idx = #{idx}	
	</update>

</mapper>

 

인터페이스 경로 지정과 메서드 이름들을 잘 적었다면

컨트롤을 누르고 메서드 이름에 마우스를 올리면 클릭할 수 있게 바뀌는데

클릭하면 인터페이스의 해당 메서드로 이동한다. 

 

<mapper>

namespace : 인터페이스가 위치한 경로 지정

 

<sql>

반복되는 쿼리문을 BoardColumns 라는 이름으로 저장해놓고 

다른 쿼리문에서 필요할 때 <include refid="BoardColumns> 으로 참조해서 사용한다

 

#{ }

전달받은 파라미터를 표현하는 방법

 

 

 

 

4. 마이바티스 SELECT 컬럼과 DTO 속성 매핑

 

XML 

notice_yn   ( ' _ ' 를 사용)

=> 🐍스네이크 케이스🐍 사용

 

Java 

noticeYn ( 소문자 + 대문자 )

=> 🐪카멜 케이스🐪 사용

 

마이바티스의 map-upderscore-to-comel-case 설정을 사용하면

자동으로 매핑 처리가 된다고 함

 

application.properties 에 설정 추가해줌

 

*사용할 스키마 이름을 제대로 써줘야함

이번에 사용할 스키마는 board2이므로

localhost:3306/board2? 로 수정해줌

#HikariCP 데이터 소스(DataSource)
spring.datasource.hikari.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.hikari.jdbc-url=jdbc:mysql://localhost:3306/board2?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.hikari.username=username
spring.datasource.hikari.password=password
spring.datasource.hikari.connection-test-query=SELECT NOW() FROM dual

#MyBatis snake case to camel case
mybatis.configuration.map-underscore-to-camel-case=true

 

 

 

 

5. DBConfiguration 수정

 

application.properties에 설정이 추가 되었으니

Configuration에서 해당 설정을 처리할 Bean을 만들어줘야함

 

package com.board2.configuration;

import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

@Configuration
@PropertySource("classpath:/application.properties")
public class DBConfiguration {

	@Autowired
	private ApplicationContext applicationContext;
	
	@Bean
	@ConfigurationProperties(prefix="spring.datasource.hikari")
	public HikariConfig hikariConfig() {
		return new HikariConfig();
	}
	
	@Bean
	public DataSource dataSource() {
		return new HikariDataSource(hikariConfig());
	}
	
	@Bean
	public SqlSessionFactory sqlSessionFactory() throws Exception{
		SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
		factoryBean.setDataSource(dataSource());
		
		//추가
		factoryBean.setMapperLocations(applicationContext.getResources
				("classpath:/mappers/**/*Mapper.xml"));
		factoryBean.setTypeAliasesPackage("com.board2.domain");
		factoryBean.setConfiguration(mybatisConfg());
		
		return factoryBean.getObject();
	}
	
	@Bean
	public SqlSessionTemplate sqlSession() throws Exception{
		return new SqlSessionTemplate(sqlSessionFactory());
	}
	
	//추가
	@Bean
	@ConfigurationProperties(prefix="mybatis.configuration")
	public org.apache.ibatis.session.Configuration mybatisConfg(){
		return new org.apache.ibatis.session.Configuration();
	}
	
}

 

setMapperLocation

getResource의 인자로 지정된

classpath:/mapper/**/*Mapper.xml  이런 패턴에 포함되는 XML Mapper를 인식함

 

*은 전체를 의미하는 애스터리스크라고 함

 

 

setTypeAliasesPackage

BoardMapper XML 에서 parameterType과 resultType을 사용하기 위해선 

클래스의 전체 패키지 경로가 필요하다고 함

여기서 com.board2.domain 로 경로를 작성해주고 

XML에서는 BoardDTO 와 같이 이름만 작성해줘도 됨 

 

 

SetConfiguration

mybatisConfg에서 추가된 관련된 Bean을 설정 파일로 지정함

 

 

mybatisConfg

prefix 설정으로 application.properties에서  mybatis.configuration으로 시작하는

모든 설정을 읽어들여서 Bean으로 등록함

 

 

 

 

6. 테스트

 

Junit에서 테스트 해볼 코드를 작성해줌

여기서 실패하면 경로를 잘못 작성했거나 메소드 이름에 오타가 났을 경우인데

Junit 창 로그를 쭉 읽어보면 어디서 틀렸는지 단서가 나옴

 

1) DB에 등록하기

package com.board2;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.board2.domain.BoardDTO;
import com.board2.mapper.BoardMapper;

@SpringBootTest
public class MapperTest {

	@Autowired
	private BoardMapper boardMapper;
	
	@Test
	public void testInsert() {
		BoardDTO params = new BoardDTO();
		params.setTitle("1번 제목");
		params.setContent("1번 내용");
		params.setWriter("테스터");
		
		int result = boardMapper.insertBoard(params);
		System.out.println("result=============> "+result);
	}
}

 

 

성공

 

 

 

2) 게시글 하나 조회하기

 

 

	@Test
	public void testSelectDetail() {
		BoardDTO board = boardMapper.selectBoardDetail((long) 1);
		try {
				String boardJson = new ObjectMapper().writeValueAsString(board);
				
				System.out.println("==============================");
				System.out.println(boardJson);
				System.out.println("==============================");
				
		} catch (JsonProcessingException e) {
			e.printStackTrace();
		}
	}

 

너무 길어서 여기까지만 캡쳐

 

 

board에 저장된 게시글 정보를 Jackson 라이브러리를 이용해서 Json으로 변경 후 콘솔에 출력함

 

 

 

 

3) 수정하기

 

	@Test
	public void testUpdate() {
		BoardDTO params = new BoardDTO();
		params.setTitle("2번 게시물");
		params.setContent("2번 게시글 내용");
		params.setWriter("2번 테스터");
		params.setIdx((long) 2);
		
		int result = boardMapper.updateBoard(params);
		System.out.println("수정==========>"+result);
	}

 

 

4) 삭제하기

 

실제로 삭제하는 것이 아니라 삭제를 Y값으로 수정해준다

	@Test
	public void testDelete() {
		int result = boardMapper.deleteBoard((long) 1);
		System.out.println("삭제==========>"+result);
	}

 

 

5) 여러개 추가 후 목록 조회하기

 

삭제되지 않은 글만 조회하기

 

일단 글 48개 추가

	@Test
	public void testAdd() {
		for(int i=3; i<=50; i++) {
			BoardDTO params = new BoardDTO();
			params.setTitle(i+"번 제목");
			params.setContent(i+"번 내용");
			params.setWriter(i+"번 작성자");
			boardMapper.insertBoard(params);
		}
	}

 

 

목록 조회하기

	@Test
	public void testSelectAll() {
		int boardTotalCount = boardMapper.selectBoardTotalCount();
		if(boardTotalCount>0) {
			List<BoardDTO> boardList = boardMapper.selectBoardList();
			if(!boardList.isEmpty()) {	//리스트가 비었나?
				for(BoardDTO board:boardList) {	//foreach문
					System.out.println("==============================");
					System.out.println(board.getTitle());
					System.out.println(board.getContent());
					System.out.println(board.getWriter());
					System.out.println("==============================");
				}
			}
		}
	}

1번은 삭제 테스트 이후 삭제여부가 Y이므로 나오지 않음

 

 

 

VO, DTO

 

VO vs DTO

VO vs DTO VO(Value Object) -       데이터 그 자체로 의미 있는 것을 담고 있는 객체이다. -       DTO와 동일한 개념이나 차이점은 Read–Only 속성 객체이다. -       간단한 독립체( Entit..

ijbgo.tistory.com

DAO, DTO, Entity, Controller, Service등 용어 정리

 

[DAO] DAO, DTO, Entity Class의 차이 - Heee's Development Blog

Step by step goes a long way.

gmlwjd9405.github.io

 

sql, include 반복되는 쿼리 묶기

 

MyBatis] 반복되는 쿼리 묶기 Sql , include 태그

같은 쿼리를 다른 쿼리에서 일부분 사용한다거나  그렇게 반복이 될 때 다음과 같이 사용하면 좋다  ... .... SELECT * FROM TABLE1 WHRE name = #{value}

marobiana.tistory.com

 

과정 참고한 블로그

 

스프링 부트(Spring Boot) - 게시판 CRUD 처리하기 [개발을 시작해봐요!]

이전 글에서는 스프링 부트와 MySQL 데이터베이스의 연동에 대해 알아보았습니다. 이번 글부터는 약속대로 게시판을 구현해 보도록 하겠습니다. 대학에서 동아리원들과 처음으로 진행해 본 프로

congsong.tistory.com