Networks/SpringBoot

SK networks AI Camp - SpringBoot

코딩하는 Español되기 2024. 9. 6. 12:30

저희 프로젝트에서 서버는 SpringBoot로 열기! 가 결정되었습니다....

 

그래서 다시 Java 공부를 해야 하는 상황에서 강사님의 자료가 있어서 그 자료로 실습을 하면서 익혀보고자 합니다.

Maven으로 빌드하고자 했던 파일은 Gradle로 변경했습니다.

(강사님 : 이건 제가 현직에 있을 때나 쓰던 건데요...? 새로운 게 나오면 새로운 걸 써야죠!)

Gradle

: 오픈소스 빌드 자동화 툴로, 거의 모든 타입의 소프트웨어를 빌드할 수 있음

○ 특징

    ● High Performance : 실행시켜야 하는 task만 실행하고 build cache를 통해 이전 실행 task output을 재사용

    ● JVM foundation : JVM에서 실행되기 때문에 JDK를 설치해야 함

    ● Convetions : Maven으로부터 의존 라이브러리 관리 기능을 차용

    ● Extensibility : Gradle을 고유의 task 타입을 제공하거나 모델을 빌드할 수 있음

 

○ 구성(In Spring)

├─ gradle
│ └─ wrapper
│ ├─ gradle-wrapper.jar
│ └─ gradle-wrapper.properties
├─ gradlew
├─ gradlew.bat
├─ build.gradle
└─ settings.gradle

 

○ Gradle 라이브러리 의존성 관리

    ● build.gradle

        - 라이브러리 의존성 설정은 build.gradle(스크립트 파일)에 작성 가능

        - Dependency Configuration

            * Implementation : 구현할 때만 사용

            * complieOnly : 컴파일 때만 사용

            * runtimeOnly : 런타임 때만 사용

            * testImplementation : 테스트에서만 사용

 

Filter

: 디스패처서블릿에 요청이 전달되기 전/후에 url패턴에 맞는 모든 요청에 대해 부가작업을 처리할 수 있는 기능 제공

  단, Request, Response는 조작 가능하지만 인터셉터는 조작 불가

  FilterChain(필터 체인)을 통해 여러 필터가 연쇄적으로 동작하게 가능(오른쪽 사진)

 

○ 사용법

    ● 요청/응답 로깅 : 요청 및 응답 내용 기록 & 모니터링 용도로 사용

    ● 인증 및 권한 부여 : 요청에 대한 인증 및 권한 부여 작업

    ● 데이터 변환 : 요청 데이터 | 응답 데이터 변환 or 형식 조작 가능

    ● 캐싱 : 응답을 캐시 하여 성능 향상 가능(exception 처리 가능)

 

○ 주요 메서드

    init() : 필터 인스턴스 초기화 시 실행되는 메서드

    doFilter() : 클라이언트의 요청/응답 처리 시 실행되는 메서드

    destory() : 필터 인스턴스가 제거될 때 실행되는 메서드

Interceptor

: 스프링 프레임워크에서 제공하는 기능

  웹 앱의 요청 처리 과정에서 컨트롤러 호출 전후에 추가적인 작업을 수행할 수 있도록 도와줌

 

○ Spring MVC Request Lifecycle

○ Interceptor를 사용하면 가능한 작업

    ● 요청 전/후에 공통적으로 처리해야 할 작업을 수행 가능

       e.g. 인증 및 권한 검사, 세션 관리 등

    ● 컨트롤러 실행 전/후에 부가적인 작업을 수행 가능

       e.g. 로깅, 성능 측정, 트랜잭션 관리 등

    ● 컨트롤러의 결과를 가공하거나 추가 데이터 주입 가능

 

○ 주요 메서드

    ● perHandle()

        - 컨트롤러 실행 전 호출되는 메서드

        - 주로 요청 전에 수행해야 하는 사전 처리 작업 구현

    ● postHandle()

        - 컨트롤러 실행 후 뷰가 렌더링 되기 전에 호출되는 메서드

        - 컨트롤러가 실행된 이후 추가적인 처리 작업 수행 가능

    ● afterCompletion()

        - 뷰가 렌더링 된 후에 호출되는 메서드

        - 요청의 완료 후에 처리해야 하는 작업을 구현 가능

 

○ Filter와 차이점

    ● Filter

        - 서블릿 컨테이너에서 작동 So, 웹 앱 전체에 대해 적용

        - 모든 요청과 응답에 대해 적용

    ● Interceptor

        - Spring MVC 프레임워크에서 작동 So, Spring MVC 콘텍스트에만 적용

        - 요청이 MVC 컨트롤러로 라우팅 될 때만 Interceptor가 적용


SpringDBJPA

MySQL 설치

1. dbeaver 설치

2. Docker 설치

2024.07.18 - [Networks/MySQL&DB] - SK networks AI Camp - MySQL & DBeaver설치

 

SK networks AI Camp - MySQL & DBeaver설치

저번에 Docker를 설치했었습니다.그냥 install 하는 방법도 있지만 저희는 Docker을 통해 MySQL을 설치하겠습니다.  [Installer로 설치하는 방법]https://github.com/good593/course_sql/blob/main/MySQL%20Installer.md course_

joowon582.tistory.com

3. mysql 설치 폴더 생성

mkdir ./mysql
mkdir ./mysql/data

4../mysql/docker-compose.yml 파일 생성

version: "2"

services:
  vacation-db:
    image: mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: "root1234"
      MYSQL_DATABASE: "examplesdb"
      MYSQL_USER: "urstory"
      MYSQL_PASSWORD: "u1234"
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
    volumes:
      - ./database/init/:/docker-entrypoint-initdb.d/
      - ./database/datadir/:/var/lib/mysql
    platform: linux/x86_64
    ports:
      - 3306:3306

 

MySQL 실행

cd ./mysql # docker-compose.yml이 있는 폴더로 이동 
docker-compose up -d # mysql 생성 및 실행 
docker ps # 생성된 mysql 확인

Spring Boot에 MySQL 적용

    ●./src/resources/application.yml 생성(application.properties 파일 삭제!!!!!)

      * 강사님 왈 : yml 파일로 주로 사용하는 경향이 있다. 

# Spring Data Source 설정 
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/examplesdb?userSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul
    username: urstory
    password: u1234
    driver-class-name: com.mysql.cj.jdbc.Driver

 

DTO, DAO, Repository, Entity

○ Spring Boot 서비스 구조

○ Entity(Domain)

    ● DB에 쓰일 칼럼, 여러 엔티티 간의 연관관계 정의

    ● DB의 테이블을 하나의 엔티티로 생각

    ● 이 클래스의 필드는 각 테이블 내부의 Column을 의미

 

○ Repository

    ● Entity에 의해 생성된 DB에 접근하는 메서드를 사용하기 위한 인터페이스

    ● Service와 DB를 연결하는 고리 역할 수행

    ● DB에 적용하고자 하는 CRUD 정의

 

○ DAO(Data Access Object)

    ● DB에 접근하는 객체

    ● Service가 DB에 연결할 수 있게 해주는 역할

    ● DB를 사용해 데이터 조회, 조작하는 기능 담당

 

○ DTO(Data Transfer Object; VO(Value Object))

    ● 계층 간 데이터 교환을 위한 객체

    ● VO의 경우 Read Only 개념을 가지고 있음

ORM(Object Relational Mapping)

: 애플리케이션 객체와 관계형 DB의 데이터를 자동으로 매핑해 주는

   java의 데이터 클래스와 RDB의 테이블 매핑

  객체지향 프로그래밍과 관계형 DB의 차이로 발생하는 제약사항을 해결해 주는 역할 수행

  e.g. JPA

 

○ 장점

    ● SQL 쿼리가 아닌 직관적인 코드로 데이터 조작 가능(So, 비즈니스 로직에 집중 가능)

    ● 재사용 및 유지보수 편리

        - ORM은 독립적으로 작성 So, 재사용 가능

        - 매핑정보를 명확히 설계 So, 따로 DB 볼 필요 없음

    ● DBMS에 대한 종속성이 줄어듦

        - DBMS 교체 작업을 비교적 적은 리스크로 가능

○ 단점

    ● 복잡성이 커질 경우 ORM만으로 구현이 어려움(직접 쿼리 구현하지 않아 복잡한 설계 불가)

    ● 잘못 구현하면 속도 저하(대형 쿼리의 경우 별도의 튜닝 필요)

 

○ JPA(Java persistence API)

    ● JPA는 ORM과 관련된 인터페이스 모음

    ● ORM이 큰 개념이라고 하면, JPA는 더 구체화시킨 스펙을 포함

 

○ Hibernate

    ● ORM Framework 中 1

    ● JPA 구현체 中 1, 현재 가장 많이 사용

 

○ Spring Data JPA

    ● Spring Framework에서 JPA를 편리하게 사용할 수 있게 지원하는 라이브러리

    ● CRUD 처리용 인터페이스 제공

       * CRUD : Create, Read, Update, Delete의 약자로, 저장된 데이터에 대한 작업 방법을 의미

    ● Repository 개발 시 인터페이스만 작성하면 구현 객체를 동적으로 생성해 주입

    ● 데이터 접근 계층 개발 시 인터페이스만 작성해도 괜찮음

 

○ JPA Query Method

: 스프링 데이터 JPA는 메서드 이름으로 쿼리 생성하는 쿼리 메서드 기능 제공

    ● 쿼리 메서드 : 메서드 이름을 분석해 JPQL 쿼리 실행

    ● 쿼리메서드를 활용하면 쉽게 쿼리문 작성 가능

    ● Select

User findByEmail(String email);
User getByEmail(String email);
User readByEmail(String email);
User queryByEmail(String email);
User searchByEmail(String email);
User streamByEmail(String email);
User findUserByEmail(String email);

    ● And, or 

List<User> findByNameAndEmail(String name, String email);
List<User> findByNameOrEmail(String name, String email);

    ● is(Not) Empty, is(Not) Null

List<User> findByIdIsNotNull();  // Id값에 Null값이 없는지?
List<User> findByAddressIsNotEmpty();

    ● in / StringWith / EndingWith / Contains

        - findByNameIn : 입력된 이름 목록 중 하나라도 해당하는 이름을 가진 사용자 리스트 조회

        - findByNameStartingWith : 입력된 문자열로 시작하는 이름을 가진 사용자 리스트 조회

        - findByNameEndingWith : 끝나는 이름

        - findByNameContains : 포함된 이름

        - findByNameLike : 패턴이 일치하는 이름 가진 사용자

List<User> findByNameIn(List<String> name);
List<User> findByNameStartingWith(String name);
List<User> findByNameEndingWith(String name);
List<User> findByNameContains(String name);
List<User> findByNameLike(String name);

    ●Is / Equals / Sorting

Set<User> findUserByNameIs(String name);
Set<User> findUserByName(String name);
Set<User> findUserByNameEquals(String name);


List<User> findTop1ByNameOrderByIdDesc(String name); 
// Id로 내림차순으로 정렬 후 입력 name과 같은 것의 맨 위의 있는 값을 뽑아온다.
    
List<User> findFirst2ByNameOrderByIdDescEmailAsc(String name);
// 여러개의 조건으로 find하는 경우는 And를 사용하였으나 정렬 조건으로 여러개의 값을 사용하는 경우는 And를 사용하지 않고 조건을 이어서 붙인다.

List<User> findFirstByName(String name, Sort sort);

MyBatis

: 객체 지향 언어인 자바의 RDBMS 프로그래밍을 좀 더 쉽게 할 수 있게 도와주는 개발 프레임 워크

   JDBC를 통해 DB에 액세스 하는 작업을 캡슐화

   일반 SQL 쿼리, 저장 프로 시저 및 고급 매핑 지원

   모든 JDBC 코드 및 매개 변수의 중복작업을 제거

- MyBatis에서 프로그램에 있는 SQL 쿼리를 한 구성파일에 구성하여 프로그램 코드와 SQL코드를 분리할 수 있는 장점

 

○ Mybatics 설정

[블로그 방법]

1. logback-spring.xml 추가(resources 안에 작성)

    - appender : 전달받은 로그 출력할 곳 결정(콘솔 | 파일 저장 | DB 저장)

    - encoder : appender에 포함되어 출력할 로그 형식 지정

    - logger : 로그를 출력하는 요소

                    level 속성을 통해 출력할 로그 레벨을 조절하여 appender에 전달

                    첫 번째 logger에서 com.study = java 디렉터리의 java 패키지 경로를 의미

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">

    <!-- Appenders -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <charset>UTF-8</charset>
            <Pattern>%d %5p [%c] %m%n</Pattern>
        </encoder>
    </appender>

    <appender name="console-infolog" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <charset>UTF-8</charset>
            <Pattern>%d %5p %m%n</Pattern>
        </encoder>
    </appender>

    <!-- Logger -->
    <logger name="com.study" level="DEBUG" appender-ref="console" />
    <logger name="jdbc.sqlonly" level="INFO" appender-ref="console-infolog" />
    <logger name="jdbc.resultsettable" level="INFO" appender-ref="console-infolog" />

    <!-- Root Logger -->
    <root level="off">
        <appender-ref ref="console" />
    </root>
</configuration>

log

○ 로그 레벨

        ● fatal / error : (심각한) 에러

        ● warn : 실행은 문제없지만 나중에 시스템 에러 원인이 될 수 있음

        ● trace : 디버그 레벨이 너무 광범위한 것을 해결하기 위해 좀 더 상세한 이벤트를 나타냄

○ 로그 타입

        sqlonly : SQL을 로그로 남김(Prepared Statement와 관련된 파라미터 자동으로 변경되어 SQL 출력)

        ● sqltiming : SQL과 SQL 실행시간 출력

        ● audit : ResultSet을 제외한 모든 JDBC 호출 정보 출력(사용 권장 X)

        ● resultset : ResultSet을 포함한 모든 JDBC 호출 정보 출력

        ● resultsettable : SQL 조회 결과를 테이블 형태로 출력

        ● connection : Connection의 연결과 종료에 관련된 로그를 출력

 

2. Log4 JDBC 라이브러리 추가하기

: SQL 쿼리를 깔끔하게 정렬된 상태로 출력하고 추가적인 정보를 제공받을 수 있도록 추가

  build.gradle의 dependencise에 Log4 JDBC 라이브러리 추가하고 다시 로드

* implementation은 꼭 implementation끼리 뭉쳐있어야 함

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.3.0'
	implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'       /* Thymeleaf Layout */
	implementation 'org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16'  /* Log4JDBC */
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
	annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

 

3.log4 jdbc.log4j2.properties 추가(resources 디렉터리 안)

# log4jdbc spy의 로그 이벤트를 slf4j를 통해 처리
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator

# 로그를 표시할 줄의 제한, 0은 무제한
log4jdbc.dump.sql.maxlinelength=0

4. jdbc-url과 driver-class-name 변경

: application.properties의 데이터 소스 설정을 변경(DB 정보는 환경에 맞게 설정)

spring.datasource.hikari.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
spring.datasource.hikari.jdbc-url=jdbc:log4jdbc:mariadb://localhost:3306/board?serverTimezone=Asia/Seoul&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
spring.datasource.hikari.username=username
spring.datasource.hikari.password=password
spring.datasource.hikari.connection-test-query=SELECT NOW() FROM dual

 

 

[강사님 방법]

1. build.gradle

dependencies {
...
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.2'
implementation 'org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16'
...
testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.2'
...
}

2. log4 jdbc.log4j2.properties

# log4jdbc spy의 로그 이벤트를 slf4j를 통해 처리한다.
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator

# 로그를 표시할 줄의 제한, 0은 무제한
log4jdbc.dump.sql.maxlinelength=0

3. application.yml

# Spring Boot 설정!!!
spring:

  # Database(MySQL) 설정!!
  datasource:
    username: urstory
    password: u1234
    # url: jdbc:mysql://127.0.0.1:3306/examplesdb?userSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul
    # driver-class-name: com.mysql.jdbc.Driver
    
    # log4jdbc 적용!!
    url: jdbc:log4jdbc:mysql://127.0.0.1:3306/examplesdb?userSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul
    driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
    hikari:
      connection-test-query: SELECT NOW() FROM dual

# Mybatis 설정!!!
mybatis:
  # default Package location - resultType의 Alias를 지정합니다.
  type-aliases-package: com.example.basic.model.entity
  # mapper location - 바라 볼 xml 파일을 지정합니다.
  mapper-locations: classpath:mapper/**/*.xml
  # column name to camel case - 반환 받는 컬럼명을 CamelCase로 받는 설정을 합니다.
  configuration:
    map-underscore-to-camel-case: true

4. logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">

    <!-- Appenders -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <charset>UTF-8</charset>
            <Pattern>%d %5p [%c] %m%n</Pattern>
        </encoder>
    </appender>

    <appender name="console-infolog" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <charset>UTF-8</charset>
            <Pattern >%d %5p %m%n</Pattern>
        </encoder>
    </appender>

    <!-- Logger -->
    <logger name="com.study" level="DEBUG" appender-ref="console" />
    <logger name="jdbc.sqlonly" level="INFO" appender-ref="console-infolog" />
    <logger name="jdbc.resultsettable" level="INFO" appender-ref="console-infolog" />

    <!-- Root Logger -->
    <root level="off">
        <appender-ref ref="console" />
    </root>
</configuration>

Mybatis 동적 SQL

더보기

○ if

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>

○ choose / when / otherwise

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>

○ where

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG
  <where>
    <if test="state != null">
         state = #{state}
    </if>
    <if test="title != null">
        AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
        AND author_name like #{author.name}
    </if>
  </where>
</select>

○ set

<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="email != null">email=#{email},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>

○ foreach

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  <where>
    <foreach item="item" index="index" collection="list"
        open="ID in (" separator="," close=")" nullable="true">
          #{item}
    </foreach>
  </where>
</select>

 

'Networks > SpringBoot' 카테고리의 다른 글

SK networks AI Camp - SpringBoot(3)  (0) 2024.09.09
SK networks AI Camp - SpringBoot(2)  (4) 2024.09.06