Maven Project, Gradle Project 두 가지가 있는데, 이 두개 중 하나의 빌드관리 도구를 선택하면 됨
요즘에는 대부분 Gradle을 이용하여 개발하는 추세 그 이유는 스크립트의 가독성이 좋고, 빌드와 테스트 실행결과가 gradle이 더 빠르다. gradle은 캐시를 사용하여 이미 업데이트된 것에 대해서는 작업이 실행되지 않아 빌드시간이 단축된다. (의존성이 늘어날수록 성능, 속도 등에 장점이 있어 Gradle을 선호한다.)
Maven과 Gradle의 역할은 빌드관리 도구이다.
어플리케이션을 개발하다보면 필요한 라이브러리들을 다운받아 사용하게 되다. 이때 이 빌드관리 도구를 통해서 각종 라이브러리들을 버전, 종속성 등을 명시하여 사용할 수 있고, 라이브러리들의 의존관계들을 모두 가져와 의존관계를 관리해준다.
2.language
Java , Kotlin, Groovy
스프링으로 개발을 할 때 주로 Java를 사용하고, 요즘에는 Kotlin을 이용해서 개발한다. Groovy는 JVM에서 실행되는 스크립트 언어이고, Java와 문법이 비슷한 언어
* 스크립트 언어: 컴파일 하지 않고도 실행시킬 수 있는 언어, 자세한 내용은 이곳을 참고
3.SpringBoot
SpringBoot의 버전을 선택할 수 있다.
버전 뒤에 보면 SNAPSHOT은 개발중인 버전, M1은 정식으로 릴리즈 되지 않은 버전을 의미하는 것이다. 즉, 둘 다 미정식버전이라는 의미이다.
그래서 사용할 때는 버전 숫자 뒤에 영어가 붙어있지 않는 것이 정식버전이기 때문에 이 중에서 하고자 하는 버전을 선택하거나 제일 최신버전을 선택하면 된다.
4. Project Metadata
Group
보통 기업의 도메인명을 기입한다. 개인 프로젝트라면 블로그도메인이나 자유롭게 만들면 된다.
(ex: MAIAVATAR )
Artifact
빌드된 결과물 이름
Name
프로젝트명
Description
프로젝트 관련 설명을 간단히 작성
Package name
패키지명 ( ex: com.jin2rang.hello)
Packaging
배포 형태를 의미한다.
jar,war 모두 java -jar를 이용해 생성된 압축파일로 애플리케이션을 배포하고 동작할 수 있도록 프로젝트와 관련된 파일들을 패키징 한 것이다.
- jar Java 어플리케이션이 동작할 수 있도록 프로젝트를 압축한 파일로, class와 라이브러리파일이 포함되어 있다. 그리고 JRE만 있어도 실행할 수 있다. 서버에서 java -jar hello.jar하면 실행가능하다.
- war Servlet, Jsp 컨테이너를 배치할 수 있는 웹 어플리케이션을 압축한 파일이다. 웹 프로젝트에는 jsp, html , javasscript등 웹과 관련된것들이 포함되어 있고, 웹서버나 was가 필요하다.
쉽게 생각하면웹화면이 필요한 어플리케이션은 war로 패키징하고 , api서버로 사용하는 것과 같이 사용한다면 java 프로젝트로만 동작하면 되기 때문에 jar로 하면 된다.
CRUD는 각각 Create(생성), Read(조회), Update(수정), Delete(삭제)를 의미합니다. 쇼핑몰을 예로 들어보겠습니다. 관리자는 상품 정보를 "등록(C)/수정(U)/조회(R)/삭제(D)" 할 수 있어야 하며, 사용자는 상품 정보를 "조회(R)" 또는 상품을 "구매(C - 구매 정보 / U - 재고)" 할 수 있어야 합니다. 즉, 시스템의 특성과 기능에 따라 CRUD를 적절히 활용해주면 됩니다. *출처: [Let's develop:티스토리]
INTRO
개발환경은 아래와 같이 진행되었음
3.xxx버전 이상부터는 Java 버전을 17이상으로 해줘야 충돌이 나지 않음
환경변수 설정과 더불어 JDK를 여러 버전을 사용하게 되면 이 에러에 직면하는 경우가 생김
그럴때는 Settings에 들어가서 JDK 버전을 맞춰주면 된다.
1. 게시판 테이블 만들기
1-1) 테이블 생성 스크립트 생성하기
CREATE TABLE `tb_post` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'PK',
`title` varchar(100) NOT NULL COMMENT '제목',
`content` varchar(3000) NOT NULL COMMENT '내용',
`writer` varchar(20) NOT NULL COMMENT '작성자',
`view_cnt` int(11) NOT NULL COMMENT '조회 수',
`notice_yn` tinyint(1) NOT NULL COMMENT '공지글 여부',
`delete_yn` tinyint(1) NOT NULL COMMENT '삭제 여부',
`created_date` datetime NOT NULL DEFAULT current_timestamp() COMMENT '생성일시',
`modified_date` datetime DEFAULT NULL COMMENT '최종 수정일시',
PRIMARY KEY (`id`)
) COMMENT '게시글';
위의 SQL 문을 수행해주면 테이블이 만들어진다.
만약 만들어지지 않는다면, CREATE DATABASE "데이터베이스명"
1-2) 테이블 생성 결과 확인
칼럽 별 타입과 여러 정보들을 확인할 수 있다.
2. 게시글 요청(Request) 클래스 생성하기
과거에는 대체로 VO 또는 DTO를 붙여 공통으로 사용하는 객체를 정의하였으나, 요청(REQUEST)과 응답(RESPONSE) 클래스를 각각 생성하여 사용하면 여러 이점이 많기에 이번 기회에 사용해보도록 한다.
2-1) 요청 클래스 생성 및 소스 코드 작성하기
우선, 게시글 생성(INSERT)과 수정(UPDATE)에 사용할 요청(REQUEST)클래스를 생성한다.
package com.study.domain.post;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class PostRequest {
private Long id; // PK
private String title; // 제목
private String content; // 내용
private String writer; // 작성자
private Boolean noticeYn; // 공지글 여부
}
1) @Getter / @Setter / @Data
클래스 레벨에 선언된 두 어노테이션은 롬복(Lombok) 라이브러리에서 제공해 주는 기능으로, 클래스에 선언된 모든 멤버 변수에 대한 getter와 setter을 생성해주는 역할을 한다. @DATA를 사용하면 두개 모두의 기능을 사용할 수 있다.
2) 클래스의 멤버 변수가 왜 칼럼의 갯수보다 적은가?
사용자가 게시판에 글을 작성할 때 입력하는 필드는 제목(title), 내용(content), 작성자(writer), 공지글 여부(notice_yn) 네 가지 정도
ID의 경우에는 auto_increment 속성에 의해 자동으로 1씩 증가되므로 게시글 생성(Insert) 시점에는 필요가 없으나, 게시글 수정(UPDATE) 할 때 SQL 쿼리의 WHERE 조건으로 id(PK)를 사용하기 때문에 선언해줌
조회 수(view_cnt)와 삭제 여부(delete_yn)는 무조건 0으로 생성되어야 하며, 생성일시(created_date)는 현재 시간으로, 수정일시(modified_date)는 NULL로 설정해 주면 된다. 삭제 여부(delete_yn) 칼럼의 용도는 뒤에서 설명예정
3. 게시글 응답(Response) 클래스 생성하기
REQUEST와 동일한 경로에 만들어주면 된다.
package com.study.domain.post;
import lombok.Getter;
import java.time.LocalDateTime;
@Getter
public class PostResponse {
private Long id; // PK
private String title; // 제목
private String content; // 내용
private String writer; // 작성자
private int viewCnt; // 조회 수
private Boolean noticeYn; // 공지글 여부
private Boolean deleteYn; // 삭제 여부
private LocalDateTime createdDate; // 생성일시
private LocalDateTime modifiedDate; // 최종 수정일시
}
4. Mapper 인터페이스 생성하기
MyBatis는 Mapper을 통해서 데이터 베이스와 통신함
기존에는 DAO Interface와 DAO implements를 통해서 데이터베이스와 통신하도록 설계했었음.
MyBatis는 Mapper 와 XML Mapper(실제로 DB에 접근해서 호출할 SQL 쿼리를 작성(선언)하는 파일)를 통해 DB와 통신 한다.
package com.study.domain.post;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface PostMapper {
/**
* 게시글 저장
* @param params - 게시글 정보
*/
void save(PostRequest params);
/**
* 게시글 상세정보 조회
* @param id - PK
* @return 게시글 상세정보
*/
PostResponse findById(Long id);
/**
* 게시글 수정
* @param params - 게시글 정보
*/
void update(PostRequest params);
/**
* 게시글 삭제
* @param id - PK
*/
void deleteById(Long id);
/**
* 게시글 리스트 조회
* @return 게시글 리스트
*/
List<PostResponse> findAll();
/**
* 게시글 수 카운팅
* @return 게시글 수
*/
int count();
}
1) @Mapper
XML Mapper에 SQL 쿼리를 선언해 두고 Mapper를 통해 SQL 쿼리를 호출
Mapper는 XML Mapper에 선언된 SQL 중에서 메서드명과 동일한 id를 가진 SQL 쿼리를 찾아 실행.
예를 들어, 메서드명이 "savePost( )"라고 가정했을 때 SQL id는 "savePost"가 되어야 한다
여기서 꼭 기억해 주셔야 할 게 있는데요. Mapper에는 @Mapper 어노테이션을 필수적으로 선언해 주어야 함
Mapper와 XML Mapper는 XML Mapper의 namespace라는 속성을 통해 연결된다는 점
2) save( )
게시글을 생성하는INSERT 쿼리를 호출
파라미터로 전달받는 params는 요청(PostRequest) 클래스의 객체이며, params에는 저장할 게시글 정보가 담김
3) findById( )
특정 게시글을 조회하는 SELECT 쿼리를 호출
파라미터로 id(PK)를 전달받아 SQL 쿼리의 WHERE 조건으로 사용
쿼리가 실행되면 메서드의 리턴 타입인 응답(PostResponse) 클래스 객체의 각 멤버 변수에 결괏값이 매핑(바인딩)
4) update( )
게시글 정보를 수정하는 UPDATE 쿼리를 호출합니다. save( )와 마찬가지로 요청(PostRequest) 클래스의 객체를 파라미터로 전달받으며, params에는 수정할 게시글 정보가 담기게 된다.
save( )와 차이가 있다면, UPDATE 쿼리의 WHERE 조건으로 사용되는 id(PK)에도 값이 담긴다는 점.
5) deleteById( )
게시글을 삭제 처리하는 UPDATE 쿼리를 호출
findById( )와 마찬가지로 id(PK)를 파라미터로 전달받아 SQL 쿼리의 WHERE 조건으로 사용
SQL 쿼리가 실행되면 삭제 여부(delete_yn) 칼럼의 상태 값을 0(false)에서 1(true)로 업데이트
삭제 여부(delete_yn)는 칼럼의 상태 값을 기준으로 삭제된 데이터(1)인지, 삭제되지 않은 데이터(0)인지 구분해 주는 역할을 합니다. 사용자에게 데이터를 보여줄 땐 삭제 여부가 0(false)인 데이터만 노출하게 된다.
실무에서는 데이터가 DELETE(물리적인 삭제) 되어버리면 리스크(손실)가 크기 때문에 논리적인 삭제 방식을 이용
물리적인 삭제를 하지 않는이유
6) findAll( )
게시글 목록을 조회하는 SELECT 쿼리를 호출합니다. findById( )는 id(PK)를 기준으로 하나의 게시글을 조회한다면, 해당 메서드는 여러 개의 게시글(PostResponse)을 리스트(List)에 담아 리턴해주는 역할.
7) count( )
전체 게시글 수를 조회하는 SELECT 쿼리를 호출합니다. 당장은 사용되지 않지만, 추후에 페이징 기능을 구현할 때 사용
5. MyBatis 플러그인 설치하기
(인텔리제이에서 Ctrl + Alt + S를 누르면 설정(Settings) 창이 열리고, 창 좌측에 Plugins 탭 클)
MyBatisX 플러그인을 이용하면 Mapper와 XML Mapper 간의 이동이 쉬워지고, 문법 등 자잘한 오류들을 쉽게 캐치할 수 있음.
MyBatis를 조금이나마 편리하게 사용할 수 있도록 MyBatisX 플러그인을 설치해 주세요.
6. MyBatis XML Mapper 생성하기
resources 폴더 안에 mapper라는 폴더를 만들어서 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.study.domain.post.PostMapper">
<!-- tb_post 테이블 전체 컬럼 -->
<sql id="postColumns">
id
, title
, content
, writer
, view_cnt
, notice_yn
, delete_yn
, created_date
, modified_date
</sql>
<!-- 게시글 저장 -->
<insert id="save" parameterType="com.study.domain.post.PostRequest">
INSERT INTO tb_post (
<include refid="postColumns" />
) VALUES (
#{id}
, #{title}
, #{content}
, #{writer}
, 0
, #{noticeYn}
, 0
, NOW()
, NULL
)
</insert>
<!-- 게시글 상세정보 조회 -->
<select id="findById" parameterType="long" resultType="com.study.domain.post.PostResponse">
SELECT
<include refid="postColumns" />
FROM
tb_post
WHERE
id = #{value}
</select>
<!-- 게시글 수정 -->
<update id="update" parameterType="com.study.domain.post.PostRequest">
UPDATE tb_post
SET
modified_date = NOW()
, title = #{title}
, content = #{content}
, writer = #{writer}
, notice_yn = #{noticeYn}
WHERE
id = #{id}
</update>
<!-- 게시글 삭제 -->
<delete id="deleteById" parameterType="long">
UPDATE tb_post
SET
delete_yn = 1
WHERE
id = #{id}
</delete>
<!-- 게시글 리스트 조회 -->
<select id="findAll" resultType="com.study.domain.post.PostResponse">
SELECT
<include refid="postColumns" />
FROM
tb_post
WHERE
delete_yn = 0
ORDER BY
id DESC
</select>
</mapper>
1) <mapper> 태그
XML Mapper는 <mapper>로 시작해서 </mapper>로 끝나며, <mapper> 태그의 namespace 속성에 Mapper 인터페이스의 경로를 선언해 주면 Mapper와 XML Mapper가 연결
Mapper 인터페이스는 XML Mapper에서 메서드명과 동일한 id를 가진 SQL 쿼리를 찾아 실행한다는 것
2) <sql> 태그와 <include> 태그
MyBatis는 <sql> 태그와 <include> 태그를 이용해서 공통으로 사용되거나 반복적으로 사용되는 쿼리를 처리함.
굳이 Java로 비유하자면, 변수로 선언해 두고 필요한 상황에 호출해서 사용하는 개념과 유사 (PostMapper.xml의 "postColumns"와 같이 INSERT 쿼리와 SELECT 쿼리에 주로 사용)
각각의 쿼리에 전체 칼럼을 선언해 줘도 되지만, 해당 태그들을 이용하면 코드 라인을 줄일 수 있음 두 태그의 포인트는 중복 제거이며, 동일한 XML Mapper뿐만 아니라, 다른 XML Mapper에 선언된 SQL 조각도 인클루드(Include) 할 수 있다.
3) parameterType
SQL 쿼리 실행에 필요한 파라미터의 타입을 의미
단일(하나의) 파라미터가 아닌 경우에는 일반적으로 객체를 전달받아 쿼리를 실행
4) resultType
SQL 쿼리의 실행 결과를 매핑할 결과 타입을 의미
Mapper 인터페이스에 선언한 메서드의 리턴 타입과 동일한 타입으로 선언해 주면 된다
5) #{ } 표현식
MyBatis는 #{ 변수명 } 표현식을 이용해서 전달받은 파라미터를 기준으로 쿼리를 실행
7. DatabaseConfig 클래스
Config에 대해서는 다시 한번 정리를 할 것 (간단하게 이야기하자면 원래는 spring을 설정할 때, 요청과 응답을 처리해주는 과정에서 repogitory, service, 등의 객체를 생성하고, 그것을 다시쓰고,,,, 너무나도 복잡한과정을 줄여주도록 config에서 설정되어 알아서 처리해준다.)
마찬가지로 Mybaits의 설정을 잘 읽을 수 있도록 Bean을 설정해 주는 것이다.
application.porperties 에서 데이터베이스에 대한 정보들이 바탕이 되어있다.
그러면 그러한 정보를 기반으로 MapperScan을통해서 Mapper의 인터페이스를 Mapper.xml이 받아서 데이터베이스에 접근 할 수 있게 된다. (다시한번 요약하자면 propeties에 적힌 데이터환경을 바탕으로 Mapper를 통해 Mapper.xml을 실행시킬 수 있게끔 config가 servlet 역할을 하는것이다.)
package com.study.config;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
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 javax.sql.DataSource;
@Configuration
@PropertySource("classpath:/application.properties")
public class DatabaseConfig {
@Autowired
private ApplicationContext context;
@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(context.getResources("classpath:/mappers/**/*Mapper.xml"));
factoryBean.setConfiguration(mybatisConfig());
return factoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSession() throws Exception {
return new SqlSessionTemplate(sqlSessionFactory());
}
@Bean
@ConfigurationProperties(prefix = "mybatis.configuration")
public org.apache.ibatis.session.Configuration mybatisConfig() {
return new org.apache.ibatis.session.Configuration();
}
}
Reference & 마무리
프레임워크를 잘 다루기 위해서는 그 기술이 생겨난 배경에 대해서 아는 것이 좋았다.
예전에 레거시 프로젝트를 처음 배웠을 때는 마냥 복잡하기만 하다라고 생각한 것들이 지금의 간결한 프레임워크의 편리함과 기술에대한 이해에 도움이 되었다.