📌 IoC
: 설계 원칙 => Inversion of Control, 제어의 역전
프로그램 제어 흐름의 주도권을 외부로 넘겨주는 것
📌 DI
: 디자인 패턴 => Dependency Injection, 의존성 주입
객체가 필요한 종속성을 외부에서 주입해주는 것을 “의존성 주입”
=> IoC는 느슨한 결합을 만들기 위한 틀이고, 그 과정으로 DI를 사용한다.
📌 의존성이 높은 코드
public class Consumer {
void eat() {
Chicken chicken = new Chicken();
chicken.eat();
}
public static void main(String[] args) {
Consumer consumer = new Consumer();
consumer.eat();
}
}
class Chicken {
public void eat() {
System.out.println("치킨을 먹는다.");
}
}
Consumer가 Chicken을 만들고 있음. 따라서, 제어의 흐름은 Consumer에서 Chicken이다.
📌 생성자를 통한 주입
public class Consumer {
Food food;
public Consumer (Food food) {
this.food = food;
}
void eat() {
this.food.eat();
}
public static void main(String[] args) {
Consumer consumer1 = new Consumer(new Chicken());
consumer1.eat();
Consumer consumer2 = new Consumer(new Pizza());
consumer2.eat();
}
}
interface Food {
void eat();
}
class Chicken implements Food {
@Override
public void eat() {
System.out.println("치킨을 먹는다.");
}
}
class Pizza implements Food {
@Override
public void eat() {
System.out.println("피자를 먹는다.");
}
}
main은 Consumer class 밖임. 따라서, 밖에서 Food가 만들어지고 Consumer에게 전달만 된다. 따라서, 제어의 흐름이 Food에서 Consumer이다. 따라서, 이 코드는 IoC 설계 원칙을 따르고, interface Food를 사용하여 DI 디자인 패턴 형태를 가진다.
코드의 중복성을 줄이고, Ioc, DI를 구현한다.
MemoController.java
package com.sparta.memo.controller;
import com.sparta.memo.dto.MemoRequestDto;
import com.sparta.memo.dto.MemoResponseDto;
import com.sparta.memo.service.MemoService;
import org.springframework.web.bind.annotation.*;
import java.util.List;
// Dispatcherservlet이 내부적으로 Controller를 사용함
// Controller도 @Component이다. bean 객체이다.
@RestController
@RequestMapping("/api")
public class MemoController {
// MemoController -> MemoService -> MemoRepository 강한 결함 약하게 만들기
private final MemoService memoService;
// JDBC DB 초기화 <-final 이므로
public MemoController(MemoService memoService) { // 외부에서 만들어진 memoService를 필드에 주입
this.memoService = memoService;
}
@PostMapping("/memos")
public MemoResponseDto createMemo(@RequestBody MemoRequestDto requestDto) {
// Controller의 메서드와 service의 메서들 이름을 일치시켜라
return memoService.createMemo(requestDto);
}
@GetMapping("/memos")
public List<MemoResponseDto> getMemos() {
return memoService.getMemos();
}
@PutMapping("/memos/{id}")
public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto) {
return memoService.updateMemo(id, requestDto);
}
@DeleteMapping("/memos/{id}")
public Long deleteMemo(@PathVariable Long id) {
return memoService.deleteMemo(id);
}
}
MemoService.java
package com.sparta.memo.service;
import com.sparta.memo.dto.MemoRequestDto;
import com.sparta.memo.dto.MemoResponseDto;
import com.sparta.memo.entity.Memo;
import com.sparta.memo.repository.MemoRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.util.List;
@Service // @Component 포함
// MemoService를 memoService로 bean으로써 IoC container에 등록
// @Componet가 붙어있는 class 내에서만 @Autowired를 쓸 수 있다.
// @RequiredArgsConstructor // // (4). Lombok 주입 방법
public class MemoService {
// JDBC DB 생성
private final MemoRepository memoRepository;
// JDBC DB 초기화 <-final 이므로
// (1). 생성자 주입 방법 (최고)
// memoRepository는 final이기 때문에 final은 생성자를 통해서만 초기화가 가능하다.
public MemoService(MemoRepository memoRepository) { // 외부에서 만들어진 memoRepository를 필드에 주입
this.memoRepository = memoRepository;
}
// (5). IoC Container에 직접 접근하기
// public MemoService(ApplicationContext context) {
// (5)-1. 'Bean' 이름으로 찾기
// MemoRepository memoRepository = (MemoRepository) context.getBean("memoRepository");
//
// (5)-2. 'Bean' 클래스 형식으로 찾기
// MemoRepository memoRepository = context.getBean(MemoRepository);
// this.memoRepository = memoRepository;
// }
// (2). 메서드 주입 방법
// final 빼주기: private MemoRepository memoRepository;
// @Autowired
// public void setDi(MemoRepository memoRepository) {
// this.memoRepository = memoRepository;
// }
// (3). 필드 주입 방법 (추천 X)
// @Autowired
// private MemoRepository memoRepository;
public MemoResponseDto createMemo(MemoRequestDto requestDto) {
// RequestDto -> Entity
Memo memo = new Memo(requestDto);
// DB 저장
Memo saveMemo = memoRepository.save(memo);
// Entity -> ResponseDto
MemoResponseDto memoResponseDto = new MemoResponseDto(memo);
return memoResponseDto;
}
public List<MemoResponseDto> getMemos() {
// DB 조회
return memoRepository.findAll();
}
public Long updateMemo(Long id, MemoRequestDto requestDto) {
// 해당 메모가 DB에 존재하는지 확인
Memo memo = memoRepository.findById(id);
if(memo != null) {
// memo 내용 수정
memoRepository.update(id, requestDto);
return id;
} else {
throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
}
}
public Long deleteMemo(Long id) {
// 해당 메모가 DB에 존재하는지 확인
Memo memo = memoRepository.findById(id);
if(memo != null) {
// memo 삭제
memoRepository.delete(id);
return id;
} else {
throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
}
}
}
MemoRepository.java
package com.sparta.memo.repository;
import com.sparta.memo.dto.MemoRequestDto;
import com.sparta.memo.dto.MemoResponseDto;
import com.sparta.memo.entity.Memo;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
@Repository // @Component 포함
//@Component
// MemoRepository를 memoRepository로 bean으로써 IoC container에 등록
public class MemoRepository {
private final JdbcTemplate jdbcTemplate;
// @Autowired 매개변수가 여러 개면 써줘야됨, 1개면 생략가능
public MemoRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public Memo save(Memo memo) {
// DB 저장
KeyHolder keyHolder = new GeneratedKeyHolder(); // 기본 키를 반환받기 위한 객체
String sql = "INSERT INTO memo (username, contents) VALUES (?, ?)"; // 값은 동적이므로 ?, ?
jdbcTemplate.update( con -> {
PreparedStatement preparedStatement = con.prepareStatement(sql,
Statement.RETURN_GENERATED_KEYS);
preparedStatement.setString(1, memo.getUsername());
preparedStatement.setString(2, memo.getContents());
return preparedStatement;
},
keyHolder);
// DB Insert 후 받아온 기본키 확인
Long id = keyHolder.getKey().longValue();
memo.setId(id);
return memo;
}
public List<MemoResponseDto> findAll() {
// DB 조회
String sql = "SELECT * FROM memo";
return jdbcTemplate.query(sql, new RowMapper<MemoResponseDto>() {
@Override
public MemoResponseDto mapRow(ResultSet rs, int rowNum) throws SQLException {
// SQL 의 결과로 받아온 Memo 데이터들을 MemoResponseDto 타입으로 변환해줄 메서드
Long id = rs.getLong("id");
String username = rs.getString("username");
String contents = rs.getString("contents");
return new MemoResponseDto(id, username, contents);
}
});
}
public void update(Long id, MemoRequestDto requestDto) {
String sql = "UPDATE memo SET username = ?, contents = ? WHERE id = ?";
jdbcTemplate.update(sql, requestDto.getUsername(), requestDto.getContents(), id);
}
public void delete(Long id) {
String sql = "DELETE FROM memo WHERE id = ?";
jdbcTemplate.update(sql, id);
}
public Memo findById(Long id) {
// DB 조회
String sql = "SELECT * FROM memo WHERE id = ?";
return jdbcTemplate.query(sql, resultSet -> {
if(resultSet.next()) {
Memo memo = new Memo();
memo.setUsername(resultSet.getString("username"));
memo.setContents(resultSet.getString("contents"));
return memo;
} else {
return null;
}
}, id);
}
}
📌 Bean
: Spring이 관리하는 객체
📌 IoC Container
: Bean을 담아둔 컨테이너
📌 Bean 등록 방법
:‘Bean’으로 등록하고자하는 클래스 위에 @Component.
@Component
public class MemoService { ... }
📌 Bean 사용 방법
: @Autowired
@Component
public class MemoService {
@Autowired
private MemoRepository memoRepository;
// ...
}
@Component
public class MemoService {
private final MemoRepository memoRepository;
@Autowired
public MemoService(MemoRepository memoRepository) {
this.memoRepository = memoRepository;
}
// ...
}
@Component
@RequiredArgsConstructor // final로 선언된 멤버 변수를 파라미터로 사용하여 생성자를 자동으로 생성합니다.
public class MemoService {
private final MemoRepository memoRepository;
// public MemoService(MemoRepository memoRepository) {
// this.memoRepository = memoRepository;
// }
...
}
📌 3 layered annotation
1. @Controller, @RestController
2. @Service
3. @Repository
📌 JPA
: Java Persistence API
자바 ORM 기술에 대한 표준 명세
반복적이고 번거로운 애플리케이션 단에서의 SQL 작업을 줄여주기 위해서 ORM(객체 관계 매핑)기술들이 등장 하게 됩니다.
📌 Entity
: JPA에서 관리되는 클래스 즉, 객체를 의미
Entity 클래스는 DB의 테이블과 매핑되어 JPA에 의해 관리
package com.sparta.entity;
import jakarta.persistence.*;
@Entity // JPA가 관리할 수 있는 Entity 클래스 지정
@Table(name = "memo") // 매핑할 테이블의 이름을 지정
public class Memo {
@Id // Entity를 구분해주는 primary key 이므로 필수적으로 있어야함
@GeneratedValue(strategy = GenerationType.IDENTITY) // Auto increament
private Long id;
// nullable: null 허용 여부
// unique: 중복 허용 여부 (false 일때 중복 허용)
@Column(name = "username", nullable = false, unique = true)
private String username;
// length: 컬럼 길이 지정
@Column(name = "contents", nullable = false, length = 500)
private String contents;
}