[Spring Boot] Thymeleaf(타임리프) layout dialect 사용하기

 

 

웹 페이지를 만들게되면 기본적으로 head, header, content, footer 등의 레이아웃으로 나누어서 만들게된다. 기존에 스프링과 jsp를 통하여 만들 때 tiles를 이용하여 각 영역들을 분할하여 작업할 수 있었는데, 타임리프에선 thymeleaf-layout-dialect를 이용하여 비스하게 사용할 수 있다.

 

 

타임리프는 프로젝트를 생성할 때 의존성을 추가하면 된다.

 

 

2021.08.13 - [Spring-Boot] - [Spring Boot] 프로젝트 생성하기(Intellij)

 

[Spring Boot] 프로젝트 생성하기(Intellij)

[Spring Boot] Spring Initializr를 이용하여 프로젝트 생성하기(Intellij) 기존에 STS(Spring Tool Suite)나 Eclipse 툴 안에서 Maven이나 Gradle로 프로젝트를 생성했던 경험이 있는데 Spring Initializr 웹 도..

dbsyys.tistory.com

 

기존에 프로젝트에 타임리프를 사용중이라면 의존성을 하나 더 추가하면 된다.

 

 

 

https://mvnrepository.com/

 

메이븐 사이트에 접속하여 "Thymeleaf layout dialect"를 검색한다.

 

 

검색 후 내용. 같은 이름이지만 2013년은 너무 오래전이니 2021년을 선택하자

 

 

Maven인지 Gradle인지는 각자 프로젝트 의존성 설정방식에 맞추면된다. 현재 프로젝트에서는 Gradle을 사용하고 있기때문에 Gradle을 사용한다.

 

 

implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
// https://mvnrepository.com/artifact/nz.net.ultraq.thymeleaf/thymeleaf-layout-dialect
implementation group: 'nz.net.ultraq.thymeleaf', name: 'thymeleaf-layout-dialect', version: '2.5.3'

 

해당 코드를 Gradle 의존성에 추가했다면 하나의 웹페이지를 구성할 영역들과 동적으로 바꿀 페이지를 지정하면 된다.

 

 

 

  • layout/head.html(영역으로 쓸 페이지)
  • layout/header.html(영역으로 쓸 페이지)
  • layout/navBar.html(영역으로 쓸 페이지)
  • layout/footer.html(영역으로 쓸 페이지)
  • layout/layout.html(각 영역들을 조합한 페이지)
  • main/content.html(각 영역을 유지하고 동적으로 변환할 페이지)
  • member/saveForm.html(각 영역을 유지하고 동적으로 변환할 페이지)

 

 

- layout/layout.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
    <head th:replace="layout/head :: layout_head(~{::meta}, ~{::title}, ~{::link})">
    </head>
    <body class="nav-md">
        <div class="container body">
            <div class="main_container">

                <div th:replace="~{layout/navBar :: layout_navBar}"></div>
                <div th:replace="~{layout/header :: layout_header}"></div>
                <div layout:fragment="layout_content"></div>
<!--                <div th:replace="~{layout/content :: layout_content}"></div>-->
                <div th:replace="~{layout/footer :: layout_footer}"></div>

            </div>
        </div>

    </body>
</html>

 

기본적으로 타임리프를 쓰기위해선 xmlns:th="http://www.thymeleaf.org" 설정을 해야하며 layout-dialect를 사용하는 페이지에선 xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"를 설정한다.

 

각 영역들을 지정할 땐 th:replace로 경로를 지정하며, 주된 content부분의 내용은 페이지마다 동적으로 변환할 것이기 때문에  layout:fragment를 지정한다. 또한 <head> 부분은 head.html 페이지에서 설정한 정보를 받기위하여 meta, title, link 요소를 파라미터로 받아오게 설정했다.

 

 

- layout/head.html

<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="layout_head(meta, title, link)">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <!-- Meta, title, CSS, favicons, etc. -->
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" th:href="@{/images/favicon.ico}" type="image/ico" />

    <title>Gentelella Alela! | </title>

    <!-- Bootstrap -->
    <link th:href="@{/vendors/bootstrap/dist/css/bootstrap.min.css}" rel="stylesheet">
    <!-- Font Awesome -->
    <link th:href="@{/vendors/font-awesome/css/font-awesome.min.css}" rel="stylesheet">
    <!-- NProgress -->
    <link th:href="@{/vendors/nprogress/nprogress.css}" rel="stylesheet">
    <!-- iCheck -->
    <link th:href="@{/vendors/iCheck/skins/flat/green.css}" rel="stylesheet">
    .
    .
    .
    .

 

head부분은 <head>태그의 모든 설정부분을 그대로 넘기기 위해서 meta, title, link등 의 요소를 파라미터로 넘겼다. 현재 설정한 페이지의 요소들이 layout.html 파일의 내용으로 변경된다.

 

 

- layout/header.html

<html xmlns:th="http://www.thymeleaf.org">
    <th:block th:fragment="layout_header">
        <!-- top navigation -->
            <div class="top_nav">
                <div class="nav_menu">
                    <div class="nav toggle">
                        <a id="menu_toggle"><i class="fa fa-bars"></i></a>
                    </div>
                    .
                    .
                    .
                    .

 

 

- layout/navBar.html

<html xmlns:th="http://www.thymeleaf.org">
    <th:block th:fragment="layout_navBar">
        <div class="col-md-3 left_col">
            <div class="left_col scroll-view">
                <div class="navbar nav_title" style="border: 0;">
                    <a href="index.html" class="site_title"><i class="fa fa-paw"></i> <span>Gentelella Alela!</span></a>
                </div>

                <div class="clearfix"></div>
                .
                .
                .
                .

 

 

- layout/footer.html

<html xmlns:th="http://www.thymeleaf.org">
    <th:block th:fragment="layout_footer">
        <!-- footer content -->
            <footer>
                <div class="pull-right">
                    Gentelella - Bootstrap Admin Template by <a href="https://colorlib.com">Colorlib</a>
                </div>
                <div class="clearfix"></div>
            </footer>
        <!-- /footer content -->
    </th:block>
</html>

 

layout.html 페이지에서 영역으로 쓰일 부분은 th:replace, 각 영역에 해당하는 페이지는 th:fragment로 지정하며 두 부분의 이름은 같아야 한다.

 

 

- main/content.html

<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorator="layout/layout">
    <th:block layout:fragment="layout_content">
        <!-- page content -->
            <div class="right_col" role="main">
                <!-- top tiles -->
                <div class="row" style="display: inline-block;" >
                    <div class="tile_count">
                        <div class="col-md-2 col-sm-4  tile_stats_count">
                            <span class="count_top"><i class="fa fa-user"></i> Total Users</span>
                            <div class="count">2500</div>
                            <span class="count_bottom"><i class="green">4% </i> From last Week</span>
                        </div>
                        .
                        .
                        .
                        .
                        .

 

layout.html 에서 동적으로 변경시킬 content가 들어갈 페이지 이기때문에 html 부분에 xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" 설정을 한다. 추가적으로 해당 페이지에서는 어떤 페이지의 fragment로 쓸지 알아야하기 때문에 layout:decorator 설정을하고 layout으로 쓸 페이지의 경로를 지정해준다.

 

해당 페이지는 페이지 레이아웃은 유지하면서 상황에따라 content 부분만 바꿀 것이기 때문에 위에서 지정했던 영역들과는 다르게 th:fragment가 아닌 layout:fragment로 설정한다. 마찬가지로 layout.html에서 지정했던 name과 동일하게 지정한다.

 

 

- member/saveForm.html

<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorator="layout/layout">
    <th:block layout:fragment="layout_content">

    </th:block>
</html>

 

해당 페이지에도 content.html과 같은 설정을 했다. layout.html 페이지를 레이아웃으로 유지하면서 content 부분을 content.html과 saveForm.html 부분을 동적으로 변환하며 사용할 수 있다. 이러한 설정을 통해 다른 페이지를 추가했을 때도 같은 레이아웃을 적용할 수 있다.

[Spring Boot] IoC(제어의 역전)와 DI(의존주입) 스프링 컨테이너

 

 

우리는 자바란 프로그래밍 언어를 이용해서 프로젝트를 개발 시에 스프링이라는 프레임워크를 이용해서 개발한다. 스프링 프레임워크를 사용하는 이유는 여러가지가 있겠지만 가장 주된 이유로는 자바가 객체지향프로그래밍 언어라는 이유일 것이다.

 

객체지향 프로그래밍은 추상화, 캡슐화, 상속, 다형성을 가지며 이 특징들로 SOLID라는 5가지의 원칙을 만족하는 프로그래밍 방식을 의미한다. 이러한 특징들을 가진 프로그래밍 방식으로 여러개의 독립된 단위로 나누며 각각의 객체들의 역할과 구현방식으로 구분하여 프로그램을 유연하고 변경이 용이 하도록 만들 수 있다. 그러므로 확장이나 재 사용에 이점을 보인다.

 

설계를 할 때, 인터페이스와 구현체를 가지고 다형성을 만족하는 객체 지향 설계를 할 수있다. 하지만 다형성 만으로는 완전한 객체 지향 설계를 했다고 보기는 힘들다.

 

예를 들어

public class Member {

    private Long id;
    private String name;
    private boolean check; // 회원 여부
    private int count = 10000; // 가진 금액

    public Member(Long id, String name, boolean check) {
        this.id = id;
        this.name = name;
        this.check = check;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isCheck() {
        return check;
    }

    public void setCheck(boolean check) {
        this.check = check;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

}

Member라는 도메인과

 

public class MemberMain {

    public static void main(String[] args) {

        MemberService memberService = new MemberServiceImpl();

        Member member = new Member(1L, "memberA", true);

        memberService.enter(member);
    }

}

로직을 수행 할 Main

 

public interface MemberService {

    void enter(Member member);

}
public class MemberServiceImpl implements MemberService {

    private GradeMember gradeMember = new CommonGradeMember();

    @Override
    public void enter(Member member) {
        gradeMember.countInfo(member);
    }
}

회원 여부 별 입장에 관한 로직을 수행할 서비스 인터페이스와 서비스 구현체를 나눴다.

 

package com.example.demo.memberGrade;

import com.example.demo.domain.Member;

public interface GradeMember {

    void countInfo(Member member);

}
package com.example.demo.memberGrade;

import com.example.demo.domain.Member;

public class CommonGradeMember implements GradeMember {

    private final int MEMBER_PRICE = 4000;
    private final int COMMON_PRICE = 10000;

    @Override
    public void countInfo(Member member) {
        System.out.println("------------------------");
        System.out.println(member.getName() + "님, 회원 여부 : " + member.isCheck());

        checkResult(member);

        System.out.println("------------------------");
    }

    private void checkResult(Member member) {
        if (member.isCheck()){
            System.out.println("결제 될 금액은 " + MEMBER_PRICE + "원 입니다");
            System.out.println("남은 금액: " + (member.getCount() - MEMBER_PRICE) + "원");
        }else{
            System.out.println("결제 될 금액은 " + COMMON_PRICE + "원 입니다");
            System.out.println("남은 금액: " + (member.getCount() - COMMON_PRICE) + "원");
        }
    }
}

지불해야 할 금액에 관한 상세 인터페이스와 구현체로 나눴다.

 

 

Member의 정보 별로 입장에 관한 인터페이스와 구현체, Member의 회원 여부에 따른 금액 측정에 관한 인터페이스와 구현체로 나눈 것이다. main 함수를 실행하면

------------------------
memberA님, 회원 여부 : true
결제 될 금액은 4000원 입니다
남은 금액: 6000원
------------------------

의 형태로 출력된다.

 

 

인터페이스(역할)과 구현체(구현)을 분리하여 개발했고 다형성(polymorphism)을 통해 하나의 객체 타입으로 여러가지 타입을 가질 수 있게, 즉 인터페이스를 통해 해당 인터페이스를 구현한 여러가지 구현체를 가질 수 있도록 만들었다.

 

하지만 만약에 여기서 회원정보 별로 결제될 금액이 고정 값이 아니라 할인율을 통해서 할인이 된다고 바뀐다고하면 위에서 말한 SOLID의 5원칙을 지키지 못하게 된다.

 

public class VIPGradeMember implements GradeMember {

    private final int MEMBER_PRICE = 10;
    private final int COMMON_PRICE = 10000;

    @Override
    public void countInfo(Member member) {
        System.out.println("------------------------");
        System.out.println(member.getName() + "님, 회원 여부 : " + member.isCheck());

        checkResult(member);

        System.out.println("------------------------");
    }

    private void checkResult(Member member) {

        if (member.isCheck()){
            int discount = (COMMON_PRICE / MEMBER_PRICE);
            int resultCount = (member.getCount() - discount);

            System.out.println("할인 될 금액은 " + discount + "원 입니다");
            System.out.println("남은 금액: " + resultCount + "원");
        }else{
            System.out.println("결제 될 금액은 " + COMMON_PRICE + "원 입니다");
            System.out.println("남은 금액: " + (member.getCount() - COMMON_PRICE) + "원");
        }

    }
}

할인을 고정 값이 아닌 할인율을 통해 지정하는 구현체이다.

 

 

MemberServiceImpl 구현체가 GradeMember 인터페이스를 의존하면서 CommonGradeMember 구현체까지 의존하고 있다.

 

 

위의 VIPGradeMember 구현체로 변경하게되면 MemberServiceImpl 구현체에서 아래와 같은 작업을 해야한다.

// private GradeMember gradeMember = new CommonGradeMember();
private GradeMember gradeMember = new VIPGradeMember();

 

 

SOLID 5원칙 중 DIP(Dependency inversion principle)의 원칙에 따라 구현체에 의존하지 않으며 인터페이스에 의존해야 하기때문에 설계를 변경해야한다.

 

// private GradeMember gradeMember = new CommonGradeMember();
private final GradeMember gradeMember;

인터페이스에만 의존하도록 설계를 변경하게되어 실제로 구현체가 없어 예외가 발생한다. 그렇기 때문에 다른 곳에서 GradeMember의 구현 객체를 대신 생성하여 주입해야한다.

 

public class MemberServiceImpl implements MemberService {

    /*private GradeMember gradeMember = new CommonGradeMember();*/
    private final GradeMember gradeMember;

    public MemberServiceImpl(GradeMember gradeMember) {
        this.gradeMember = gradeMember;
    }

    @Override
    public void enter(Member member) {
        gradeMember.countInfo(member);
    }
}

MemberServiceImpl에서는 생성자를 통해 주입된 구현체의 인터페이스만 의존하여 처리하도록 변경했기때문에 GradeMember를 의존하는 구현체는 어떠한 것이든 받을 수 있고 구현체가 바뀌어도 따로 구조를 변경하지 않아도 된다.

 

public class MemberConfig {

    public MemberService memberServiceImpl() {

//        return new MemberServiceImpl(new CommonGradeMember());
        return new MemberServiceImpl(new VIPGradeMember());
    }
}
public class MemberMain {

    public static void main(String[] args) {

        MemberConfig memberConfig = new MemberConfig();
        MemberService memberService = memberConfig.memberServiceImpl();
//        MemberService memberService = new MemberServiceImpl();

        Member member = new Member(1L, "memberA", true);

        memberService.enter(member);
    }

}

위의 MemberConfig라는 클래스를 생성했다. 해당 클래스는 앞에서 할인율을 어떻게 적용할지 변경 할 때, 인터페이스와 구현체의 로직 변경은 일어나지 않고 단순히 구성 부분만 변경함으로 할인율을 변경 할 수 있다.

 

MemberConfig를 통해서 전체 동작 방식을 구성할 수 있다. 해당 클래스에서선 구현체를 생성하고 연결하는 역할을 하게 된다. 이로써 각 구현 객체들은 어떤 객체가 들어오게 되던 자신의 로직을 실행하는 역할만을 담당하고 전체 프로그램의 제어의 흐름은 MemberConfig 객체가 하게된다. 이렇게 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전(IoC)이라고 한다.

 

 

프로그램의 의존관계를 보게되면 MemberServiceImpl은 GradeMember에 의존하지만 어떤 구현체가 사용 될지는 모른다. 의존 관계는 정적 클래스 의존관계와 동적 객체 인스턴스 의존 관계로 나누어진다.

 

- 정적 클래스 의존관계

  • 클래스가 사용하는 코드만 보고 의존관계를 판단 할 수 있다. MemberServiceImpl은 GradeMember에 의존한다.

- 동적 객체 인스턴스 의존관계

  • 애플리케이션 실행 시점에서 실제 생성된 객체 인스턴스의 참조가 연결된 의존 관계이다. CommonGradeMember객체를 구현체로 쓸건지 VIPGradeMember 구현체인지는 실행 시에 결정된다. 런타임 시에 객체를 생성하고 클라이언트와 서버의 실제 구현체와의 의존관계가 연결 되는 것을 의존관계 주입(DI)이라 한다. 

MemberConfig처럼 객체를 생성하고 관리하면서 의존관계를 연결해주는 것을 IoC 컨테이너 또는 DI 컨테이너라고 한다. 

 


 

지금까지 작성한 IoC, DI는 순수하게 자바로만 구현한 것이다. 추가되어야 할 부분이 많지만 기본 틀은 이러한 방식을 사용했다. 스프링을 이용하게되면 IoC와 DI 컨테이너와 함께 그에 관련된 수 많은 추가 기능들을 제공한다. 앞에선 개발자가 MemberConfig와 같은 제어하는 객체를 직접 만들고 의존관계를 주입했지만 스프링에선 스프링이 의존관계를 주입해준다. 그것을 스프링 컨테이너라고 하고 여기에 등록된 객체를 스프링 빈이라고 한다.

 

@Configuration
public class MemberConfig {

    @Bean
    public MemberService memberServiceImpl() {

//        return new MemberServiceImpl(new CommonGradeMember());
        return new MemberServiceImpl(new VIPGradeMember());
    }
}
public class MemberMain {

    public static void main(String[] args) {

        ApplicationContext ac = new AnnotationConfigApplicationContext(MemberConfig.class);
        MemberService memberService = ac.getBean("memberServiceImpl", MemberService.class);

//        MemberConfig memberConfig = new MemberConfig();
//        MemberService memberService = memberConfig.memberServiceImpl();

//        MemberService memberService = new MemberServiceImpl();

        Member member = new Member(1L, "memberA", true);

        memberService.enter(member);
    }

}

기존에 DI 컨테이너로 사용하던 MemberConfig와 데이터를 입력하는 MemberMain을 스프링에서 지원하는 어노테이션 방식으로 사용을 변경했다. main함수에서 사용된 ApplicationContext 객체는 스프링 컨테이너이며 스프링컨테이너는 @Configuration이 붙은 MemberConfig를 설정 정보로 사용한다. 그리고 @Bean이 붙은 메서드를 모두 호출해서 반환된 객체로 스프링 컨테이너에 등록한다. 다시 말해서 @Bean이 붙은 객체는 스프링 컨테이너에 등록되어 있으며 그것을 스프링 빈이라고 한다.

 

이처럼 스프링은 제어의역전(IoC)과 의존주입(DI)에 대해 기본적인 설정들을 편리하게 제공하며, 공통적으로 누구나 사용하면서 고려해볼 수 있는 무수히 많은 설정들을 제공한다.

 

[Spring Boot] 프로젝트 빌드하고 실행하기(.jar)

 

 

평소 지금까지 해오던 방식이나 회사에서는 운영서버에 웹 서버를 설치하고 WAS서버를 설치한 후, 설치한 서버를 통해서 해당 웹 어플리케이션을 가동했지만 스프링부트의 내장 서버를 가지고 프로젝트 파일을 빌드하여 서버를 올려보려고 한다.(.jar)

 

 

프로젝트를 아직 만들지 않았다면 툴을 사용하여 프로젝트를 생성하거나, Spring Initializr을 이용하여 프로젝트를 생성하도록 하자.

 

2021.08.13 - [Spring-Boot] - [Spring Boot] 프로젝트 생성하기(Intellij)

 

[Spring Boot] 프로젝트 생성하기(Intellij)

[Spring Boot] Spring Initializr를 이용하여 프로젝트 생성하기(Intellij) 기존에 STS(Spring Tool Suite)나 Eclipse 툴 안에서 Maven이나 Gradle로 프로젝트를 생성했던 경험이 있는데 Spring Initializr 웹 도..

dbsyys.tistory.com

 

위 링크에보면 Spring Initializr을 이용하여 프로젝트를 생성할 수 있다.

 

 

먼저, 프로젝트를 만든 폴더의 Root경로로 가자.

프로젝트 구성파일

콘솔창을 열고 해당 루트 경로로 들어간다. (복붙이 가능하면 복붙 추천)

 

C:\Users\{사용자폴더}> cd {프로젝트 경로}

 

해당 경로에서 "ls" 입력하게되면 프로젝트 구성파일 이미지와 같은 폴더와 파일 목록이 출력된다.

 

 

해당 목록을 확인했다면 gradlew.bat 파일을 실행시켜주자.

 

C:\Users\{프로젝트경로}> ./gradlew.bat build

(*Mac의 경우는 ./gradlew build)

 

 

문제없이 빌드 성공메세지가 나오게되면 build 폴더가 생성된다.

 

(*build폴더나 build폴더 안에 libs 파일이 생성되지 않았다면 ./gradlew.bat clean build로 재실행)

 

폴더가 생성되면

 

build - libs - {프로젝트명}-0.0.1-SNAPSHOT.jar 파일이 있는지 확인한다.

 

C:\Users\{프로젝트경로}> cd build\libs

 

위의 경로에 jar파일이 생성되었다면 콘솔창에서 해당 경로로 들어가서

 

C:\Users\{프로젝트경로}\build\libs> java -jar {프로젝트명}-0.0.1-SNAPSHOT.jar

 

명령어를 실행시켜준다.

 

성공 !

 

간혹 8080번 포트가 이미 실행되어 있으면 서버가 올라가지 않을 수도 있다. 그렇다면 netstat 명령어나 작업관리자 서비스에서 8080번 포트를 죽이고 올리자

(이클립스나 인텔리제이에서 실행중이어도 마찬가지)


 

- 스프링부트를 사용하여 내장 서버를 통해 서버를 가동했는데 이런 방식이라면 운영서버에 빌드하고 배포하는 일이 많이 수월해질것같다..! (지금 회사에서 운영서버 관리할 때 서버 설치하는건 둘째치고...기존에 class, jsp 등 리소스 파일이랑....sql쿼리랑...일일이 하나하나 날짜보면서 옮겼던거 생각하면...옮기는건 그렇다쳐도 파일이 하나라도 빠지면...에러.....반복작업....후....)

 

아무튼..

 

끝 !

[JPA] JPA? JPA 세팅 및 시작하기

 

 

기존에 MyBatis 방식만을 사용하다가 ORM(Object-Relation Mapping)기반인 JPA(Java Persistence Interface) 프로그래밍에 관심이 생겨서 알아보게 되었다.

 

자바 퍼시스턴스 API또는 자바 지속성 API(Java Persistence API, JPA)는 자바 플랫폼 SE와 자바 플랫폼 EE를 사용하는 응용프로그램에서 관계형 데이터베이스의 관리를 표현하는 자바 API이다.

기존에 EJB에서 제공되던 엔터티 빈(Entity Bean)을 대체하는 기술이다. 자바 퍼시스턴스 API는 JSR 220에서 정의된 EJB 3.0 스펙의 일부로 정의가 되어 있지만 EJB 컨테이너에 의존하지 않으며 EJB, 웹 모듈 및 Java SE 클라이언트에서 모두 사용이 가능하다. 또한, 사용자가 원하는 퍼시스턴스 프로바이더 구현체를 선택해서 사용할 수 있다.

 

 

MyBatis과 JPA의 특징

-MyBatis

  • 동적인 쿼리를 사용하기 유연하다.
  • 새로운 패러다임(ex. JPA) 없이 Sql을 그대로 이용할 수 있다.
  • 프로그래밍 코드와 sql을 분리하여 작업 할 수 있다.

-JPA

  • CRUD 시 반복적인 Sql쿼리 사용을 줄여준다
  • 테이블을 엔티티로 관리하여 객체지향적으로 데이터를 관리할 수 있다.
  • 스키마 변경 시 엔티티 변경만으로 관련 테이블쿼리 변경이 가능하다.
  • 동일한 트랜잭션에서 엔티티의 동일성을 보장한다.

 

추후에는 Spring Data JPA 를 쓰겠지만 JPA의 고유의 특징들을 알기위해 기본적인 설정부터 했다.

 

	<!-- JPA 하이버네이트 -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>5.4.29.Final</version>
        </dependency>
        
	<!-- H2 데이터베이스 -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.200</version>
        </dependency>

프로젝트의 의존성 추가를 위해 pom.xml에 위 항목을 추가한다. 테스트를 위한 데이터베이스로 H2 데이터베이스를 사용했다. 프로젝트나 사용하려는 데이터베이스를 맞게 설정하면 된다.

 

 

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
             xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
    <persistence-unit name="xxx">
        <properties>
            <!-- 필수 속성 -->
            <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
            <property name="javax.persistence.jdbc.user" value="sa"/>
            <property name="javax.persistence.jdbc.password" value=""/>
            <property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>

            <!-- 옵션 -->
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.use_sql_comments" value="true"/>
            <property name="hibernate.hbm2ddl.auto" value="create" />
        </properties>
    </persistence-unit>
</persistence>

'src - main - resources - META-INF - persistence.xml'

위의 경로에 해당 xml파일을 생성하고 <persistence-unit name="xxx"> 태그에 이름을 등록한다. 해당 태그에 들어갈 이름은 추후에 EntityManagerFactory을 생성 시에 매핑될 이름이다.

 

 

import javax.persistence.EntityManagerFactory;

public class JpaTest {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("XXX");

        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {
        
        	// 실행할 코드 //

            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
        emf.close();
}

 

 

-EntityManagerFactory

  • 데이터베이스와의 커넥션 등과 같은 역할을 하며 보통 하나만 생성하여 애플리케이션 전체에서 공유하며 사용한다. 그리고 persistence.xml에서 지정한 name을 통해 연결한다.

 

-EntityManager

  • 특정 작업을 위해 단위별로 데이터베이스에 접근하는 것을 관리한다. EntityManagerFactory의 경우는 Thread-Safe하지만 EntityManager는 쓰레드간의 공유하면 안되며 사용하고 버려야한다.

 

-EntityTransaction

  • JPA의 모든 데이터 변경은 트랜잭션 안에서 수행되어야 한다. 데이터의 변경이 발생했을 때, 문제 없이 성공했다면 commit을 하고 반대로 문제가 발생 했을 시 rollback을 실행한다.

 

[Spring Boot] Spring Initializr를 이용하여 프로젝트 생성하기(Intellij)

 

 

기존에 STS(Spring Tool Suite)나 Eclipse 툴 안에서 Maven이나 Gradle로 프로젝트를 생성했던 경험이 있는데 Spring Initializr 웹 도구를 사용해서 스프링 프로젝트를 만들 수 있다.

(개인적으로 이 방법이 더 편한것 같다.)

 

https://start.spring.io/

 

해당 사이트에 접속하게되면

 

 

이런 화면이 나오는데 차례대로 설정할 부분이 있다.

 

- project

Maven인지 Gradle인지 선택한다. 해당 항목은 프로젝트의 의존성을 관리한다. 과거에는 Maven을 많이 사용했지만 Maven은 xml 태그 형식이라 의존이 많아질수록 코드가 길어지기도 하고 요새는 Gradle을 많이 쓰는것 같아 Gradle로 설정했다.

 

 

- Language

Spring 프레임워크를 통한 Java 웹 개발을 할 것이기 때문에 Java 를 선택한다. Kotlin은 안드로이드 앱 개발에 사용하는 언어이며 Groovy는 Java 에 Python, Ruby, Smalltalk 등의 특징을 더한 동적 객체 지향 프로그래밍 언어이다.

 

(나중에 접해볼 수 있는 기회가 있으면 보면 좋을것 같다.)

 

 

- SpringBoot

해당 프로젝트의 버전을 설정한다. SNAPSHOT이 붙은 건 현재 개발중인 버전이며, M 또한 정상화하여 정식 릴리즈 된 버전이 아니기 때문에 버전만 명시된것을 선택한다.

 

 

- Project Metadata

해당 프로젝트의 이름과 특징을 입력하면 된다. 보통 Group과 Artifact/Name 을 .으로 연결한 값이 패키지이름이 되며 Artifact를 입력하면 Name도 같은 내용으로 등록된다. (개인 프로젝트라면 자유롭게 등록하면 될 듯하다.)

 

Pakaging에는 Jar와 War가 있는데

 

Jar는 자바 어플리케이션이 동작할 수 있도록 프로젝트를 압축한 파일(Class, Java리소스, 라이브러리 등)이며 폴더 구조 등이 자유롭고 JRE로 실행가능하다.

 

War는 서블릿이나 jsp에 배치 할 수 있는 웹 어플리케이션 압축 파일(servlet, jsp, jar, class, xml, html 등)이며 정형화 된 구조를 사용(WEB-INF, META-INF 등)하고 실행 하려면 WEB서버(Apache)나 WAS서버(Tomcat) 등이 필요하다.

 

...

 

현재 회사에서는 프로젝트를 할 때 운영서버에 웹서버, WAS 서버를 설치하고 리소스 파일을 옮기고 난 후, WAS 서버를 리부팅하는 형식으로 작업을 하고있다.

 

하지만 스프링부트는 내장 서버를 제공하고있고 서버 기동 시, 콘솔 창에서 해당 프로젝트 파일을 빌드하여 서버를 부팅시킬 수 있기때문에 Jar을 선택하자.

(빌드 하는 방법도 추후에 정리해야겠다.)

 

 

JAVA 버전은 이클립스를 쓰면서 원래 8버전을 사용했지만 개발자들 사이에서 핫하다는 인텔리제이를 접하게 되면서 11 버전을 사용하게 되었다.

 

(아직 무료버전 사용중.......)

 

추후 이직하게되거나 본격적으로 툴을 사용하게 된다면 정식 버전을 사용할것 같다.

 

 

- Dependencies

ADD DEPENDENCIES를 클릭하여 생성할 프로젝트의 의존성을 추가한다. 추후에 maven이나 gradle로 추가 할 수 있다.

* Spring Web

  • 스프링을 이용하여 웹개발을 하려면 필요한 의존성이다. MVC패턴을 사용하여 RESTful을 포함한 웹 개발이 가능하며 WEB서버 WAS를 기본 포함 컨테이너로 사용한다.

 

* Lombok

  • 어노테이션을 사용하여 반복되는 상용구 코드를 줄일 수 있다. (ex. getter, setter, constructor 등) 여러가지 기능이 있겠지만 일단 엔티티 설계할때 코드를 많이 줄일 수 있을것 같다.

 

* Thymeleaf

  • 자바 템플릿엔진으로써 스프링에서 지향하는 템플릿엔진이다. 기존 jsp보다 속도가 빠르고 확장자 명이 .jsp가 아닌 .html이기 때문에 다른 WAS서버 없이 브라우저에서 정적으로 표기가 가능하다. 

 

Spring Data JPA

  • JPA를 통해서 DB에 데이터를 관리, 유지하는데 기존 JPA의 기능을 좀 더 효율적으로 사용할 수 있다. 아직 사용 안해봤지만 추후에 자주 이용하게 될것 같다.

 

이제 Generate를 클릭하면 zip파일이 생성되고 해당 파일의 압축을 풀면 아래와 같은 파일이 나온다..

 

 

툴은 인텔리제이를 사용한다.

 

https://www.jetbrains.com/ko-kr/idea/download

 

다운로드 IntelliJ IDEA: 우수성과 인체 공학이 담긴 JetBrains Java IDE

 

www.jetbrains.com

초기화면

(프로젝트를 빌드하기전에....)

 

메뉴에서 File - settings - 검색: plugin

 

화면에서는 이미 install했지만 추후 JPA와 Lombok을 사용할거니까 marketplace에서 lombok과 JPA BUddy를 검색하여 install한다.

 

시작 메뉴의 File - Open에서 프로젝트를 지정한 경로의 "build.gradle" 파일을 선택한다.

 

 

....짓는중....

빌드가 성공적으로 완료되면 해당경로에 있는 클래스안의 메인메서드를 실행하면 서버가 올라간다.

 

끝 !


인줄 알았는데

********************************************* 추가 **********************************************

프로젝트 파일을 Generate할 때 Spring Data JPA를 dependency에 추가했는데 JPA를 사용 할 데이터베이스를 설정해주지 않았기 때문에 하위의 오류가 발생한다.

 

"

Description:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class

 

"


{해당 프로젝트 디렉토리} - build.gradle 클릭

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'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

해당 디펜던시를 주석처리 한다.

 

 

추가로 인텔리제이에서 설정해야할 사항이 있다. (안 맞으면 오류가 뜨는경우가 있음...)


Gradle

메뉴에서 File - settings - 검색: gradle

 

1) Settings에서 Download external annotations for dependencies를 체크하고 Build and run suing과 Run tests using을 Gradle -> IntelliJ IDEA로 변경한다.

 

2) Gradle JVM을 환경변수에 등록한 경로의 JDK버전을 프로젝트와 맞는 버전으로 등록한다.

 


성공 !

 

진짜 끝 !

+ Recent posts