[Spring Boot] MyBatis 설정 및 사용법


 

Spring Boot에서 MyBatis 설정 방법 및 간단 사용법에 대해 알아보겠다.MyBatis는 java에서 쿼리를 실행을 돕는 라이브러리다.


[수행 환경]

Windows 10
IntelliJ 2020.3.4
MariaDB 10.11
java 11
gradle-7.6


[Gradle 설정]
// MyBatis
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.3.1'

 

[Yml 설정]
mybatis:
  type-aliases-package: com.test.main.data.mapper
  mapper-locations: static/mapper/*.xml   #해당 위치에 xml 파일을 둬야 한다. 밑에서 다시 설명하겠다.

 

[테이블 생성]
CREATE TABLE `agree` (
  `SEQ` int(11) NOT NULL AUTO_INCREMENT COMMENT '시퀀스',
  `MEM_ID` varchar(50) NOT NULL COMMENT '아이디',
  `PHONE` varchar(50) DEFAULT NULL COMMENT '전화번호',
  `EMAIL` varchar(50) DEFAULT NULL COMMENT '이메일',
  `CONTENTS_SEQ` int(11) NOT NULL COMMENT '동의서 컨텐츠 아이디',
  `AGREE_YN` varchar(1) DEFAULT NULL  COMMENT '동의여부',
  `SYS_CREATE_DT` datetime DEFAULT current_timestamp() COMMENT '등록한시간',
  `SYS_CREATE_USR` varchar(50) NULL COMMENT '등록한사람',
  `SYS_UPDATE_DT` datetime DEFAULT current_timestamp() COMMENT '등록한시간',
  `SYS_UPDATE_USR` varchar(50) NULL COMMENT '수정한사람',
  PRIMARY KEY (`SEQ`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;

 

[디렉토리 구조 확인하기]
아래 부터 Mybatis를 구동하는데 필요한 맵퍼를 살펴볼 것인데 파일 저장 위치는 위와 같다.
AgreeMapper.xml은 위에 YML에 설정 한 것 처럼 resources/static/mapper에 뒀다. 
둘다 위치가 같지 않으면 실행이 되지 않으니 주의 하여야 한다.

 

[AgreeMapper.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.test.main.data.mapper.AgreeMapper">
    <select id="findAgree" resultType="com.test.main.data.dto.AgreeDto">
        <![CDATA[
            SELECT
            AGREE_YN AS agreeYn,
            CONTENTS_SEQ As contentsSeq
            FROM AGREE
            WHERE MEM_ID = #{memId}
        ]]>
    </select>
</mapper>

 

[Agree Mapper interface 작성]
package com.test.main.data.mapper;

import com.test.main.data.dto.AgreeDto;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
@Mapper
public interface AgreeMapper {
    List<AgreeDto> findAgree(@Param("memId") String memId);
}

 

[Generate POJOs.groovy 파일 내용 작성]
import com.intellij.database.model.DasTable
import com.intellij.database.util.Case
import com.intellij.database.util.DasUtil

/*
 * Available context bindings:
 *   SELECTION   Iterable<DasObject>
 *   PROJECT     project
 *   FILES       files helper
 */

packageName = "com.main.service.data.entity;"
typeMapping = [
  (~/(?i)int/)                      : "long",
  (~/(?i)float|double|decimal|real/): "double",
  (~/(?i)datetime|timestamp/)       : "LocalDateTime",
  (~/(?i)date/)                     : "LocalDateTime",
  (~/(?i)time/)                     : "LocalDateTime",
  (~/(?i)/)                         : "String"
]

FILES.chooseDirectoryAndSave("Choose directory", "Choose where to store generated files") { dir ->
  SELECTION.filter { it instanceof DasTable }.each { generate(it, dir) }
}

def generate(table, dir) {
  def className = javaName(table.getName(), true)
  def fields = calcFields(table)
  new File(dir, className + ".java").withPrintWriter { out -> generate(out, className, fields) }
}

def generate(out, className, fields) {
    out.println "package $packageName"
    out.println ""
    out.println "import lombok.*;"
    out.println "import javax.persistence.*;"
    out.println "import java.time.LocalDateTime;"
    out.println "import java.math.BigDecimal;"
    out.println ""
    out.println "@Entity"
    out.println "@Getter"
    out.println "@Setter"
    out.println "@Builder"
    out.println "@NoArgsConstructor"
    out.println "@AllArgsConstructor"
    out.println "public class $className {"
    out.println ""
    fields.each() {
        out.println "  /**"
        out.println "  * "
        out.println "  */"
        if (it.annos != "") out.println "  ${it.annos}"
        out.println "  private ${it.type} ${it.name};"
        out.println ""
    }
    out.println "}"
}

def calcFields(table) {
  DasUtil.getColumns(table).reduce([]) { fields, col ->
    def spec = Case.LOWER.apply(col.getDataType().getSpecification())
    def typeStr = typeMapping.find { p, t -> p.matcher(spec).find() }.value
    fields += [[
                 name : javaName(col.getName(), false),
                 type : typeStr,
                 annos: ""]]
  }
}

def javaName(str, capitalize) {
  def s = co  m.intellij.psi.codeStyle.NameUtil.splitNameIntoWords(str) 
    .collect { Case.LOWER.apply(it).capitalize() }
    .join("")
    .replaceAll(/[^\p{javaJavaIdentifierPart}[_]]/, "_")
  capitalize || s.length() == 1? s : Case.LOWER.apply(s[0]) + s[1..-1]
}
[서비스 클래스 작성]
package com.test.main.domain.agree;

import com.test.main.data.dto.AgreeDto;
import com.test.main.data.mapper.AgreeMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;

@Slf4j                      //로그 기능 추가 Annotation
@Service                    //서비스 컴포넌트로 spring boot에 등록하는 Annotation
@RequiredArgsConstructor    //final로 지정된 맴버변수에 대해서 생성자 자동 생성
public class AgreeService {

    final private AgreeMapper agreeMapper;

    public List<AgreeDto>  findAgree(String memId) {

        List<AgreeDto> agrees = null;

        try {
            agrees = agreeMapper.findAgree(memId);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return agrees;
    }
}
[Controller 클래스 작성]
package com.test.main.domain.agree;

import com.test.main.data.dto.AgreeDto;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@Slf4j                      //로그 기능 추가 Annotation
@RequestMapping("/v1")
@RestController             //RestController 컴포넌트로 spring boot에 등록하는 Annotation
@AllArgsConstructor         //final로 지정된 맴버변수에 대해서 생성자 자동 생성
@Api(tags = {"20.동의 API"})
public class AgreeController {

    private final AgreeService agreeService;
    
    @GetMapping("/agree")
    @ApiOperation(value = "동의 조회")
    public List<AgreeDto> selAgree (String memId) {
        return agreeService.findAgree(memId);
    }
}

 

반응형

[Spring Boot] JPA 설정 및 사용 방법


 

Java ORM(Object Relational Mapping) 라이브러리인 JPA 설정 및 사용방법에 대해 알아보겠다.


[수행 환경]

Windows 10
IntelliJ 2020.3.4
MariaDB 10.11
java 11
gradle-7.6


[gradle 설정]
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

 

[yml 설정]
spring:
  jpa:
    properties:
         show_sql: true
         format_sql: true

 

[테이블 생성]
CREATE TABLE `agree` (
  `SEQ` int(11) NOT NULL AUTO_INCREMENT COMMENT '시퀀스',
  `MEM_ID` varchar(50) NOT NULL COMMENT '아이디',
  `PHONE` varchar(50) DEFAULT NULL COMMENT '전화번호',
  `EMAIL` varchar(50) DEFAULT NULL COMMENT '이메일',
  `CONTENTS_SEQ` int(11) NOT NULL COMMENT '동의서 컨텐츠 아이디',
  `AGREE_YN` varchar(1) DEFAULT NULL  COMMENT '동의여부',
  `SYS_CREATE_DT` datetime DEFAULT current_timestamp() COMMENT '등록한시간',
  `SYS_CREATE_USR` varchar(50) NULL COMMENT '등록한사람',
  `SYS_UPDATE_DT` datetime DEFAULT current_timestamp() COMMENT '등록한시간',
  `SYS_UPDATE_USR` varchar(50) NULL COMMENT '수정한사람',
  PRIMARY KEY (`SEQ`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;

 

[Entity 클래스 생성]
import lombok.*;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.math.BigDecimal;

@Entity
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Agree {

  /**
  * 
  */
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private long seq;

  /**
  * 
  */
  private String memId;

  /**
  * 
  */
  private long contentsSeq;

  /**
  * 
  */
  private String agreeYn;

  /**
  * 
  */
  private LocalDateTime sysCreateDt;

  /**
  * 
  */
  private String sysCreateUsr;

  /**
  * 
  */
  private LocalDateTime sysUpdateDt;

  /**
  * 
  */
  private String sysUpdateUsr;

}

 

[Repository 클래스 생성]
import org.springframework.data.jpa.repository.JpaRepository;

public interface AgreeRepository extends JpaRepository<Agree, Integer> {

}
[서비스 클래스 생성]
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

@Slf4j                      //로그 기능 추가 Annotation
@Service                    //서비스 컴포넌트로 spring boot에 등록하는 Annotation
@RequiredArgsConstructor    //final로 지정된 맴버변수에 대해서 생성자 자동 생성
public class AgreeService {

    //@RequiredArgsConstructor 선언으로
    //생성자가 자동 생성되면서 컨테이너로 부터 메모리 자동 주입된다.
    final private AgreeRepository agreeRepository;

    /**
     * 임의 데이터 저장
     */
    public void saveAgree() {

        //Agree 데이터 셋팅
        Agree agree = Agree.builder()
                        .agreeYn("Y")
                        .sysCreateDt(LocalDateTime.now())
                        .sysUpdateDt(LocalDateTime.now())
                        .contentsSeq(1)
                        .memId("system")
                        .build();

        //DB에 저장
        agreeRepository.save(agree);
    }
}

 

[Controller 클래스 생성]
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j                      //로그 기능 추가 Annotation
@RequestMapping("/v1")
@RestController             //RestController 컴포넌트로 spring boot에 등록하는 Annotation
@AllArgsConstructor         //final로 지정된 맴버변수에 대해서 생성자 자동 생성
@Api(tags = {"20.동의 API"})
public class AgreeController {

    private final AgreeService agreeService;

    @PostMapping("/agree")
    @ApiOperation(value = "동의 등록")
    public void regAlbum() {
        agreeService.saveAgree();
    }
}

 

[개발한 API 실행]
[DB에 데이터 입력 되었는지 확인]

 

반응형

[Spring Boot] 인텔리제이를 이용한 Entity 자동 생성


 

테이블 생성 후 해당 테이블과 맵핑 되는 Entity를 수동으로 만드는 작업이 지루하고 반복적인 일이 될 수 있다.Intellij를 이용하면 이부분을 자동으로 생성 할 수 있어 지루함을 덜고 더 생산적인 업무를 진행할 수 있으니 알아보도록 하겠다.


[수행 환경]

Windows 10
IntelliJ 2020.3.4
MariaDB 10.11
java 11
gradle-7.6


[테이블 생성]
CREATE TABLE `agree` (
  `SEQ` int(11) NOT NULL AUTO_INCREMENT COMMENT '시퀀스',
  `MEM_ID` varchar(50) NOT NULL COMMENT '아이디',
  `PHONE` varchar(50) DEFAULT NULL COMMENT '전화번호',
  `EMAIL` varchar(50) DEFAULT NULL COMMENT '이메일',
  `CONTENTS_SEQ` int(11) NOT NULL COMMENT '동의서 컨텐츠 아이디',
  `AGREE_YN` varchar(1) DEFAULT NULL  COMMENT '동의여부',
  `SYS_CREATE_DT` datetime DEFAULT current_timestamp() COMMENT '등록한시간',
  `SYS_CREATE_USR` varchar(50) NULL COMMENT '등록한사람',
  `SYS_UPDATE_DT` datetime DEFAULT current_timestamp() COMMENT '등록한시간',
  `SYS_UPDATE_USR` varchar(50) NULL COMMENT '수정한사람',
  PRIMARY KEY (`SEQ`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;

 

[Intellij에서 테이블 확인]
[엔티티 자동생성 디렉토리 이동]
[폴더 이동 이후 새로운 파일 생성]
이동된 폴더에 Generate POJOs.groovy 파일 생성

 

[Generate POJOs.groovy 파일 내용 작성]
import com.intellij.database.model.DasTable
import com.intellij.database.util.Case
import com.intellij.database.util.DasUtil

/*
 * Available context bindings:
 *   SELECTION   Iterable<DasObject>
 *   PROJECT     project
 *   FILES       files helper
 */

packageName = "com.main.service.data.entity;"
typeMapping = [
  (~/(?i)int/)                      : "long",
  (~/(?i)float|double|decimal|real/): "double",
  (~/(?i)datetime|timestamp/)       : "LocalDateTime",
  (~/(?i)date/)                     : "LocalDateTime",
  (~/(?i)time/)                     : "LocalDateTime",
  (~/(?i)/)                         : "String"
]

FILES.chooseDirectoryAndSave("Choose directory", "Choose where to store generated files") { dir ->
  SELECTION.filter { it instanceof DasTable }.each { generate(it, dir) }
}

def generate(table, dir) {
  def className = javaName(table.getName(), true)
  def fields = calcFields(table)
  new File(dir, className + ".java").withPrintWriter { out -> generate(out, className, fields) }
}

def generate(out, className, fields) {
    out.println "package $packageName"
    out.println ""
    out.println "import lombok.*;"
    out.println "import javax.persistence.*;"
    out.println "import java.time.LocalDateTime;"
    out.println "import java.math.BigDecimal;"
    out.println ""
    out.println "@Entity"
    out.println "@Getter"
    out.println "@Setter"
    out.println "@Builder"
    out.println "@NoArgsConstructor"
    out.println "@AllArgsConstructor"
    out.println "public class $className {"
    out.println ""
    fields.each() {
        out.println "  /**"
        out.println "  * "
        out.println "  */"
        if (it.annos != "") out.println "  ${it.annos}"
        out.println "  private ${it.type} ${it.name};"
        out.println ""
    }
    out.println "}"
}

def calcFields(table) {
  DasUtil.getColumns(table).reduce([]) { fields, col ->
    def spec = Case.LOWER.apply(col.getDataType().getSpecification())
    def typeStr = typeMapping.find { p, t -> p.matcher(spec).find() }.value
    fields += [[
                 name : javaName(col.getName(), false),
                 type : typeStr,
                 annos: ""]]
  }
}

def javaName(str, capitalize) {
  def s = co m.intellij.psi.codeStyle.NameUtil.splitNameIntoWords(str) 
    .collect { Case.LOWER.apply(it).capitalize() }
    .join("")
    .replaceAll(/[^\p{javaJavaIdentifierPart}[_]]/, "_")
  capitalize || s.length() == 1? s : Case.LOWER.apply(s[0]) + s[1..-1]
}
[Entity 자동 생성 실행]
[실행 후 확인]

 

[생성된 Entity 확인]
import lombok.*;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.math.BigDecimal;

@Entity
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Agree {

  /**
  * 
  */
  private long seq;

  /**
  * 
  */
  private String memId;

  /**
  * 
  */
  private long contentsSeq;

  /**
  * 
  */
  private String agreeYn;

  /**
  * 
  */
  private LocalDateTime sysCreateDt;

  /**
  * 
  */
  private String sysCreateUsr;

  /**
  * 
  */
  private LocalDateTime sysUpdateDt;

  /**
  * 
  */
  private String sysUpdateUsr;

}
키 값 설정 애노테이션이 존재하지 않아 오류가 날 것이다.

 

 

[생성된 Entity 수정]
/**

*/
@Id  //<-- 해당 컬럼이 ID 임을 지정
@GeneratedValue(strategy = GenerationType.IDENTITY) //시퀀스 값을 DB로 부터 어떤 방식으로 가져올지 지정
private long seq;

 

 

반응형

[Spring Boot] 이미지 캐싱, CDN 기능 설정


 

클라이언트에서 이미지 로딩시 매번 서버에 저장된 이미지를 조회하게 되면 성능이 저하가 생긴다.이를 해결하기위해 spring boot 자체에서 제공하는 이미지 캐싱 기능을 설정 해 보도록 하겠다.


[수행 환경]

Windows 10
IntelliJ
java 11
gradle-7.6


[application.yml 설정]
spring:
  image:
    path: file:///C:Image/  #<----- 마지막에 꼭 / 를 넣어준다.
참고로 linux 환경인 경우 path를 file///home/service/image/ 식으로 넣어줍니다.

 

[WebConfig 클래스 작성]
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.time.Duration;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    private String connPath = "/imagePath/**";   //<------------ 서버를 기동하면 http://localhost/imagePath/test.jpg
    public static String resourcePath;

    @Value("${spring.image.path}")
    public void setPath(String resourcePath) {this.resourcePath = resourcePath;}

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        CacheControl cacheControl = CacheControl.noCache().mustRevalidate().cachePrivate().sMaxAge(Duration.ofDays(1));
        registry.addResourceHandler(connPath)
                .addResourceLocations(resourcePath)
                .setCacheControl(cacheControl);
    }
}

 

[실행 후 확인]
http://127.0.0.1/imagePath/test.jpg
1. 크롬 브라우져를 켠다
2. F12를 누른다. 
3. Network 메뉴를 누른다.
4. http://127.0.0.1/imagePath/test.jpg 호출한다. 그럼 응답으로 200이 나온다.
5. 다시한번 http://127.0.0.1/imagePath/test.jpg 호출 한다 캐싱이 제대로 됐다면 304가 나온다.

 

 

반응형

+ Recent posts