2026. 4. 29. 12:55ㆍ2026 상반기 전공과목 공부/Java 프레임워크
Spring Boot 회원 관리 시스템 구현 및 계층별 아키텍처 분리
- 서비스 계층 인터페이스 및 구현체 분리 인터페이스 정의: MemberService를 생성하여 기능 규격을 정의하고 구현체 분리를 통한 유연한 아키텍처 기반 마련 빈 등록 설정: @Service 어노테이션을 구현체에 명시하여 스프링 컨테이너 관리 대상임을 알리고 서비스 로직 실행 환경 구축
- 의존성 주입(DI) 방식 고도화 및 Lombok 활용 생성자 주입 적용: @RequiredArgsConstructor를 사용해 final 필드에 대한 의존성을 자동으로 주입받도록 설정하여 코드 간결화 도메인 최적화: Member 클래스에 @Data와 @Builder 어노테이션을 적용하여 객체 생성의 가독성 및 데이터 접근성 향상
- 타임리프 연동 및 회원 등록 폼 구현 객체 바인딩: th:object와 th:field를 활용해 HTML 폼 데이터와 자바 객체(Member)를 실시간으로 연결 데이터 전달: @ModelAttribute를 사용하여 화면에서 입력한 정보를 컨트롤러에서 객체 형태로 안정적으로 수신
- 데이터베이스 스키마 정의 및 영속성 계층 구축 테이블 설계: MySQL 데이터베이스에 tbl_member 테이블을 생성하고 회원 관리를 위한 기초 데이터(admin) 입력 JDBC 연동: DataSource와 JdbcTemplate을 연결하여 실제 DB와 통신할 수 있는 리포지토리 환경 구축
- 리포지토리 구현 및 전체 계층 연동 데이터 저장: MemberRepository 인터페이스 및 구현체를 작성하고 SQL 실행을 통해 실제 데이터 저장 로직 완성 최종 흐름 연결: 컨트롤러-서비스-리포지토리 간의 호출 구조를 완성하고 실행 과정에서 발생한 테이블 명 불일치 등 오류 수정
[요약] Spring Boot 회원 관리 시스템 구현 및 계층별 아키텍처 구축
- 인터페이스 기반 서비스 설계로 비즈니스 로직의 확장성 및 유연성 확보
- 생성자 주입 및 Lombok 적용을 통한 코드 최적화 및 결합도 감소
- 타임리프 바인딩 기능을 활용한 사용자 입력 데이터 처리 구조 구축
- MySQL 및 JdbcTemplate 연동으로 영속성 계층의 데이터 저장 기능 구현
- 각 계층 간 유기적인 데이터 흐름 완성 및 시스템 안정화 처리 완료
[Spring Core] 제어의 역전(IoC)과 의존성 주입(DI) 총정리
1. 소프트웨어 설계의 배경과 목표
- 강한 결합(Tightly Coupling): 관련 없는 기능 간 높은 의존성으로 인해 유지보수 및 수정이 난해함.
- 느슨한 결합(Loosely Coupling): 요소 간 의존성을 줄여 유연성을 확보하는 설계 지향.
- 학습 목표: Spring Core의 핵심인 IoC와 DI를 통해 객체 간 의존성을 관리하는 방법 습득.
1. 강한 결합 vs 느슨한 결합 (The Battery Analogy)- 강한 결합 (Tightly Coupled): 일체형 배터리 스마트폰. 배터리가 고장 나면 폰 전체를 뜯거나 버려야 함.
(수정이 어렵고 의존적임) - 느슨한 결합 (Loosely Coupled): 탈착형 배터리나 보조 배터리.
배터리가 수명이 다하면 새 배터리로 그냥 갈아 끼우면 끝. (수정이 쉽고 독립적임) - 소프트웨어에서의 목표: 부품(클래스)을 언제든 갈아 끼울 수 있는 '탈착형' 구조를 만드는 것.
- 강한 결합 (Tightly Coupled): 일체형 배터리 스마트폰. 배터리가 고장 나면 폰 전체를 뜯거나 버려야 함.
2. Spring Core의 정의 및 구성
- 정의: Spring Framework의 필수 기술 집합이자 기반 기술.
- 주요 구성:
- IoC 컨테이너: 객체의 생명주기 관리 및 의존성 주입 담당.
- POJO(Plain Old Java Object) 지원: 특정 프레임워크에 종속되지 않는 순수 자바 객체 활용.
- AOP(관점 지향 프로그래밍): 핵심 로직과 공통 기능을 분리하여 모듈성 강화.
- 기타: 이벤트 처리, 리소스 관리, i18n(국제화), 유효성 검사, SpEL 등.
3. 제어의 역전 (IoC, Inversion of Control)
- 개념: 개발자가 가진 객체의 제어권(생성, 호출 등)을 프레임워크(컨테이너)로 넘기는 디자인 패턴.
- 전통적 방식: 개발자 코드가 라이브러리를 직접 호출하고 관리함.
- IoC 방식: 프레임워크가 개발자의 코드를 호출하여 흐름을 제어함.
- 구현 방법: DI(의존성 주입), 서비스 로케이터 패턴, 전략 패턴 등.
3. IoC (제어의 역전) : "요리사는 요리만 하세요"- 전통적 방식: 요리사가 시장에 가서 "A 농장 고기를 사오자"라고 직접 결정함. (개발자가 new로 직접 객체 생성)
- IoC 방식: 요리사는 주방에 가만히 있고, 주방 매니저(Spring)가 재료를 딱딱 갖다 줌.
요리사는 어떤 농장 고기인지 신경 안 쓰고 오직 '요리'라는 본연의 역할에만 집중함. - 결론: 객체의 생성과 관리를 개발자가 아닌 스프링 컨테이너가 대신 해주는 것.
- 원래라면 요리사(개발자)가 재료(객체)를 직접 사고, 손질하고, 관리해야 했지만, IoC가 도입되면 상황이 바뀐다.
4. 의존성 주입 (DI, Dependency Injection)
- 정의: 계층/클래스 간 필요한 의존 관계를 컨테이너(ApplicationContext)가 자동으로 연결하는 것.
- 주요 주입 방법:
- 필드 주입 (Field Injection): 멤버 변수에 @Autowired를 사용하여 직접 주입. 코드 간결하나 외부 변경이 어려움.
- 생성자 주입 (Constructor Injection): 객체 생성 시점에 의존성 주입.
불변성 보장, 순환 참조 방지 등으로 가장 권장되는 방식. - 세터 주입 (Setter Injection): setXxx() 메서드를 통해 주입. 선택적인 의존성 주입 시 활용.
- Lombok 활용: @RequiredArgsConstructor 사용 시 final 필드에 대한 생성자 자동 생성 및 주입 가능.
4. DI (의존성 주입) : "배달 왔습니다!"주입 방법 3가지 (비유)- 생성자 주입 (가장 권장): "식당 오픈할 때 아예 냉장고를 들여놓고 시작함."
처음에 한 번 딱 세팅되면 바꿀 수 없어 안전함. (불변성 보장) - 세터(Setter) 주입: "영업 중간에 재료가 떨어지면 그때그때 보충함."
나중에 바꿀 수 있는 유연함이 있지만, 재료가 안 왔는데 요리를 시작할 위험이 있음. - 필드 주입: "요리사 주머니에 재료를 몰래 찔러 넣어줌." 코드는 짧지만,
외부에서 재료를 확인하거나 바꾸기 힘들어 권장하지 않음.
- 생성자 주입 (가장 권장): "식당 오픈할 때 아예 냉장고를 들여놓고 시작함."
- IoC를 실제로 구현하는 방법이 바로 DI입니다. 필요한 객체를 외부에서 '주입'해 주는 것이죠.
5. @Autowired의 원리 및 특징
- 역할: 스프링 컨테이너가 관리하는 빈(Bean) 중 적절한 객체를 찾아 자동으로 주입.
- 매칭 순서: 기본적으로 타입(Type) 매칭 수행 → 중복 시 이름(Name)으로 매칭.
- Spring 4.3 이후 변화: 클래스 내 생성자가 하나뿐이라면 @Autowired 생략 가능.
6. Spring Bean과 POJO
- Spring Bean: Spring 컨테이너(ApplicationContext)가 생성하고 관리하는 인스턴스.
- 일반적으로 서비스, 레파지토리 레이어 클래스들이 대상임.
- Java Bean: new 연산자로 개발자가 직접 생성하는 일반적인 인스턴스 (예: DTO, Entity).
- 등록 방법:
- 어노테이션: @Component, @Service, @Repository, @Controller.
- 설정 클래스: @Configuration 내 @Bean 정의.
- XML: <bean id="..." class="..." /> 파일 설정.
6. Spring Bean : "관리 대상 리스트"- 스프링이라는 주방 매니저의 장부에 등록된 '공식 재료/도구'들.
- @Component, @Service 같은 어노테이션을 붙이면 "이건 매니저님이 관리해 주세요!"라고 등록하는 것
- 스프링이 관리하는 객체를 특별히 '빈(Bean)'이라고 부릅니다.
7. 자동 와이어링 (Auto-wiring) 방식 (XML 기준)
- byType: 필드 타입과 일치하는 빈을 찾아 주입 (기본 방식).
- byName: 필드명/세터명과 빈의 ID가 일치하는 경우 주입.
- constructor: 생성자의 매개변수 타입과 일치하는 빈 주입.
- autodetect: 생성자 방식을 먼저 시도하고 실패 시 타입 방식 수행.
- 모호성 해결:
- primary="true": 여러 후보 중 최우선 빈 선정.
- autowire-candidate="false": 해당 빈을 자동 주입 대상에서 제외.
8. 의존성 주입(DI)의 기대효과
- 유지보수성 향상: 코드 수정 없이 설정 변경만으로 구현체(JDBC ↔ JPA 등) 교체 가능.
- 테스트 용이성: 가짜 객체(Mock) 주입을 통한 단위 테스트가 수월함.
- 객체 지향적 설계: 인터페이스 기반 설계를 통해 결합도는 낮추고 응집도는 높임.
정리 : Spring Core는 IoC와 DI를 통해 객체 간의 복잡한 연결을 자동화하여
개발자가 비즈니스 로직에만 집중할 수 있게 돕는 핵심 프레임워크임.
MemberService interface, MemberServiceImpl를 만듬

MemberService
package indusw.sba.admin2024001910a.service;
import indusw.sba.admin2024001910a.domain.Member;
public interface MemberService {
int create(Member m);
Member readOne(Member m);
}
MemberServiceImpl
package indusw.sba.admin2024001910a.service;
import indusw.sba.admin2024001910a.domain.Member;
import org.springframework.stereotype.Service;
@Service // Service 형 Spring Bean 임을 컴파일러/컨테이너에 알림
public class MemberServiceImpl implements MemberService{
@Override
public int create(Member m) {
System.out.println("MemberService - Create");
return 0;
}
@Override
public Member readOne(Member m) {
return null;
}
}


MemberController 코드 수정
package indusw.sba.admin2024001910a.controller;
import indusw.sba.admin2024001910a.domain.Member;
import indusw.sba.admin2024001910a.service.MemberService;
import indusw.sba.admin2024001910a.service.MemberServiceImpl;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/members")
public class MemberController {
MemberService memberService = new MemberServiceImpl();
@GetMapping("/create")
public String create(Model model) {
Member member = new Member();
memberService.create(member);
return "/main/index";
}
}
실행해서 http://localhost:9000/members/create로 접속하면
로그에 MemberService - Create가 나오게 된다
MemberServiceImpl2를 만들고
package indusw.sba.admin2024001910a.service;
import indusw.sba.admin2024001910a.domain.Member;
public class MemberServiceImpl2 implements MemberService{
@Override
public int create(Member m) {
return 0;
}
@Override
public Member readOne(Member m) {
return null;
}
}
MemberController
package indusw.sba.admin2024001910a.controller;
import indusw.sba.admin2024001910a.domain.Member;
import indusw.sba.admin2024001910a.service.MemberService;
import indusw.sba.admin2024001910a.service.MemberServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/members")
public class MemberController {
private final MemberService memberService;
@Autowired // Spring 4.3이후 생성자가 하나인 경우 생략 가능
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
@GetMapping("/create")
public String create(Model model) {
Member member = new Member();
memberService.create(member);
return "/main/index";
}
}
spring boot에서는 아래 코드로도 가능하다
@Autowired 생략, @RequiredArgsConstructor사용해서 생성자(의존성) 주입 (Constructor Injection)을 생략 가능하다
package indusw.sba.admin2024001910a.controller;
import indusw.sba.admin2024001910a.domain.Member;
import indusw.sba.admin2024001910a.service.MemberService;
//import indusw.sba.admin2024001910a.service.MemberServiceImpl;
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.beans.factory.annotation.Qualifier;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/members")
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
//@Autowired // Spring 4.3이후 생성자가 하나인 경우 생략 가능 , DI(Dependency Injection) - Constructor(생성자) 활용
/*
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
*/
@GetMapping("/reg-form")
public String getRegForm(Model model) {
return "/members/register";
}
@GetMapping("/create")
public String create(Model model) {
Member member = new Member();
memberService.create(member);
return "/main/index";
}
}
MemberRepository 생성
package indusw.sba.admin2024001910a.repository;
public interface MemberRepository {
}
MemberRepositoryImpl 생성
package indusw.sba.admin2024001910a.repository;
import org.springframework.stereotype.Repository;
@Repository
public class MemberRepositoryImpl implements MemberRepository{
}
MemberController 수정
package indusw.sba.admin2024001910a.controller;
import indusw.sba.admin2024001910a.domain.Member;
import indusw.sba.admin2024001910a.repository.MemberRepository;
import indusw.sba.admin2024001910a.service.MemberService;
//import indusw.sba.admin2024001910a.service.MemberServiceImpl;
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.beans.factory.annotation.Qualifier;
//import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/members")
//@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
// private final MemberRepository memberRepository;
//@Autowired // Spring 4.3 이후 생략 가능, DI(Dependency Injection) - Constructor(생성자) 활용
public MemberController(MemberService memberService, MemberRepository memberRepository) {
this.memberService = memberService;
// this.memberRepository = memberRepository;
}
@GetMapping("/reg-form")
public String getRegForm(Model model) {
return "/members/register";
}
@PostMapping("/register")
public String postRegister(Model model) {
Member member = new Member();
memberService.create(member);
return "/main/index";
}
}
MemberRepositoryImpl 수정
package indusw.sba.admin2024001910a.service;
import indusw.sba.admin2024001910a.domain.Member;
import indusw.sba.admin2024001910a.repository.MemberRepository;
import org.springframework.stereotype.Service;
@Service // Service 형 Spring Bean 임을 컴파일러/컨테이너에 알림
public class MemberServiceImpl implements MemberService {
private MemberRepository memberRepository;
public MemberRepository getMemberRepository(MemberRepository memberRepository) {
return memberRepository;
}
@Override
public int create(Member m) {
System.out.println("MemberService - Create");
return 0;
}
@Override
public Member readOne(Member m) {
return null;
}
}
sb.html
../members/register.html을 ../members/reg-form로 변경
<a class="collapse-item" href="../members/reg-form">Register</a>

실행하면
register를 누르면
http://localhost:9000/members/reg-form 로 링크가 변경된 모습
register.html
상단 부분을 변경
<html lang="en" xmlns:th="http://www.thymeleaf.org">
Member.java 코드 수정
package indusw.sba.admin2024001910a.domain;
import lombok.*;
import org.springframework.context.annotation.Bean;
//@Getter
//@Setter
//@NoArgsConstructor
//@AllArgsConstructor
//@EqualsAndHashCode
//@ToString
@Data
@Builder
public class Member {
private long id;
private String name;
private String email;
private String pw;
}
MemberController
package indusw.sba.admin2024001910a.controller;
import indusw.sba.admin2024001910a.domain.Member;
import indusw.sba.admin2024001910a.repository.MemberRepository;
import indusw.sba.admin2024001910a.service.MemberService;
//import indusw.sba.admin2024001910a.service.MemberServiceImpl;
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.beans.factory.annotation.Qualifier;
//import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/members")
//@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
// private final MemberRepository memberRepository;
//@Autowired // Spring 4.3 이후 생략 가능, DI(Dependency Injection) - Constructor(생성자) 활용
public MemberController(MemberService memberService, MemberRepository memberRepository) {
this.memberService = memberService;
// this.memberRepository = memberRepository;
}
@GetMapping("/reg-form")
public String getRegForm(Model model) {
model.addAttribute("dto", Member.builder().build());
return "/members/register";
}
@PostMapping("/register")
public String postRegister(Model model) {
Member member = Member.builder().build();
// new 와 동일한 기능이지만, 필드를 명시적으로 지정하므로 모호성을 줄임
Member member = new Member();
memberService.create(member);
return "/main/index";
}
}
register.html 39줄 부터 수정
<form th:action="@{'/members/register'}" th:method="POST" th:object="${dto}"
action="#" class="user">
<div class="form-group row">
<div class="col-sm-6 mb-3 mb-sm-0">
<input type="text" name="id" class="form-control form-control-user" id="exampleFirstName"
placeholder="ID" th:field="*{id}">
</div>
<div class="col-sm-6">
<input type="text" name="name" class="form-control form-control-user" id="exampleLastName"
placeholder="Name" th:field="*{name}">
</div>
<div class="form-group row">
<div class="col-sm-6 mb-3 mb-sm-0">
<input type="password" name="pw" class="form-control form-control-user"
id="exampleInputPassword" placeholder="Password" th:field="*{pw}">
</div>
<div class="col-sm-6">
<input type="password" name="email" class="form-control form-control-user"
id="exampleRepeatPassword" placeholder="RepeatPassword">
</div>
</div>
</div>
<div class="form-group">
<input type="email" name="email" class="form-control form-control-user" id="exampleInputEmail"
placeholder="Email" th:field="*{email}">
</div>
<input type="submit" href="login.html" class="btn btn-primary btn-user btn-block">
Register Account
</input>
MemberController
postRegister부분 코드 수정
@PostMapping("/register")
public String postRegister(@ModelAttribute("dto") Member dto, Model model) {
System.out.println(dto.getEmail());
// new와 동일한 기능이지만, 필드를 명시적으로 지정함으로써 모호성을 줄임
memberService.create(dto);
model.addAttribute("dto", dto); // view에서 model에 포함된 dto 속성을 사용할 수 있도록 추가
return "/main/index";
}
실행하면
입력한 email이 로그에 찍히고 register에서 main/index로 잘 넘어감
register에서 sumit버튼을 변경
<input type="submit" value="Register Account" class="btn btn-primary btn-user btn-block"/>
MemberServiceImpl2를 삭제

MemberController에서

create에 Implementation(s)를 실행한다
package indusw.sba.admin2024001910a.service;
import indusw.sba.admin2024001910a.domain.Member;
import indusw.sba.admin2024001910a.repository.MemberRepository;
import org.springframework.stereotype.Service;
@Service // Service 형 Spring Bean 임을 컴파일러/컨테이너에 알림
public class MemberServiceImpl implements MemberService {
private MemberRepository memberRepository;
public MemberRepository getMemberRepository(MemberRepository memberRepository) {
return memberRepository;
}
@Override
public int create(Member m) {
System.out.println("MemberService - Create");
return 0;
}
@Override
public Member readOne(Member m) {
return null;
}
}
ab_2024001910(본인학번) 데이터베이스를 만들고 use를 하고 tbl_member 테이블을 만들고 insert를 해준다
create table tbl_member (
id int(11) not null auto_increment primary key, -- 회원 고유 번호
name varchar(30) not null, -- 이름
pw varchar(30) not null, -- 비밀번호
email varchar(30) not null unique, -- 이메일 (중복 불가)
phone varchar(30), -- 전화번호
address varchar(100) -- 주소
);
insert into tbl_member(name, pw, email)
values('admin', 'cometrue', 'admin@induk.ac.kr');
select * from tbl_member;

MemberRepositoryImpl 코드 수정
package indusw.sba.admin2024001910a.repository;
import indusw.sba.admin2024001910a.domain.Member;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import javax.sql.DataSource;
@Repository
public class MemberRepositoryImpl implements MemberRepository {
private JdbcTemplate jdbcTemplate;
public MemberRepositoryImpl(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public int insert(Member domain) {
String sql = "insert into member(name, pw, email) values(?, ?, ?)";
try {
jdbcTemplate.update(sql, domain.getName(), domain.getPw(), domain.getEmail());
return 1;
} catch (Exception e) {
System.out.println(e);
return 0;
}
}
}
MemberRepository
package indusw.sba.admin2024001910a.repository;
import indusw.sba.admin2024001910a.domain.Member;
public interface MemberRepository {
int insert(Member domain);
}
MemberServiceImpl
@Service // Service 형 Spring Bean 임을 컴파일러/컨테이너에 알림
public class MemberServiceImpl implements MemberService {
private MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Override
public int create(Member m) {
int ret = memberRepository.insert(m);
return ret;
}
@Override
public Member readOne(Member m) { return null; }
}
실행하면 오류뜸 수정



'2026 상반기 전공과목 공부 > Java 프레임워크' 카테고리의 다른 글
| 자바 프레임워크 12주차 / 기말고사 전 정리 (0) | 2026.06.10 |
|---|---|
| 자바 프레임 워크 10주차 / DI (0) | 2026.05.06 |
| 자바 프레임 워크 7,8주차 (0) | 2026.04.22 |
| 자바 프레임워크 6주차 (0) | 2026.04.08 |
| 자바 프레임 워크 (0) | 2026.04.01 |










