[LG CNS AM CAMP 1기] 백엔드 II 3 | SpringBoot

letthem·2025년 1월 22일
0

LG CNS AM CAMP 1기

목록 보기
19/31
post-thumbnail

AOP

@Pointcut("execution(public * ex04..*(..))")
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
           포인트컷 지정자
           
* : 모든 값
.. : 0개 이상

하나의 Pointcut에 여러 Advice가 적용되는 경우

팩토리얼 계산 결과를 캐싱하는 공통 기능을 구현

aop/CacheAspect

package com.test.test1.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

import java.util.HashMap;
import java.util.Map;

@Aspect
public class CacheAspect {
    
    // factorial(10) => 3628800
    // { 10: 3628800 }
    private Map<Long, Object> cache = new HashMap();

    @Pointcut("execution(public * ex04..*(..)")
    public void cacheTarget() {

    }

    // factorial(10) => 3628800

    @Around("cacheTarget()")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        long num = (Long)joinPoint.getArgs()[0];
        if (cache.containsKey(num)) {
            System.out.printf("CacheAspect: cache에서 가져옴 [%d]\n", num);
            return cache.get(num);
        }
        
        Object result = joinPoint.proceed();
        cache.put(num, result);
        System.out.printf("CacheAspect: cache에 추가 [$d]\n", num);
        return result;
    }
}

AppCtxAspect

 @Bean
public CacheAspect cacheAspect() {
    return new CacheAspect();
}

MainForAspect

public class MainForAspect {
    public static void main(String[] args) {
        AbstractApplicationContext ctx = new AnnotationConfigApplicationContext(AppCtxAspect.class);

        Calculator rec = ctx.getBean("recCalculator", Calculator.class);
        System.out.println(rec.factorial(10));
        System.out.println(rec.factorial(10));
        System.out.println(rec.factorial(7));
        System.out.println(rec.factorial(7));
        System.out.println(rec.factorial(5));
        
        ctx.close();
    }
}

실행 결과


Advice의 적용 순서는 설정 클래스에 빈 순서와 동일하게 동작 => 스프링 프레임워크와 자바 버전에 따라 상이
=> @Order 사용해서 명시적으로 순서를 지정

@Aspect
@Order(1)
public class ExeTimeAspect {


@Aspect
@Order(2)
public class CacheAspect {

실행 결과


    @Pointcut("execution(public * ex04..*(..))")
    private void publicTarget() { }
    
    @Around("publicTarget()")

@Around 어노테이션에 execution 명시자를 직접 지정하는 것도 가능하다.

    @Around("execution(public * ex04..*(..))")

but, 위에 걸 쓰는 이유는? => Pointcut을 재사용하기 위해 쓴다.

@Aspect
@Order(1)
public class ExeTimeAspect {
    @Pointcut("execution(public * ex04..*(..))")
    public void publicTarget() {	// 다른 클래스에서 사용할 수 있도록 public
        
    }
    
    @Around("publicTarget()")
    public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {



@Aspect
@Order(2)
public class CacheAspect {
    // factorial(10) => 3628800
    // { 10: 3628800 }
    private Map<Long, Object> cache = new HashMap<>();
    
    /*
    @Pointcut("execution(public * ex04..*(..))")
    public void cacheTarget() {
        
    }
    */
    
    @Around("aspect.ExeTimeAspect.publicTarget()")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {

여러 Aspect에서 공통으로 사용하는 Pointcut이 있는 경우

CommonPointcut

package com.test.test1.aop;

import org.aspectj.lang.annotation.Pointcut;

public class CommonPointcut {
    @Pointcut("execution(public * com.test.test1.ex04..*(..))")
    public void commonTarget() {

    }
}

ExeTimeAspect

package com.test.test1.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;

import java.util.Arrays;

@Aspect
@Order(1)
public class ExeTimeAspect {
    /*
    @Pointcut("execution(public * com.test.test1.ex04...*(..))")
    private void publicTarget() {

    }
    */

    @Around("aop.CommonPointcut.commonTarget()")
    public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.nanoTime();

        try{
            Object result = joinPoint.proceed();
            return result;
        } finally {
            long end = System.nanoTime();

            System.out.printf("%s.%s(%s) 실행결과 = %d \n",
                    joinPoint.getTarget().getClass().getSimpleName(),
                    joinPoint.getSignature().getName(),
                    Arrays.toString(joinPoint.getArgs()),
                    (end - start)
            );
        }


    }
}

@Around("aop.CommonPointcut.commonTarget()")
CacheAspect

package com.test.test1.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;

import java.util.HashMap;
import java.util.Map;

@Aspect
@Order(2)
public class CacheAspect {

    // factorial(10) => 3628800
    // { 10: 3628800 }
    private Map<Long, Object> cache = new HashMap<>();

    /*
    @Pointcut("execution(public * com.test.test1.ex04..*(..)")
    public void cacheTarget() {

    }
    */
    // factorial(10) => 3628800

    @Around("aspect.CommonPointcut.commonTarget()")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        long num = (Long)joinPoint.getArgs()[0];
        if (cache.containsKey(num)) {
            System.out.printf("CacheAspect: cache에서 가져옴 [%d]\n", num);
            return cache.get(num);
        }

        Object result = joinPoint.proceed();
        cache.put(num, result);
        System.out.printf("CacheAspect: cache에 추가 [$d]\n", num);
        return result;
    }
}

@Around("aspect.CommonPointcut.commonTarget()")


스프링 부트(SpringBoot)

스프링 기반 애플리케이션을 빠르고 쉽게 개발

  • 의존성 : Spring Web, Spring Boot DevTools, Lombok


http://localhost:8080/ <= / (Web Document Root) 디렉토리 웹 서버에서 정의한 기본 페이지를 검색

404 Not Found 인 이유: 이 서버에는 아무것도 없기 때문에 !!


컨트롤러 하나 만들어보기 !

package com.test.sample.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    // @RequestMapping("/")
    // @RequestMapping(value = "/", method = RequestMethod.GET)
    @GetMapping("/")
    public String hello() {
        return "Hello World!!!";
    }
}

@RestController

사용자 요청에 대한 응답으로 데이터를 내려보내주는 것

@GetMapping("/") = @RequestMapping("/")

= @RequestMapping(value = "/", method = RequestMethod.GET)

  • "/"로 요청이 들어오면 반환값을 응답해준다.

요청 구조

GET URI HTTP/1.1      <= 요청 시작 -> 방식(method) URL 프로토콜
요청헤더: 헤더값          <= 헤더 ex) Authorization
요청헤더: 헤더값
요청헤더: 헤더값
                      <= 헤더 끝 (아무 내용 없이 한 줄 띄움)
HTTP/1.1 200          <= 응답시작
응답헤더: 헤더값          <= 헤더
name=value&name=value <= 요청 본문 (서버로 전달하는 값으로 방식에 따라 있을 수도 있고 있음)

ex ⬇️

> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.10.1
> Accept: */*
>
< HTTP/1.1 200
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 14
< Date: Wed, 22 Jan 2025 01:49:32 GMT
<

URL ⬇️

http://www.test.com:8080/path/subpath/file?name=value&name=value#abcd
~~~~   ~~~~~~~~~~~~ ~~~~
스킴    호스트주소      포트
프로토콜  도메인/IP

스프링 부트 특징 ⬇️

  • 쉽고 빠르게 개발할 수 있다 !!!!!!!!!!!!! 🩷🩷🩷🩷🩷
  • 자동 설정
    SampleApplication*
    @SpringBootApplication
package com.test.sample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SampleApplication {

	public static void main(String[] args) {
		SpringApplication.run(SampleApplication.class, args);
	}

}
  • 스타터 의존성
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
  • 독립 실행 => 내장 웹 서버(WAS. ex)tomcat)를 포함

  • DevTools => 코드 변경 시 자동 재시작, 라이브 리로딩, 개발 모드 전용 설정 등 사용이 가능

  • 프로덕션 준비 기능 => actuator => 모니터 및 관리 기능을 제공

  • 간편한 설정

  • CLI


스프링 MVC

모델(Model)

  • 애플리케이션의 데이터를 나타내며, 데이터의 구조와 비즈니스 로직을 정의하는 데 사용

  • 도메인 객체, 폼 객체, 데이터 전송 객체, 리포지토리 등으로 구성

    • 도메인 객체(Domain Object)

      • 애플리케이션의 주요 비즈니스 개념을 나타내는 객체 (예: 사용자를 나타내는 User 클래스)
      • 일반적으로 엔티티(Entity)라고도 하며, 데이터베이스의 테이블과 매핑됨
    • 폼 객체(Form Object)

      • 사용자 입력을 받아서 저장하는 객체
      • 주로 웹 애플리케이션에서 폼 데이터를 캡처하고 검증하는데 사용
      • 도메인 객체와 비슷하지만, 주로 사용자 인터페이스와 상호작용하는데 사용
    • 데이터 전송 객체(DTO, Data Transfer Object)

      • 계층 간 데이터를 전달하는데 사용되는 객체
      • 주로 서비스 계층과 프레젠테이션 계층 간 데이터 전송에 사용
      • 도메인 객체와 구분하여 사용되며, 필요한 데이터만 포함하도록 설계
    • 리포지토리(Repository)

      • 데이터베이스와 상호작용하는 계층
      • CRUD 작업을 처리
      • Spring Data JPA와 같은 라이브러리를 사용하여 리포지토리를 쉽게 구현이 가능

뷰(View)

  • 사용자에게 데이터를 표시하고 사용자 인터페이스를 제공하는 역할
  • 모델 데이터를 표시하고, 사용자가 입력한 데이터를 컨트롤러로 전달하는 중간 매개체로 작동
  • JSP, Thymeleaf, FreeMarker, Velocity와 같은 템플릿 엔진을 사용
  • 뷰 리졸버(View Resolver) : 컨트롤러가 반환한 뷰 이름을 실제 뷰로 매핑하는 역할

컨트롤러(Controller)

  • 사용자 요청을 처리하고 적절한 응답을 생성하는 역할

  • 사용자가 요청한 URL을 매핑하여 해당 요청을 처리하는 메서드를 호출하고, 모델 데이터를 생성하거나 수정한 후에 적절한 뷰를 선택하여 응답을 반환

    • 요청 매핑 (Request Mapping)

      • URL 경로와 HTTP 메서드(GET, POST, PUT, DELETE 등)를 매핑하여 해당 요청을 처리하는 메서드를 지정
      • @RequestMapping, @GetMapping, @PostMapping, @PutMapping, @DeleteMapping 등의 애너테이션을 사용
    • 모델 처리 (Model Handling)

      • 모델 객체를 사용하여 데이터를 뷰에 전달
      • Model, ModelMap, ModelAndView를 사용하여 모델 데이터를 처리
    • 뷰 선택 (View Selection)

      • 적절한 뷰 이름을 반환하여, 뷰 리졸버(View Resolver)가 이를 실제 뷰로 매핑하고 렌더링
    • 폼 데이터 처리 (Form Handling)

      • 사용자 입력 폼 데이터를 처리하고 검증
      • @ModelAttribute, @Valid 애너테이션을 사용하여 폼 데이터를 바인딩하고 검증

Dispatcher Servlet에서 핸들러를 조회하고 매핑한다.(1) -> 핸들러가 url 주소를 봐서 해당 컨트롤러를 조회한다. (2)
-> (3) -> (4) -> 컨트롤러 동작 -> 데이터 생성, 조작 -> (5) -> 어떤 View를 써야할까? -> (6) -> (7) -> (8)

우리가 구현해야 하는 것

  • Controller 어떤 메소드로 ?
  • Controller에서 서비스 로직 호출.
  • 서비스 로직 - 인터페이스 구현
    • 하나의 서비스가 여러 DB와 접근할 수도 있다. (트랜잭션 처리도 서비스에서 해줘야 함 !)
  • Data Access Layer : CRUD 구현
  • View Template : HTML과 템플릿 엔진 이용

DAL(Data Access Layer, 데이터 접근 계층) - DB와 관련된 것

애플리케이션의 다른 부분과 저장소 간의 상호작용을 담당하는 계층
비즈니스 로직과 데이터 접근 코드를 분리해 애플리케이션의 유지 보수성과 확장성을 높이는 것이 목적

  • DAO : 상호작용. CRUD 기능 구현. 앞단 서비스로 인해 이용되는 것. (연결)
  • JDBC Template : DB와 연동하려면 인터페이스(명세. Interface)를 정의
  • JDBC Driver : 구현체 = Driver - MySQL JDBC Driver가 있어야 MySQL과 연동할 수 있다.
  • DataSource : JDBC 명세의 일부분. 서버와의 연결

DB 를 만들어보자 !!!

MySQL workbench에서 테이블 생성 ⬇️

create table t_board (
    board_idx 	int(11) 		not null auto_increment 	comment "글 번호", 
    title 		varchar(300)	not null 					comment "제목", 
    contents	text			not null					comment "내용", 
    hit_cnt		smallint(10)	not null default "0" 		comment "조회수", 
    created_dt	datetime		not null 					comment "작성일시", 
    created_id	varchar(300)	not null 					comment "작성자", 
    updator_dt	datetime		null 						comment "수정일시", 
    updator_id	varchar(300)	null 						comment "수정자", 
    deleted_yn	char(1)			not null default "N" 		comment "삭제여부", 
    primary key (board_idx)
);

DAO(Data Access Object)

데이터베이스와 상호작용을 담당하는 객체
개발자는 SQL을 사용해 CRUD 기능을 직접 구현해야 한다.

애플리케이션의 Object      (SQL을 이용해 CRUD)      DB의 Record 단위의 처리
               ~~~~~~             ^                      ~~~~~~
                 |                |                         | 
                 +----------------|-------------------------+
                                  |
                         불일치를 해결해야 함 

개발자가 SQL을 이용해 불일치를 해결할 수 있다.
JPA: 불일치 자동으로 해결해주는 것

Data Source

  • DB와 관련된 연결(Connection) 정보를 담고 있으며, 빈(bean)으로 등록해 인자로 넘겨 준다. -> 이 과정을 통해 스프링은 DataSource로 DB와 연결을 획득
  • JDBC 드라이버 벤더 별로 여러가지가 존재 => 일반적으로 connectionURL, username, password, jdbcDriver 등으로 구성

JDBC(Java DataBase Connectivity)

DB에 접근할 수 있도록 Java에서 제공하는 API
데이터베이스에서 자료를 추가, 검색, 수정, 삭제하는 방법을 제공

🥵 Plain JDBC API 문제점

  • 쿼리 실행 전후에 많은 코드를 작성해야 함
  • 예외 및 트랜젝션 처리
  • 매번 쿼리를 실행할 때 마다 수행

=> JPA , QueryDSL 많이 쓴다.

JDBC Template

Plain JDBC API의 문제점을 해결하기 위해 스프링에서 제공하는 Spring JDBC 접근 방법 중 하나
~~~~~~~~~~~~~~~~~~~~~
DataSource 설정 → Connection 생성 → SQL 작성 → Statement 생성 → Statement 실행 → ResultSet 처리 →  결과 출력 → Exception 처리 → Transaction 처리 → ResultSet 닫기 → Statement 닫고 → Connection 닫고

여기서 SQL 작성과 결과 출력(결과 사용)은 개발자가 해줘야하는 것 !!
나머지는 반복되는 것이고 자동화할 수 있다. => JDBC Template이 수행

=> 반복되는 많은 단순 작업들이 빠질 수 있다 !!

JDBC Driver

  • 자바 프로그램의 요청을 DBMS가 이해할 수 있는 프로토콜로 변환해 주는 클라이언트 측 어댑터
  • DB마다 Driver가 존재하고, 자신이 사용하는 DB에 맞는 JDBC Driver를 사용
  • DataSource를 JDBC Template에 주입하고, JDBC Template은 JDBC Driver를 이용해서 DB에 접근
    • 연결 정보 : DataSource에 있다.
    • JDBC Driver : 구현 클래스
    • JDBC Template이 반복되는 작업을 수행한다.

게시판 프로젝트

DB 연결에 필요한 설정

application.properties

spring.application.name=board

spring.datasource.hikari.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.hikari.jdbc-url=jdbc:mysql://localhost:3306/springbootdb?useUnicode=true&characterEncoding=utf-8&serverTimeZone=Asia/Seoul
spring.datasource.hikari.username=root
spring.datasource.hikari.password=[MySQL password]
spring.datasource.hikari.connection-test-query=select 1

Connection URL Syntax
Configuration Properties

DatabaseConfiguration 생성

DatabaseConfiguration

package board.configuration;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
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 DatabaseConfiguration {

    @Bean
    @ConfigurationProperties(prefix="spring.datasource.hikari")
    HikariConfig hikariConfig() {
        return new HikariConfig();
    }

    @Bean
    DataSource dataSource() {
        DataSource dataSource = new HikariDataSource();
        System.out.println(dataSource);
        return dataSource;
    }
}

MyBatis 연동

💡 MyBatis 란 ?

  • 자바 애플리케이션에서 관계형 데이터베이스와 상호작용하기 위한 퍼시스턴스 프레임워크
  • 객체와 SQL 구문의 자동 맵핑을 제공해 개발 생산성을 향상

DatabaseConfiguration

package 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 DatabaseConfiguration {
    @Autowired
    private ApplicationContext applicationContext;
    
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.hikari")
    HikariConfig hikariConfig() {
        return new HikariConfig();
    }
    
    @Bean
    DataSource dataSource() {
        DataSource dataSource = new HikariDataSource(hikariConfig());
        System.out.println(dataSource);        
        return dataSource;
    }
    
    // SqlSessionFactory: SqlSession 객체를 생성하는 역할을 담당하는 인터페이스
    //                    데이터베이스 연결, 트랜잭션 관리, 매퍼 파일의 위치 등 MyBatis 설정 정보를 포함       
    // SqlSession: MyBatis에서 데이터베이스와 상호작용(SQL 실행, 트랜잭션을 관리, ...)하는 인터페이스
    @Bean
    SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource);
        sessionFactoryBean.setMapperLocations(
            applicationContext.getResources("classpath:/mapper/**/sql-*.xml")
        );
        return sessionFactoryBean.getObject();
    }
    
    // SqlSessionTemplate: MyBatis와 스프링 프레임워크를 통합할 때 사용하는 클래스 
    //                     SqlSesstion 인터페이스를 구현 
    @Bean
    SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}
  • SqlSessionFactory

    • SqlSession 객체를 생성하는 역할을 담당하는 인터페이스
    • 데이터베이스 연결, 트랜잭션 관리, 매퍼 파일의 위치 등 MyBatis 설정 정보를 포함
  • SqlSession

    • MyBatis에서 데이터베이스와 상호작용(SQL 실행, 트랜젝션을 관리, ...)하는 인터페이스
  • SqlSessionTemplate

    • MyBatis와 스프링 프레임워크를 통합할 때 사용하는 클래스
    • SqlSession 인터페이스를 구현

📎 DB와 상호작용할 수 있는 인터페이스 반환 방법
1. 연결 정보가 담겨있는 dataSource를 던져주기. DB 연결
2. sql로 시작하는 xml 파일이 mapper 파일이라는 것을 알려준다.

🚨 class path resource [mapper/] cannot be resolved to URL because it does not exist

=> resources 밑에 mapper 폴더만 만들어주면 해결 된다.


DTO(Data Transfer Object) 생성


BoardDto

package board.dto;

import lombok.Data;

@Data
public class BoardDto {
    private int boardIdx;
    private String title;
    private String contents;
    private int hitCnt;
    private String createdDt;
    private String createdId;
    private String updatorDt;
    private String updatorId;
}

MyBatis 설정을 추가

java의 boardIdx - MyBatis - db의 board_idx
MyBatis가 중간 역할 잘 하도록 설정하자 !

application.properties

mybatis.configuration.map-underscore-to-camel-case=true

DatabaseConfiguration

@Bean
@ConfigurationProperties(prefix = "mybatis.configuration")
org.apache.ibatis.session.Configuration mybatisConfig() {
    return new org.apache.ibatis.session.Configuration();
}

게시판 목록 조회 기능을 구현

Controller 만들기

BoardController

서비스가 가지고 있는 메서드를 호출해야 한다.
그리고 서비스가 반환해주는 값을 반환해줄 것이다.
=> 서비스를 의존한다.

package board.controller;

import board.dto.BoardDto;
import board.service.BoardService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;

import java.util.List;

@Controller
public class BoardController {

    @Autowired
    private BoardService boardService;

    @GetMapping("/board/openBoardList.do")
    public ModelAndView openBoardList() throws Exception {
        ModelAndView mv = new ModelAndView("/board/boardList");

        List<BoardDto> list = boardService.selectBoardList();
        mv.addObject("list", list);

        return mv;
    }
}

Service 인터페이스를 생성


BoardService

package board.service;

import board.dto.BoardDto;

import java.util.List;

public interface BoardService {
    List<BoardDto> selectBoardList();
}

Service 구현 클래스를 생성

BoardServiceImpl

package board.service;

import board.dto.BoardDto;
import board.mapper.BoardMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class BoardServiceImpl implements BoardService {
    @Autowired
    private BoardMapper boardMapper;

    @Override
    public List<BoardDto> selectBoardList() {
        return boardMapper.selectBoardList();
    }

}

Mapper 인터페이스 - 레포지토리 느낌..!

spring 코드랑 sql이랑 연결해주는 역할
BoardMapper

package board.mapper;

import org.apache.ibatis.annotations.Mapper;

import java.util.List;

import board.dto.BoardDto;

@Mapper
public interface BoardMapper {
    List<BoardDto> selectBoardList();
}

맵퍼 생성 및 SQL 작성

sql-board.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="board.mapper.BoardMapper">
    <select id="selectBoardList" resultType="board.dto.BoardDto">
        select board_idx, title, hit_cnt, date_format(created_dt, '%Y.%m.%d %H:%i:%s') as created_dt from t_board
        where deleted_yn = 'N'
        order by board_idx desc
    </select>
</mapper>

@Mapper로 지정하고 namespace와 id를 연결하면 MyBatis가 구현클래스를 자동으로 만들어준다 !! 😆


View 만들기


boardList.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <meta charset="UTF-8">
    <title>게시판</title>
</html>
<body>
    <div class="container">
        <h2>게시판 목록</h2>
        <table class="board_list">
            <colgroup>
                <col width="15%" />
                <col width="*" />
                <col width="15%" />
                <col width="20%" />
            </colgroup>
            <thead>
                <tr>
                    <th scope="col">글번호</th>
                    <th scope="col">제목</th>
                    <th scope="col">조회수</th>
                    <th scope="col">작성일</th>
                </tr>
            </thead>
            <tbody>
                <tr th:if="${#lists.size(list)} > 0" th:each="list : ${list}">
                    <td th:text="${list.boardIdx}">00</td>
                    <td th:text="${list.title}" class="title">게시판 글 제목</td>
                    <td th:text="${list.hitCnt}">00</td>
                    <td th:text="${list.createdDt}">2025.01.22 15:49:00</td>
                </tr>
                <tr th:unless="${#lists.size(list)} > 0">
                    <td colspang="4">조회된 결과가 없습니다.</td>
                </tr>
            </tbody>
        </table>
    </div>
</body>

결과 확인

http://localhost:8080/board/openBoardList.do


MySQL Workbench를 이용해서 t_board 테이블에 테스트 데이터를 추가

insert into t_board (title, contents, created_datetime, creator_id) values ('첫번째 게시글', '첫번째 게시글 입니다.', now(), 'tester');
insert into t_board (title, contents, created_datetime, creator_id) values ('두번째 게시글', '두번째 게시글 입니다.', now(), 'tester');
insert into t_board (title, contents, created_datetime, creator_id) values ('세번째 게시글', '세번째 게시글 입니다.', now(), 'tester');


Thymeleaf

서버 사이드 자바 템플릿 엔진으로 스프링 MVC와 사용
HTML, XML, JS, CSS 등의 템플릿을 처리할 수 있으며, 내추럴 템플릿 방식으로 동작

표준 표현식

https://www.thymeleaf.org/doc/articles/standarddialect5minutes.html

${...} : Variable expressions / 변수 표현식 / 모델 객체의 값을 참조
*{...} : Selection expressions / 선택 변수 표현식 / 선택된 객체의 속성을 참조
#{...} : Message (i18n) expressions / 메시지 표현식 / 메시지 번들을 참조
@{...} : Link (URL) expressions / 링크 (URL) 표현식 / 링크(URL)을 생성
~{...} : Fragment expressions / 조각 표현식 / 템플릿 조각을 참조

속성 처리

th:text 		    텍스트 컨텐츠를 설정 / <p th:text="${dto.title}"></p>
th:href			    링크 URL을 설정 / <a th:href="@{/users/{id}(id=${user.id})}">user detail</a>
				    ⇒  user 객체의 id 속성의 값이 123이라면 
                       <a href="/users/123">user detail</a>
th:each			    컬렉션을 반복 / <tr th:each="user : ${users}"> ... </tr>
th:if / th:unless	조건부 렌더링 / <p th:if="${users.active}">active</p>
                                <p th:unless="${users.active}">deactive</p>

내장 메서드

https://www.thymeleaf.org/doc/tutorials/3.1/usingthymeleaf.html#expression-utility-objects


일반적인 웹 애플리케이션의 흐름

🟡 : 내부 처리 => 컨트롤러 메서드를 통해서 처리
🟨 : 화면 => 뷰를 통해서 처리


스타일 시트를 등록

static/css/board.css
boardList.html

<link rel="stylesheet" th:href="@{/css/board.css}" />

WoW 예뿌당

0개의 댓글

관련 채용 정보