😳 코드내 인터페이스로 메서드 인식이 가능한 이유
아래 구조를 보면 이런식으로 선언하고 써야하지 않나 생각했는데
private final JdbcTemplateMemo memoRepository;
🌱 가능한 이유
바로 스프링의 DI(의존성 주입) + 빈 자동 등록 구조 때문.
👉 한마디로 말하면: “인터페이스 타입으로 선언해도, 그걸 구현한 클래스(@Repository 붙은 것)를 스프링이 자동으로 찾아서 주입해줌!”
여기서 의문이 생겼는데
🔍 의문: “근데 MemoRepository 인터페이스를 사용한 구현체가 여러 개면 어떡하나?”
찾아보니 해결방법이 있음.
✅ 해결 방법 3가지 (실무에서도 사용)
- @Primary :하나를 "우선 주입 대상"으로 설정
- @Qualifier("이름") : 명시적으로 어떤 빈을 넣을지 지정
- 수동 빈 등록 : Java Config로 직접 빈 생성 및 주입
🎯 예시 - @Primary 사용
@Repository
@Primary
public class JdbcTemplateMemoRepository implements MemoRepository {
...
}
✔️ 이렇게 하면 다른 구현체가 있더라도 얘가 기본값이 됨
🎯 예시 - @Qualifier 사용
public class MemoServiceImpl {
private final MemoRepository memoRepository;
public MemoServiceImpl(@Qualifier("jdbcTemplateMemoRepository") MemoRepository memoRepository) {
this.memoRepository = memoRepository;
}
}
JDBC Template을 활용한 DB 연동 코드 구조 이해하기
🔍 코드 원문
private final JdbcTemplate jdbcTemplate;
public JdbcTemplateMemoRepository(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
1. 스프링이 JdbcTemplateMemoRepository를 만들 때
2. DataSource를 자동으로 주입해줌 → DataSource는 DB 연결 정보를 담고 있는 객체
3. 이걸 이용해서 JdbcTemplate 객체를 새로 만들어서 필드로 전달.
🔗 쉽게 말하면
1. 스프링이 DataSource를 주입해줌
2. 이걸 가지고 JdbcTemplate을 만들어서
3. 그걸 이용해 SQL 실행 (jdbcTemplate.query(), update() 등)
🧠 비유로 하면?
“DataSource는 DB 연결선”이고 “JdbcTemplate은 그 연결선을 통해 SQL을 실행하는 도구”
💡 예시 코드 (이후에 이렇게 쓰임)
String sql = "SELECT * FROM memo WHERE id = ?";
Memo memo = jdbcTemplate.queryForObject(sql, memoRowMapper(), id);
💬 JdbcTemplate?
스프링에서 JDBC를 쉽게 사용하도록 도와주는 유틸 클래스
SQL을 직접 쿼리문으로 작성해서 실행할 수 있게 도와줌 (예: INSERT, SELECT 등)
코드 해석
Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));
💡 Number는 자바의 추상 클래스
Integer, Long, Double, Float 모든 숫자 타입의 부모 타입
✅ executeAndReturnKey(...)
👉 DB에 insert 하고, AUTO_INCREMENT 또는 SERIAL 같은 자동 생성된 id 값을 반환해주는 메서드
→ insert 성공 후, 그 새로 생성된 id(PK)를 key 변수에 저장
✅ MapSqlParameterSource
📦 Map<String, Object>를 SQL 파라미터로 변환해주는 래퍼 클래스
RowMapper<MemoResponseDto> memoRowMapper() 메서드 해석
private RowMapper<MemoResponseDto> memoRowMapper() {
return new RowMapper<MemoResponseDto>() {
@Override
public MemoResponseDto mapRow(ResultSet rs, int rowNum) throws SQLException {
return new MemoResponseDto(
rs.getLong("id"),
rs.getString("title"),
rs.getString("contents")
);
}
};
}
👉 RowMapper란?
DB에서 select 쿼리한 결과(ResultSet)의 한 줄(row) 을
→ 우리가 만든 Java 객체로 매핑해주는(변환해주는) 인터페이스
🧠 쉽게 바꿔 말하면
SELECT * FROM memo;
을 실행하면 DB에서 여러 행(row)이 반환 됨.
RowMapper가 ResultSet 안의 한 줄(row)을 MemoResponseDto 객체로 바꿔줌. (즉, DB row → 자바 객체)
📦 DB의 각 행(row)을
✨ MemoResponseDto(id, title, contents) 객체로 만들어주는 함수
ResultSet이란
ResultSet은 DB에서 select 쿼리를 실행한 결과를 자바 코드에서 읽을 수 있도록 만들어진 “표 형태의 데이터 구조”
💡 아주 쉬운 코드 예시
ResultSet rs = statement.executeQuery("SELECT * FROM memo");
while (rs.next()) { //rs.next() : 다음 행(row)으로 이동하는 것
Long id = rs.getLong("id");
String title = rs.getString("title"); //rs.getString("컬럼명") : 해당 셀의 값을 꺼내는 것
String contents = rs.getString("contents");
System.out.println(id + " / " + title + " / " + contents);
}
getTimestamp() vs getDate() vs getTime() 차이점
메서드 | 반환 타입 | 포함 정보 | 설명 |
getTimestamp() | java.sql.Timestamp | ⏰ 날짜 + 시간 모두 포함 | 가장 일반적, 자주 씀 |
getTime() | java.sql.Time | ⏱️ 시간(HH:mm:ss) 만 포함 | 시:분:초만 필요한 경우 |
getDate() | java.sql.Date | 📅 날짜(yyyy-MM-dd) 만 포함 | 시:분:초는 무시됨 |
👾 Create Date를 DB에서 자동 생성하게 설정했으나, null이 들어가던 문제
✅ 핵심 원인
SimpleJdbcInsert는 자동으로 모든 필드에 대해 insert SQL을 생성해주는데,
테이블의 모든 컬럼 중에서 parameters에 안 넣은 필드도 무조건 null로 넣어버림 😱
기존 스케쥴 저장 Repository 코드
@Override
public ScheduleResponseDto saveScheudle(Schedules sc) {
// INSERT Query를 직접 작성하지 않아도 된다.
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
jdbcInsert.withTableName("schedules").usingGeneratedKeyColumns("id");
Map<String, Object> parameters = new HashMap<>();
parameters.put("title",sc.getTitle());
parameters.put("content",sc.getContent());
parameters.put("user_id",sc.getUserId()); //외래키로 유저테이블과 연결하기
parameters.put("update_date",sc.getUpdate_date());
// 저장 후 생성된 key값을 Number 타입으로 반환하는 메서드
// executeAndReturnKey : DB에 insert 하고, AUTO_INCREMENT 또는 SERIAL 같은 자동 생성된 id 값을 반환해주는 메서드
Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));
return new ScheduleResponseDto(key.longValue(), sc.getTitle(), sc.getContent(), sc.getUserId(),sc.getCreate_date(),sc.getUpdate_date());
}
✨ [방법 1] SimpleJdbcInsert에 사용할 컬럼 명시하기
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate)
.withTableName("schedules")
.usingGeneratedKeyColumns("id")
.usingColumns("title", "content", "user_id", "update_date"); // 👈 create_date는 미등록
이렇게 하면 지정한 컬럼만 insert SQL에 포함되고
→ create_date는 DB가 알아서 자동으로 넣어준다고 함.
✨ [방법 2] 직접 insert SQL을 작성
String sql = "INSERT INTO schedules (title, content, user_id, update_date) VALUES (?, ?, ?, ?)";
jdbcTemplate.update(sql, sc.getTitle(), sc.getContent(), sc.getUserId(), sc.getUpdate_date());
조건 중 한 가지만을 충족하거나, 둘 다 충족을 하지 않을 수도, 두 가지를 모두 충족하는 방법
@RequestParam(required = false) 어노테이션 추가로 해결
@GetMapping
public List<ScheduleResponseDto> findAllSchedules(
@RequestParam(required = false) String date, // 날짜는 문자열로 받아서 변환 추천
@RequestParam(required = false) String name
) {
return scService.findAllSchedules(date, name);
}
컨트롤러에서 위와 같이 수정 후 DB 불러오는 구조 수정 진행
여기서 동적 쿼리랑 SpringBuilder를 쓰는 방법을 알아야 함;;
(이건 안배웠는디유... 찾아서 알아냄... 과제가 어렵네욧 선생님...)
SpringBuilder는 쿼리를
👉 append() 메서드로 문자열을 계속 이어 붙이기 가능하고,
👉 마지막에 toString()으로 문자열로 변환가능한 메서드를 제공함!
SELECT * FROM users WHERE 1=1;
1=1은 **항상 참(TRUE)**인 조건으로 (모든 레코드 선택 (= 조건 없음)을 의미)
SELECT * FROM users;
와 동일한 의미인데 WHERE 1=1를 붙이는 이유는 아래 예시 참고
💬 조건 없이 시작하면 불편한 경우
-- 사용자가 조건을 안 넣을 수도 있고 넣을 수도 있음
SELECT * FROM users
WHERE age > 20 -- 첫 조건이 있으면 ok
-- 근데 조건이 없으면 WHERE도 없어야 함
SELECT * FROM users -- 이렇게...
-- 조건 여러 개면? 첫 조건엔 WHERE, 나머진 AND 붙여야 함
SELECT * FROM users
WHERE age > 20
AND gender = 'F'
→ 조건이 몇 개 있는지에 따라 WHERE vs AND 분기처리를 해야 함
✅ 그래서 WHERE 1=1을 써버리면!
SELECT * FROM users
WHERE 1=1
AND age > 20
AND gender = 'F'
→ 조건이 있든 없든 그냥 AND만 붙이면 됨
🔄 다른 연산자도 붙일 수는 있음!
SELECT * FROM users
WHERE 1=1
OR name = 'John'
이건 문법적으로는 되지만…
💥 문제 생김
• 1=1이 항상 참이니까 → OR name = 'John'이든 뭐든 상관없이 항상 모든 row가 나와.
• OR보단 보통 AND를 쓰는 이유는:
👉 WHERE 1=1 뒤에 오는 조건들이 실제로 필터 역할을 하게 하려면 AND가 필요함!
그래서 최종
@Override
public List<ScheduleResponseDto> findAllSchedules(String date, String name) {
StringBuilder sql = new StringBuilder("SELECT * FROM schedules WHERE 1=1");
List<Object> params = new ArrayList<>();
if (date != null) {
sql.append(" AND DATE(update_date) = ?");
params.add(date); // "2025-03-27"
}
if (name != null) {
sql.append(" AND user_id IN (SELECT id FROM users WHERE name = ?)");
params.add(name);
}
sql.append(" ORDER BY update_date DESC");
return jdbcTemplate.query(sql.toString(), scheduleRowMapper(), params.toArray());
//return jdbcTemplate.query(
//sql.toString(), // ① SQL 문장 (String)
//scheduleRowMapper(), // ② RowMapper : 결과를 객체로 매핑
//params.toArray() // ③ SQL의 ?에 들어갈 값들 (Object[]));
}
✅ 최종적으로 이 둘이 같이 동작해서 아래처럼 실행 됨
String sql = "SELECT * FROM schedules WHERE 1=1 AND DATE(update_date) = ? AND user_id IN (...) ORDER BY ...";
List<Object> params = List.of("2025-03-27", "sun");
jdbcTemplate.query(sql, rowMapper, params.toArray());
개발 전, 공통 조건
- 일정 작성, 수정, 조회 시 반환 받은 일정 정보에 비밀번호는 제외해야 합니다.
- 일정 수정, 삭제 시 선택한 일정의 비밀번호와 요청할 때 함께 보낸 비밀번호가 일치할 경우에만 가능합니다.
- 비밀번호가 일치하지 않을 경우 적절한 오류 코드 및 메세지를 반환해야 합니다.
- 3 Layer Architecture 에 따라 각 Layer의 목적에 맞게 개발해야 합니다.
- CRUD 필수 기능은 모두 데이터베이스 연결 및 JDBC 를 사용해서 개발해야 합니다.
- JDBC 설정은 강의와 강의에서 제공해주는 코드 스니펫을 참고하셔도 됩니다.
- ‼️ 잠깐! 왜 JPA가 아닌 JDBC로 하나요?
- 데이터베이스와의 연동을 위해 JDBC를 사용해보며, 기본적인 SQL 쿼리 작성과 데이터 관리를 연습합니다.
- JPA에 비해, 첫 시도로 좋은 연습 상대가 될거에요! 충분히 익숙해지고 난 후, JPA를 도입합니다!
필수 기능 가이드
Lv 0 API 명세 및 ERD 작성
Lv 0. API 명세 및 ERD 작성 필수
- [ ] API 명세서 작성하기
- [ ] API명세서는 프로젝트 root(최상위) 경로의 README.md 에 작성
- 참고) API 명세서 작성 가이드
- API 명세서란 API명, 요청 값(파라미터), 반환 값, 인증/인가 방식, 데이터 및 전달 형식 등 API를 정확하게 호출하고 그 결과를 명확하게 해석하는데 필요한 정보들을 일관된 형식으로 기술한 것을 의미합니다.
- request 및 response는 json(링크) 형태로 작성합니다.

- API 명세서 추천 무료 Tool
- Document your APIs in Postman | Postman Learning Center
- [ ] ERD 작성하기
- [ ] ERD는 프로젝트 root(최상위) 경로의 README.md 에 첨부

- 참고) ERD 작성 가이드출처: https://online.visual-paradigm.com/ko/community/share/er-diagram-for-online-book-store-1gnrscfbme
- API 명세 작성을 통해 서비스의 큰 흐름과 기능을 파악 하셨다면 이제는 기능을 구현하기 위해 필요한 데이터가 무엇인지 생각해봐야합니다.
- 이때, 구현해야 할 서비스의 영역별로 필요한 데이터를 설계하고 각 영역간의 관계를 표현하는 방법이 있는데 이를 ERD(Entity Relationship Diagram)라 부릅니다.
- ERD 작성간에 다음과 같은 항목들을 학습합니다.
- E(Entity. 개체)
- 구현 할 서비스의 영역에서 필요로 하는 데이터를 담을 개체를 의미합니다.
- ex) 책, 저자, 독자, 리뷰
- 구현 할 서비스의 영역에서 필요로 하는 데이터를 담을 개체를 의미합니다.
- A(Attribute. 속성)
- 각 개체가 가지는 속성을 의미합니다.
- ex) 책은 제목, 언어, 출판일, 저자, 가격 등의 속성을 가질 수 있습니다.
- 각 개체가 가지는 속성을 의미합니다.
- R(Relationship. 관계)
- 개체들 사이의 관계를 정의합니다.
- ex) 저자는 여러 권의 책을 집필할 수 있습니다. 이때, 저자와 책의 관계는 일대다(1:N) 관계입니다.
- 개체들 사이의 관계를 정의합니다.
- E(Entity. 개체)
- API 명세 작성을 통해 서비스의 큰 흐름과 기능을 파악 하셨다면 이제는 기능을 구현하기 위해 필요한 데이터가 무엇인지 생각해봐야합니다.
- ERD 추천 영상 https://www.youtube.com/watch?v=jsOPr3QfMW0
- [ ] SQL 작성하기
- [ ] 설치한 데이터베이스(Mysql)에 ERD를 따라 테이블을 생성
- 참고) SQL 작성 가이드
- 과제 프로그램의 root(최상위) 경로에schedule.sql 파일을 만들고, 테이블 생성에 필요한 query를 작성하세요.
Lv 1. 일정 생성 및 조회
- [ ] 일정 생성(일정 작성하기)
- [ ] 일정 생성 시, 포함되어야할 데이터
- [ ] 할일, 작성자명, 비밀번호, 작성/수정일을 저장
- [ ] 작성/수정일은 날짜와 시간을 모두 포함한 형태
- [ ] 각 일정의 고유 식별자(ID)를 자동으로 생성하여 관리
- [ ] 최초 입력 시, 수정일은 작성일과 동일
- View를 생각해보자면..! (화면 구현하는 것은 요구사항이 아닙니다!)
- [ ] 일정 생성 시, 포함되어야할 데이터

- [ ] 전체 일정 조회(등록된 일정 불러오기)
- [ ] 다음 조건을 바탕으로 등록된 일정 목록을 전부 조회
- [ ] 수정일 (형식 : YYYY-MM-DD)
- [ ] 작성자명
- [ ] 조건 중 한 가지만을 충족하거나, 둘 다 충족을 하지 않을 수도, 두 가지를 모두 충족할 수도 있습니다.
- [ ] 수정일 기준 내림차순으로 정렬하여 조회
- View를 생각해보자면…! (화면 구현하는 것은 요구사항이 아닙니다!)
- [ ] 다음 조건을 바탕으로 등록된 일정 목록을 전부 조회

- [ ] 선택 일정 조회(선택한 일정 정보 불러오기)
- [ ] 선택한 일정 단건의 정보를 조회할 수 있습니다.
- [ ] 일정의 고유 식별자(ID)를 사용하여 조회합니다.
Lv 2. 일정 수정 및 삭제
- [ ] 선택한 일정 수정
- [ ] 선택한 일정 내용 중 할일, 작성자명 만 수정 가능
- [ ] 서버에 일정 수정을 요청할 때 비밀번호를 함께 전달합니다.
- [ ] 작성일 은 변경할 수 없으며, 수정일 은 수정 완료 시, 수정한 시점으로 변경합니다.
- [ ] 선택한 일정 내용 중 할일, 작성자명 만 수정 가능
- [ ] 선택한 일정 삭제
- [ ] 선택한 일정을 삭제할 수 있습니다.
- [ ] 서버에 일정 수정을 요청할 때 비밀번호를 함께 전달합니다.
- [ ] 선택한 일정을 삭제할 수 있습니다.
Lv 3. 연관 관계 설정
- [ ] 작성자와 일정의 연결
- [ ] 설명
- [ ] 동명이인의 작성자가 있어 어떤 작성자가 등록한 ‘할 일’인지 구별할 수 없음
- [ ] 작성자를 할 일과 분리해서 관리합니다.
- [ ] 작성자 테이블을 생성하고 일정 테이블에 FK를 생성해 연관관계를 설정해 봅니다.
- [ ] 조건
- [ ] 작성자 테이블은 이름 외에 이메일, 등록일, 수정일 정보를 가지고 있습니다.
- [ ] 작성자의 정보는 추가로 받을 수 있습니다.(조건만 만족한다면 다른 데이터 추가 가능)
- [ ] 작성자의 고유 식별자를 통해 일정이 검색이 될 수 있도록 전체 일정 조회 코드 수정.
- [ ] 작성자의 고유 식별자가 일정 테이블의 외래키가 될 수 있도록 합니다.
- [ ] 작성자 테이블은 이름 외에 이메일, 등록일, 수정일 정보를 가지고 있습니다.
- [ ] 설명
Lv 4. 페이지네이션
- [ ] 설명
- [ ] 많은 양의 데이터를 효율적으로 표시하기 위해 데이터를 여러 페이지로 나눕니다.
- [ ] 페이지 번호와 페이지 크기를 쿼리 파라미터로 전달하여 요청하는 항목을 나타냅니다.
- [ ] 전달받은 페이지 번호와 크기를 기준으로 쿼리를 작성하여 필요한 데이터만을 조회하고 반환
- [ ] 많은 양의 데이터를 효율적으로 표시하기 위해 데이터를 여러 페이지로 나눕니다.
- [ ] 조건
- [ ] 등록된 일정 목록을 페이지 번호와 크기를 기준으로 모두 조회
- [ ] 조회한 일정 목록에는 작성자 이름이 포함
- [ ] 범위를 넘어선 페이지를 요청하는 경우 빈 배열을 반환
- [ ] Paging 객체를 활용할 수 있음
Lv 5. 예외 발생 처리
- [ ] 설명
- [ ] 예외 상황에 대한 처리를 위해 HTTP 상태 코드(링크)와 에러 메시지를 포함한 정보를 사용하여 예외를 관리할 수 있습니다.
- 필요에 따라 사용자 정의 예외 클래스를 생성하여 예외 처리를 수행할 수 있습니다.
- @ExceptionHandler를 활용하여 공통 예외 처리를 구현할 수도 있습니다.
- 예외가 발생할 경우 적절한 HTTP 상태 코드와 함께 사용자에게 메시지를 전달하여 상황을 관리합니다.
- [ ] 예외 상황에 대한 처리를 위해 HTTP 상태 코드(링크)와 에러 메시지를 포함한 정보를 사용하여 예외를 관리할 수 있습니다.
- [ ] 조건
- [ ] 수정, 삭제 시 요청할 때 보내는 비밀번호가 일치하지 않을 때 예외가 발생합니다.
- [ ] 선택한 일정 정보를 조회할 수 없을 때 예외가 발생합니다.
- 잘못된 정보로 조회하려고 할 때
- 이미 삭제된 정보를 조회하려고 할 때
Lv 6. null 체크 및 특정 패턴에 대한 검증 수행
- [ ] 설명
- [ ] 유효성 검사
- 잘못된 입력이나 요청을 미리 방지할 수 있습니다.
- 데이터의 무결성을 보장하고 애플리케이션의 예측 가능성을 높여줍니다.
- Spring에서 제공하는 @Valid 어노테이션을 이용할 수 있습니다.
- [ ] 유효성 검사
- [ ] 조건
- [ ] 할일은 최대 200자 이내로 제한, 필수값 처리
- [ ] 비밀번호는 필수값 처리
- [ ] 담당자의 이메일 정보가 형식에 맞는지 확인
Why: 과제 제출시에는 아래 질문을 고민해보고 답변을 함께 제출해주세요.
- 적절한 관심사 분리를 적용하셨나요? (Controller, Service, Repository)
- RESTful한 API를 설계하셨나요? 어떤 부분이 그런가요? 어떤 부분이 그렇지 않나요?
- 수정, 삭제 API의 request를 어떤 방식으로 사용 하셨나요? (param, query, body)
제출 일정
최종 제출
- 제출 기한 : 3/26(수) 14:00 까지
- 제출해야 할 것
- 과제
- Github 링크 (Public)
- 트러블슈팅을 작성한 TIL
- 어디까지 구현했는지
- 어떤 부분을 고민했는지, 어려웠던 부분은 어디였는지
- 해당 부분을 작성하시면 피드백 간, 튜터님께서 확인 후 피드백 해주실 예정입니다.
- 고민해볼 사항
- 적절한 관심사 분리를 적용하셨나요? (Controller, Service, Repository)
- RESTful한 API를 설계하셨나요? 어떤 부분이 그런가요? 어떤 부분이 그렇지 않나요?
- 수정, 삭제 API의 request를 어떤 방식으로 사용 하셨나요? (param, query, body)
- 과제
'코딩 > sparta TIL' 카테고리의 다른 글
TIL 28 : 이해 1도 안됨, 다시 봐야함, 그냥 강의자료 메모 🤬 (0) | 2025.03.26 |
---|---|
TIL 27 : Spring 예외처리 방법 (0) | 2025.03.26 |
TIL 26 : Spring 기초 코드 복습 (0) | 2025.03.24 |
TIL 25 : Layered Architecture, DB (0) | 2025.03.21 |
TIL 24 : Spring 요청, 응답 처리 기초 (0) | 2025.03.21 |