// 이런 코드가 계속 반복되는 거죠...
dataMap.put( "name", StringUtil.equals(dataBean.getPrivateYn(), "Y") ? "비공개": dataBean.getName() );
dataMap.put( "question", StringUtil.equals(dataBean.getPrivateYn(), "Y") ? "비공개": dataBean.getQuestion() );
dataMap.put( "title", StringUtil.equals(dataBean.getPrivateYn(), "Y") ? "비공개":dataBean.getTitle() );

이번 유지보수 업무를 하다 중복되는 get 메소드를 중복해서 호출해야하는 상황이 있었다.

똑같이 privateYn이 "Y"인지 확인하는 로직인데, 뒤에 불러와야 하는 get 메소드만 getTitle(), getCtgNm1() 등등 다 달랐다. 

 

'어떻게 하면 이 똑같은 로직을 한 번에 해결할 수 있을까?' 고민하다가, 자바 8에 추가된 신박한 기능들을 알게 되어서 정리해봤습니다.

 


1. Supplier<String>: 요청하면 값을 '공급'해줌

Supplier는 자바 8에서 추가된 함수형 인터페이스라는 인데,  말 그대로 '공급자' 역할을 한다.

  • 하는 일: 아무런 입력값 없이, 요청이 오면 특정 타입의 값을 '공급'해줌
  • 사용법: Supplier<T> 형태로 쓰는데, <T>에 우리가 받을 값의 타입을 넣어주면 됨.
    • Supplier<String>: 문자열(String)을 공급해 줘!
    • Supplier<Integer>: 숫자(Integer)를 공급해 줘!

이 안에는 get()이라는 메소드 하나만 있는데, 이 메소드를 호출하면 딱! 정해진 값을 뱉어낸다.

 

나는 그래서 중복된 코드를 줄이기 위해 아래와 같이 코드를 만들었다! 

import java.util.function.Supplier;

public String getPublicValue(ExampleBean bean, Supplier<String> valueSupplier) {
    if ("Y".equals(bean.getPrivateYn())) { // privateYn이 Y면
        return "비공개";                  // 무조건 "비공개"를 반환해!
    } else {                              // 아니면
        return valueSupplier.get();     // Supplier가 공급해주는 값을 반환해줘!
    }
}

2. Supplier는 사용하는 람다! 그 중, :: (메소드 레퍼런스)

 

::는 "메소드 레퍼런스" 라고 부르는 문법인데, 쉽게말해 () -> dataBean.getTitle() 같은 람다식을 더 간단하게 쓸 수 있게 해준다! 

 

람다식(Lambda) 메소드 레퍼런스
() -> dataBean.getTitle() dataBean::getTitle
() -> new ArrayList<String>()
ArrayList::new

 

'객체::메소드' 형태로 사용하는데, " 이 객체의 이 메소드를 쓸꺼야! " 라고 딱 지정해주는 느낌이다! 

위의 Supplier<String> 과 함께 쓰면 아래처럼 위에서 만든 메소드를 사용할 수 있다. 

dataMap.put("title", getPublicValue(dataBean, dataBean::getTitle));
dataMap.put("name", getPublicValue(dataBean, dataBean::getName));
dataMap.put("question", getPublicValue(dataBean, dataBean::getQuestion));

// 람다식을 사용한 예시: 람다식으로는 복잡한 로직도 전달 가능
dataMap.put("question", getPublicValue(dataBean, 
	() -> StringUtils.replace(dataBean.getQuestion(), "\n", " ")));

 

 


4.  왜 Supplier와 ::를 사용해야 할까?

Supplier와 메소드 레퍼런스를 활용하면 다음과 같은 중요한 이점을 얻을 수 있다.

  1. 코드 간결성 및 가독성: 반복되는 코드를 제거하고, 로직을 한 곳에 모아 코드를 더 읽기 쉽게 만듭니다.
  2. 재사용성: 특정 기능을 추상화하여 다양한 곳에서 재사용할 수 있는 유연한 코드를 만들 수 있습니다.
  3. 유지보수성: 로직 변경이 필요할 때 한 곳만 수정하면 되므로, 유지보수가 훨씬 용이해집니다.

 

정말 파도파도 끝이없는 라이브러리들과..함수들... 

매번 할때마다 새롭다...

 

 

 

반응형
LIST

🧩 React 도입 시 백엔드는 어떻게 구성하면 좋을까? (Spring MVC vs React 구조 정리)

이번 프로젝트에서는 기존에 사용하던 Spring Framework + JSP 조합에서 프론트엔드를 React로 전환하는 작업을 하게 되었다.

React는 프론트와 백이 완전히 분리되는 구조라 처음에는 기존의 MVC 패턴과 어떻게 연결될지에 대해 꽤 혼란이 있었다.
특히 "React를 쓰면 기존의 Model-View-Controller는 어떻게 되는 거지?"라는 질문이 생겼다.

그래서 React를 도입하면서 기존 Spring MVC 구조와 어떻게 달라지는지를 간단히 정리해봤다.
(이 부분은 나중에 또 다른 프로젝트에서 참고할 일이 있을 것 같아서!)


✅ 기존 방식: Spring MVC + JSP

기존에는 브라우저에서 요청이 들어오면 Java(Spring) 서버가 다음과 같은 순서로 처리했다:

요청 → Controller → Service → Repository → DB
                        ↓
                     Model (DTO)
                        ↓
                    View (JSP로 HTML 렌더링)
 
  • 서버가 직접 HTML을 만들고, 그걸 응답으로 내려주는 구조.
  • 흔히 말하는 서버 사이드 렌더링(SSR) 방식.

⚛️ React로 전환하면?

React는 SPA(Single Page Application) 구조로, 브라우저가 HTML + JS를 내려받은 뒤
브라우저에서 동적으로 화면을 렌더링하게 된다.
이때 서버는 오직 데이터만 JSON으로 전달하는 역할.

React (JS/HTML 렌더링 + 사용자 이벤트 처리)
         ↑
    REST API 요청
         ↓
Java Spring 서버 (Controller → Service → Repository → DB)
  • 서버는 JSP를 사용하지 않고, 오직 JSON 형태로 데이터를 응답하는 API 서버로 사용.
  • 프론트와 백이 도메인도 다르고, 개발도 완전히 분리된 구조가 됨.

🔍 그럼 MVC 패턴은 어디로?

처음엔 "React에서는 Controller나 Model이 없잖아?"라고 생각했는데, 실제로는 다음처럼 대응된다.

역할 Spring (기존 MVC) React
Model DTO, Entity 상태(state), useState, Redux 등
View JSP JSX 컴포넌트
Controller @Controller 클래스 이벤트 핸들러, API 호출 함수 등
 

→ React 내부적으로도 MV*-like 구조가 적용된다고 보면 된다.


🛠️ React + Spring Boot 연동 구조 예시

 
📦 시스템 구조

[React Frontend] ←→ [Spring Boot API 서버] ←→ [DB]
      ↑                        ↑
   CSR 방식               REST API (JSON)

📁 프론트 (React)

  • axios나 fetch로 Spring API 서버에 데이터 요청
  • useEffect, useState, Redux, Context 등으로 상태 관리
  • HTML 렌더링은 JSX
// React에서 API 호출 예시
import axios from 'axios';

useEffect(() => {
  axios.get('http://localhost:8080/api/users')
    .then(res => setUsers(res.data))
    .catch(err => console.error(err));
}, []);

📁 백엔드 (Spring Boot)

  • @RestController로 JSON 응답 제공
  • @GetMapping, @PostMapping 등 RESTful 방식으로 API 구성
  • View(JSP)는 제거, 템플릿 엔진도 사용하지 않음
@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping
    public List<UserDto> getUsers() {
        return userService.getAllUsers();
    }
}

📦 실제 프로젝트 구조 예시

📂 backend-spring
│   └─ src/main/java/...
│   └─ build.gradle / pom.xml
│   └─ application.yml
│
📂 frontend-react
│   └─ src/
│       └─ components/
│       └─ pages/
│       └─ api/axios.js
│   └─ package.json
│   └─ vite.config.js / cra 설정
 
  • 완전히 두 프로젝트로 분리되어 있음
  • 배포 시에는 React를 빌드해서 정적 파일로 만든 뒤 Spring에서 서빙하거나, Nginx로 React 따로 배포도 가능

🧠 정리

  • React를 쓰면 프론트와 백이 명확히 분리된 구조가 된다
  • 백엔드는 View를 만들지 않고, 오직 API만 제공
  • 프론트도 내부적으로 상태 관리 시스템을 통해 Model 역할을 수행
  • 결과적으로 "프론트의 MVC + 백엔드의 MVC"가 조화롭게 동작하는 구조로 발전함

이런 구조가 처음에는 다소 생소할 수 있지만, 한 번 개념이 잡히면 각 레이어의 책임이 명확해서
유지보수나 확장 측면에서 훨씬 깔끔해지는 느낌이다.

반응형
LIST

프로젝트 중 상사가 리팩토링한 코드 중에서 immutableMap 을 사용한 부분이 있었다. 

몇가지 지정된 객체들을 특정 메소드에 파라미터로 보내는 과정에서 해당 맵을 사용했는데 

처음 보는 맵의 종류라 정리하게 되었다. 


ImmutableMap 이란?

Map 의 종류이지만 이 맵은 한 번 만들어지면 그 안의 키와 값이 변경될 수 없는 특징이 있다.

즉, 이미 지정해둔 키와 값의 쌍을 수정하거나 삭제할 수 없으며, 새로운 키-값 쌍을 추가할 수도 없다.


⭐예제

/**첫번째 방법*/
ImmutableMap<String, Object> map = ImmutableMap.of("bean", _bean, "regDt", _bean.getRegDt());
         
/**두번째 방법*/        
ImmutableMap<String, Object> map2 = ImmutableMap.<String,Object>builder()
                                    .put("bean", _bean)
                                    .put("regDt", _bean.getRegDt())
                                    .build();

ImmutableMap 도 map 의 종류이기에 선언이나 사용법은 비슷하나, 위에 두가지로 가장 많이 쓰인다. 

1. 첫번째 방법 : 최대 10쌍의 값을 정의할 수 있어 간단하게 정의할 경우 쓰인가.

2. 두번째 방법 : 더 많은 쌍의 값을 정의할때 더 유연하고 가독성이 좋기에 많은 쌍을 정의할 때 쓰인다. 


⭐일반 Map 과의 차이점

 

  • 수정 불가: ImmutableMap는 한 번 생성되면 수정할 수 없다. put, remove 등의 메서드 사용 불가능
  • 생성 시점: ImmutableMap는 생성할 때 모든 키-값 쌍을 정의해야함.

 

/**맵 정의*/
ImmutableMap<String, Object> map = ImmutableMap.of("bean", _bean, "regDt", _bean.getRegDt());

/**수정 및 삭제 불가 */
// map.put("regDt", "2025-01-01"); // 컴파일 에러
// map.remove("bean"); // 컴파일 에러

⭐ImmutableMap 사용이유 

 

  • 안전성: 불변이기 때문에 데이터가 변경될 염려가 없다. 특히 여러 군데에서 같은 데이터를 참조할 때 유용하다.
  • 성능: 동기화가 필요 없기 때문에, 여러 스레드가 동시에 읽을 때 성능이 향상된다.
  • 예측 가능성: 데이터가 변경되지 않기 때문에 항상 같은 값을 유지한다. 

중요한 점

  • 맵 자체는 변경 불가: ImmutableMap에 추가, 수정, 삭제할 수 없습니다.
  • 내부 객체는 변경 가능: ImmutableMap에 저장된 객체가 가변 객체라면, 그 객체의 내부 상태는 변경될 수 있습니다.

 

 

반응형
LIST

프로젝트에서 코드를 리팩토링 하는 과정에서 

같은 연산을 하지만은 상속받는 bean 이 다른 두개의 메소드가 있었다. 

물론 다른 패키지 내부의 dao 에서 연산을 하는 것이라 별다른 리팩토링 필요성을 못느꼈는데 

같은 연산을 두 패키지 뿐만 아니라 다른 곳에서도 쓰일 가능성을 보고 Util로 옮기며 intanceof 연산자를 사용하게 되었다. 


intanceof 연산자란? 

사용하는 이유 : 개체가 어떤 클래스인지, 어떤 클래스를 상속받았는지, 즉 인스턴스의 실제 타입을 알아보기 위해 사용

결과 : boolean (true 또는 false) 

⭐문법

object instanceOf type

object 가 내가 확인하고 싶은 객체이고 type이 비교 대상이다. 

object == type 또는 type을 상속받는 다면 true, 아닐경우 false 를 반환한다. 


⭐예제

class Parent{}

class Child extends Parent{}

public class intanceOfExample {
    public static void main(String[] args){
        Parent parent = new Parent();
        Child child = new Child();

        System.out.println( parent instanceof Parent );  // true
        System.out.println( child instanceof Parent );   // true
        System.out.println( parent instanceof Child );   // false
        System.out.println( child instanceof Child );   // true
    }
}

child 는 부모님 parent 를 상속받기에 true 이지만, parent는 child를 상속받지 않기에 false 가 나온다. 

물론 child끼리 parent 끼리 instanceof 연산자로 확인할 경우에는 같은 클래스이기에 true 가 나온다. 


이 처럼 한 메소드에서 혹 특정 bean 을 특정지어서 분기처리를 해야한다면 유용하게 쓰일 수 있는 연산자같다. 

반응형
LIST

+ Recent posts