본문 바로가기
백엔드(Back-End)/Spring Boot

[sts4-Spring Boot] 03. 데이터베이스 CRUD

by 기딩 2023. 11. 28.
728x90

전 글에 이어서

2023.11.09 - [백엔드(Back-End)/DB] - [IoT 플랫폼] 02. Java and Spring Project - STS에서 db연동하기

 

[IoT 플랫폼] 02. Java and Spring Project - STS에서 db연동하기

sts (spring tool suite4)를 사용하여 mysql 데이터베이스를 연동해보자 이를 위해 먼저 프로젝트를 생성한다. new 프로젝트 생성 게시판 만들 거라서 이름 board로 설정 type은 라이브러리 관리해줌. 각 문

silvow94.tistory.com

 

이번에는

데이터베이스 스키마의 테이블을 create, read, update, delete 해보자.

 

그러기 위해 MySql에서 해당 스키마를 활성화한다.

방법1)  스키마 더블클릭하여 볼드체 확인

방법2) 쿼리에 use 스키마; 작성하여 실행

 

쿼리에서 create문으로 테이블을 생성하고, 

create table board (
	idx INT NOT NULL auto_increment,
	title VARCHAR(100) NOT NULL,
	content VARCHAR(3000) NOT NULL,
    writer VARCHAR(20) NOT NULL,
    view_cnt INT NOT NULL DEFAULT 0,
    notice_yn ENUM('Y', 'N') NOT NULL DEFAULT 'N',
    secret_yn ENUM('Y', 'N') NOT NULL DEFAULT 'N',
    delete_yn ENUM('Y', 'N') NOT NULL DEFAULT 'N',
    insert_time DATETIME NOT NULL DEFAULT NOW(),
    update_time DATETIME NULL,
    delete_time DATETIME NULL,
    PRIMARY KEY(idx)
);

쿼리에 확인 겸 select문 등으로 확인한다.

select * from 테이블;

혹은 show tables; 혹은 desc 테이블;

 

------------------------속성 설명----------------------------

인덱스는 각 행을 구분하기 위해 행이 추가될 때마다 자동으로 1씩 증가하도록 한다. auto_increment

디폴트가 널 값도 허용하는 NULL이지만, 생성은 한 번 하고 거의 건들지 않기 때문에

NULL이라도 명시를 해주는 것이 좋다.

 

ENUM은 해당 열의 도메인을 지정하여 유효성을 제한한다.

ENUM('Y', 'N') 해당 속성 값이 Y이거나 N 두 가지 값 중 하나만 허용된다.

또한 sql의 delete문으로 해당 행을 삭제할 수도 있지만, 실제 스키마에서 행을 날리는 것은 위험이 크다.

 

(고객이 다시 요구할 수도 있기 때문 등) 따라서 실제로 지우지 않고 delete_여부를 Y로 바꾸고

나중에 조회를 할 때는 where조건을 통해 삭제되지 않은 N들만 읽어오도록 하자.

 

update와 delete time은 수정이나 삭제를 하지 않을 수도 있으므로 NULL을 허용한다.


sts로 돌아와서 메인의 com.board에 

controller, domain, mapper, service 패키지를 만든다.

 

도메인 패키지에 DTO 클래스를 생성하고 private 속성들과 getter, setter를 생성한다.

아래의 코드를 직접 작성하기 보단 해당 프로젝트에서 오른쪽 클릭>Sources>Generate Getter, Setter...

ToString도 동일한 방법

package com.board.domain;

import java.time.LocalDateTime;

//DTO(데이터를 넣고,쓰고,수정할 때) 나 DO(데이터 쓰진 않을 때)
public class BoardDTO {
	private Long idx;
	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 써도 되지만 문제가 생겨서 일단 기본적인 방법으로
	public Long getIdx() {
		return idx;
	}
	public void setIdx(Long idx) {
		this.idx = idx;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
	public String getWriter() {
		return writer;
	}
	public void setWriter(String writer) {
		this.writer = writer;
	}
	public int getViewCnt() {
		return viewCnt;
	}
	public void setViewCnt(int viewCnt) {
		this.viewCnt = viewCnt;
	}
	public String getNoticeYn() {
		return noticeYn;
	}
	public void setNoticeYn(String noticeYn) {
		this.noticeYn = noticeYn;
	}
	public String getSecretYn() {
		return secretYn;
	}
	public void setSecretYn(String secretYn) {
		this.secretYn = secretYn;
	}
	public String getDeleteYn() {
		return deleteYn;
	}
	public void setDeleteYn(String deleteYn) {
		this.deleteYn = deleteYn;
	}
	public LocalDateTime getInsertTime() {
		return insertTime;
	}
	public void setInsertTime(LocalDateTime insertTime) {
		this.insertTime = insertTime;
	}
	public LocalDateTime getUpdateTime() {
		return updateTime;
	}
	public void setUpdateTime(LocalDateTime updateTime) {
		this.updateTime = updateTime;
	}
	public LocalDateTime getDeleteTime() {
		return deleteTime;
	}
	public void setDeleteTime(LocalDateTime deleteTime) {
		this.deleteTime = deleteTime;
	}
	
	// (생성자 대신) 데이터가 잘 안 움직여질 때 도와줌
	// 객체를 출력하면 toString의 결과가 쭉 나옴
	@Override
	public String toString() {
		return "BoardDTO [idx=" + idx + ", title=" + title + ", content=" + content + ", writer=" + writer
				+ ", viewCnt=" + viewCnt + ", noticeYn=" + noticeYn + ", secretYn=" + secretYn + ", deleteYn="
				+ deleteYn + ", insertTime=" + insertTime + ", updateTime=" + updateTime + ", deleteTime=" + deleteTime
				+ "]";
	}
		
}

 

DTO = 데이터 전송 객체 (Data Transfer Object)

: 데이터를 객체화해서 전달하는 데 사용

service에서 데이터베이스나 외부 API로 데이터를 전달하고

데이터를 객체로 표현하여, 구조를 명확하게 하고 유지보수를 쉽게 만든다.

 

DTO는 서비스나 컨트롤러와 같은 계층에서 사용 

-> 비즈니스 로직, 뷰, 외부 시스템 간의 의존성 줄임

 


mapper 패키지에서 클래스를 만들어

쿼리에서 호출할 메서드를 정의한다.

package com.board.mapper;

import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.board.domain.BoardDTO;

@Mapper
public interface BoardMapper {
	// int를 boolean으로 해도 됨
		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();
}

 

@Mapper는 MyBatis에서 Mapper 인터페이스를 정의할 때 사용하는 어노테이션(@)으로,

데이터베이스와 통신하기 위해  Mapper Xml 파일과 연결 -> SQL문을 찾아 실행

 

조회하는 건 특별한 행위 없이 보여주는 것이므로 BoardDTO 객체 혹은 List를 반환하였고

그 외의 행위들은, 데이터 작업의 성공/실패 여부를 위해 int를 반환하였다.

어차피 성공/실패 즉, 1/0 이므로 Boolean으로 정의해도 된다.

 


Mapper 인터페이스를 만들었으니, XML Mapper에서 SQL 문을 적어야 한다.

src/메인/자바가 아닌! src/메인/리소스 폴더에서 

mappers의 패키지를 만들어 MyBatis XML Mapper 를 선택하여 xml 파일을 만들자.

(안 뜬다면 Help > Marketplace > MyBatis 검색 > Generator 말고 그냥 MyBatis 1.2.5 > Install > 후에 동의여부 나오는 거 다 All select해서 동의)

 

xml 파일에 위에서 만든 Mapper 인터페이스에 해당하는 SQL 문들을 작성한다.

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.board.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 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
			board
		WHERE
			delete_yn = 'N'
		AND
			idx = #{idx}
	</select>
	
	<update id ="updateBoard" parameterType="BoardDTO">
		UPDATE 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 board
		SET
			  delete_yn = 'Y'
			, delete_time = NOW()
		WHERE
			idx = #{idx}
	</update>
	
	<select id="selectBoardList" resultType="BoardDTO">
		SELECT
			<include refid="boardColumns" />
		FROM
			board
		WHERE
			delete_yn = 'N'
		ORDER BY
			notice_yn ASC,
			idx DESC,
			insert_time DESC
	</select>
	
	<select id="selectBoardTotalCount" resultType="int">
		SELECT
			COUNT(*)
		FROM
			board
		WHERE
			delete_yn = 'N'
	</select>

</mapper>

데이터베이스 연산에 대한 SQL 쿼리, 매핑, 파라미터 전달 등을 정의한다.

 

매퍼 인터페이스의 메서드에 대해 sql문을 적는다.

//  Mapper 인터페이스의 메서드 매핑을 위해 지정하는 속성

<mapper namespace="매퍼패키지.매퍼인터페이스">

 

// SQL 정의 위한 태그

<sql id=테이블명Colums>

   컬럼1,컬럼2,...

</sql

 

<sql문 id=인터페이스메서드1 매개변수타입=" " 반환타입=" ">

   SQL문~

</sql문>

 

#{} : 파라미터 표현

 

ex) INSERT INTO board (title) VALUES (#{title})의 title처럼 컬럼을 넣어야 한다면, <include>를 사용하여 테이블 속성을 표현한다.

<include> : 태그에 정의한 Columns SQL에 사용되는 속성


스프링 부트 애플리케이션의 설정을 정의하는 application.properties 파일에 

Mybatis 매핑을 위해 코드를 추가한다.

 

나의 소중한 비밀번호는 가렸다ㅎㅎ

데이터베이스(mysql)에서 언더스코어(_)로 구분된 속성명을 자바에서는 카멜케이스표기법(ex.appleApple)으로 변환

 


DBConfigutation도 수정한다.

MyBatis와 매핑을 위해 밑에서 3번째와 1번째의 메서드처럼 수정하자.

package com.board.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.getObject();
		factoryBean.setMapperLocations(applicationContext.getResources("classpath:/mappers/**/*Mapper.xml"));
		factoryBean.setTypeAliasesPackage("com.board.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();
	}
	
}

 

 

MyBatis에서 SQL을 실행할 때 사용하는 주요 객체인 sqlSessionFactory 는

데이터 연결, sQL매퍼, MyBatis 설정 등을 구성

- dataSource() : 히카리데이터소스 객체 생성 -> 데베 연결 관리 / 빈은 MyBatis의 SqlSessionFactory에 주입

- mapperLocations() : SQL 매퍼(XML파일)의 위치 지정

- typeAliasesPackage() : 도메인 객체의 패키지 위치 지정 -> MyBatis가 매핑해야 하는 클래스 위치 알려줌

- myBatisConfig() : MyBatis의 추가적인 설정 구성

 

 

다음은 오늘 작성한 CRUD 메서드, sql를 Test해보겠다.

728x90