나중에 interface BookRepository 안에
직접 메서드를 만들 수도 있다.
지금은 기본적으로 JpaRepository에서 제공해주는 메서드를 사용하는 방법만 구현한다.
이제 Service layer를 만들 것이다.
여기서는 insert, select, update, delete 하는 기능을 만들어보려고 한다.
이 서비스 메서드는 위에서 만든 repository가 필요하다. 그래서 @Autowired 로 가지고 올 수 있다.
@Autowired 를 해줘야, 메모리에 올라와있는 EntityManager(); 가 bookRepository 에 자동으로 연결이 된다.
그래야지만 bookRepository 라는 인터페이스를 이용해서 EntityManager() 라는 구현체에 있는 메서드를 써가지고 CRUD 를 할 수가 있게 된다.
어쨌든, @Autowired 를 해주게 되면, 메모리를 뒤지게 된다. 뒤져가지고, BookRepository 하고 가장 잘 맞는 자식을 찾는 것이다. 그게 바로 EntityManager다. 그게 바로 구현체다. @Autowired 말고 다른 방법도 있다.
@AllArgsConstructor
private final BookRepository bookRepository;
를 하게 되면 된다.
@AllArgsConstructor 를 하게 되면 아래와 같은 생성자 주입이 자동으로 실행이 된다.
이 객체가 생성이 될 때, BookRepository bookRepository 를 필요로 하니까,
private final BookRepository bookRepository; 여기서 만들어진 bookRepository 를 받아가지고,
this.bookRepository = bookRepository; 이렇게 주입을 하는 것이다.
이것을, 생성자 주입 방법이라고 한다.
생성자 주입 방법을 할 때에 final 이라고 해야 한다. final 을 하게 되면, bookRepository 의 값을 절대로 바꿀 수가 없다.
메모리에 있는 내용을 받았는데 다른 애를 가리키면 안 되기 때문에 final 로 한다.
그래서 @Autowired 주입 방법도 있고, 생성자 주입 방법도 있다는 것. 여기서는 생성자 주입 방법으로 해보겠다.
getById - Id 정보가 있으면 그것에 맞는 정보를 가지고 오는 메서드,
getAll -모든 정보를 가지고 오는 메서드,
delete - 삭제하는 메서드,
save - 저장하는 메서드 (만약 없으면 새로 만드는 것 insert, 기존 정보가 있으면 update하는 것.)
이렇게 만들어봤다.
이제 Service가 끝났다.
하지만 지금은 H2 Database 에 책 정보가 아무것도 없다.
console에서 직접 insert 문으로 책 정보를 넣을 수도 있고,
프로그램으로 넣을 수도 있다.
이 코드는 스프링 부트 애플리케이션 실행 시 초기 데이터를 설정하는 역할을 합니다.
CommandLineRunner 인터페이스를 구현하고 있기 때문에 애플리케이션이 시작되면 자동으로 실행됩니다.
1. 코드의 구조와 흐름
InitDataConfig 클래스
- 목적: 애플리케이션 실행 시, 데이터베이스에 초기 데이터(기본 책 정보)를 넣는 역할을 합니다.
- 위치: @Component 어노테이션 덕분에 스프링 컨테이너가 이 클래스를 자동으로 빈으로 등록하고 관리합니다.
CommandLineRunner 인터페이스
- 스프링 부트에서 제공하는 인터페이스입니다.
- 목적: 애플리케이션이 실행된 직후 특정 로직을 실행하고 싶을 때 사용합니다.
- run(String... args) 메서드를 구현하면 애플리케이션이 시작될 때 이 메서드가 실행됩니다.
2. 코드의 동작 순서
- 애플리케이션이 실행될 때
스프링은 InitDataConfig 클래스가 CommandLineRunner를 구현하고 있으므로 자동으로 run() 메서드를 호출합니다. - bookService.getAll()를 호출
- bookService를 통해 데이터베이스에서 모든 책 데이터를 조회합니다.
- 만약 데이터가 존재하지 않으면(즉, 리스트 크기 books.size()가 0이면), 아래의 초기 데이터를 추가합니다.
- 초기 데이터 추가
- 두 개의 Book 객체를 생성하고 각 객체의 필드를 설정합니다.
- bookService.save(book)를 호출해 데이터베이스에 저장합니다.
3. 주요 코드 분석
@Component
- 스프링이 이 클래스를 **빈(Bean)**으로 등록합니다.
- 빈이 등록되면 스프링이 애플리케이션 실행 시 run() 메서드를 호출합니다.
CommandLineRunner 인터페이스
- 이 인터페이스를 구현하면 애플리케이션이 시작된 직후 run() 메서드가 실행됩니다.
- 초기 데이터 로딩, 설정값 검증 등의 로직에 많이 사용됩니다.
@Autowired BookService
- 의존성 주입(Dependency Injection)을 통해 BookService 빈을 가져옵니다.
- BookService는 책 정보를 처리하는 서비스 계층입니다.
데이터 체크 및 저장
- bookService.getAll()로 모든 책 데이터를 조회합니다.
- 데이터가 없다면 if (books.size() == 0) 조건이 참이 됩니다.
- 두 개의 Book 객체를 생성하고 필드를 설정합니다.
- setSubject(), setPrice(), setAuthor(), setPage()로 각 필드 값 설정.
- bookService.save(book)를 호출해서 책 객체를 데이터베이스에 저장합니다.
4. 이 코드의 실행 결과
- 애플리케이션을 처음 실행하면:
- bookService.getAll()로 데이터베이스를 조회합니다.
- 데이터가 없기 때문에 두 개의 책 데이터(백엔드, 데브옵스)가 생성되고 데이터베이스에 저장됩니다.
- 애플리케이션을 다시 실행하면:
- 이미 데이터가 존재하므로 if (books.size() == 0) 조건이 거짓이 되어 초기 데이터 추가가 실행되지 않습니다.
5. 결론
이 코드는 초기 데이터를 자동으로 추가하는 역할을 합니다.
- CommandLineRunner를 통해 애플리케이션 시작 시 실행되며,
- 데이터베이스에 데이터가 없는 경우에만 초기 데이터를 추가합니다.
이런 방식을 통해 애플리케이션 실행 시 항상 기본 데이터가 준비된 상태로 시작할 수 있습니다.
**@Override**는 메서드 재정의(Overriding)를 할 때 사용하는 어노테이션입니다.
이 어노테이션은 주로 부모 클래스 또는 인터페이스의 메서드를 자식 클래스에서 재정의할 때 사용됩니다.
1. 메서드 재정의 (Method Overriding)란?
- 상속 관계에서 자식 클래스가 부모 클래스의 메서드를 **재정의(Override)**하여 새로운 기능을 제공하는 것을 말합니다.
- 메서드의 이름, 반환 타입, 파라미터가 부모 클래스의 메서드와 완전히 동일해야 합니다.
2. @Override 어노테이션의 역할
- 컴파일러에 재정의 여부를 확인시켜 줍니다.
- 메서드 이름이나 파라미터를 잘못 작성하면 컴파일 오류를 발생시킵니다.
- 실수를 방지하고 코드의 일관성을 유지할 수 있습니다.
- 코드의 가독성과 명확성을 높입니다.
- 해당 메서드가 부모 클래스의 메서드를 재정의한 것임을 명확하게 보여줍니다.
3. @Override 사용 예시
부모 클래스와 자식 클래스의 예제
class Parent {
void sayHello() {
System.out.println("Hello from Parent");
}
}
class Child extends Parent {
@Override
void sayHello() {
System.out.println("Hello from Child");
}
}
설명:
- Parent 클래스에 sayHello()라는 메서드가 존재합니다.
- Child 클래스가 Parent를 상속받고 sayHello()를 재정의합니다.
- @Override 어노테이션을 붙여서 재정의하고 있다는 것을 명확하게 표시합니다.
재정의 시 실수의 예
@Override를 사용하지 않으면 실수로 잘못된 메서드를 작성해도 오류가 발생하지 않을 수 있습니다.
class Parent {
void sayHello() {
System.out.println("Hello from Parent");
}
}
class Child extends Parent {
// 메서드 이름 오타 (sayHello → sayHelo)
void sayHelo() {
System.out.println("Hello from Child");
}
}
위 코드에서는 메서드 이름이 sayHelo()로 잘못 작성되었습니다.
하지만 **@Override**를 붙이지 않았기 때문에 컴파일러는 이를 새로운 메서드로 인식합니다.
이런 실수를 방지하려면 **@Override**를 사용해야 합니다.
@Override로 실수 방지
class Child extends Parent {
@Override
void sayHelo() { // 오타가 있으므로 컴파일 에러 발생
System.out.println("Hello from Child");
}
}
- 컴파일 에러: "Method does not override or implement a method from a supertype"
- sayHello()를 재정의해야 하지만 오타로 sayHelo()라고 작성된 실수를 잡아줍니다.
4. 인터페이스에서 @Override 사용
인터페이스에서도 @Override를 사용하여 메서드를 구현합니다.
interface Animal {
void makeSound();
}
class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("Woof!");
}
}
- Dog 클래스가 Animal 인터페이스를 구현하고 makeSound()를 재정의합니다.
- @Override를 붙이면 구현 메서드의 오류를 방지할 수 있습니다.
5. 정리
- @Override의 목적
- 부모 클래스나 인터페이스의 메서드를 자식 클래스에서 재정의할 때 사용합니다.
- 컴파일러가 재정의 여부를 검사하므로 실수를 방지합니다.
- 장점
- 코드의 가독성과 명확성을 높입니다.
- 메서드 이름이나 시그니처를 잘못 작성했을 때 컴파일 오류를 발생시켜 줍니다.
- 사용 상황
- 상속 관계에서 부모 클래스 메서드 재정의
- 인터페이스 메서드 구현
결론: @Override는 선택적이지만 반드시 사용하는 것이 좋습니다. 실수를 방지하고 코드의 신뢰성을 높여줍니다.
자, 이제 main 에서 실행을 해서 실제로 DB에 데이터가 2개 저장되는지 확인해보자.
select 가 된 이유는 getAll() 을 했기 때문이고,
했더니 size() ==0 이라서, 2개의 정보를 save 했기 때문에 insert 가 2번 나오는 것이다.
DB 에도 제대로 올라가있는 걸 확인할 수 있다.
ID 1, 2번의 경우에는 자동으로 증가하는 값인거고, CREATED_AT 도 날짜 값이 자동으로 들어가는 것.
만약 중지를 하고 또 실행을 한다면, (실행하기 전에, application.properties 에서 create 를 update 로 바꿔주자)
이번에는 select 만 있다. 왜냐면 이미 만들어져 있기 때문에, 그리고 size() 가 이제는 0 이 아니기 때문임.
자, 이제 Controller 를 만들어볼 것이다.
클라이언트가 책 목록을 보여달라고 요청 하면 컨트롤러가 요청을 받는다.
요청을 받아가지고 view 를 만들어가지고 응답한다.
먼저 Controller 를 만들고나서 View 를 만들것이다.
@Controller 해주고,
컨트롤러는 서비스 레이어가 필요하니까 @Autowired / private BookService bookService 해주고,
요청을 받을 때 @GetMapping("/") (root 로 받는다)
그리고 view 의 값을 넘기니까 String
그리고 북 정보를 넘기는 거니까, List<Book> books 가 북서비스의 getAll 로 북 정보를 모두 받았다.
콘트롤러가 가지고 있는 books 를 home.html 에서 어떻게 읽어갈까?
객체바인딩/ Model 클래스에다가 이 books 을 setAttribute 로 집어넣고, getAttribute 로 꺼내가야 한다.
그걸 객체바인딩이라고 하는데, Model model 을 해주자.
model.addAttribute("books", books)
"books" 라는 이름에 현재 List<Book> books 를 연결하는 것. 객체를 특정 메모리에 연결한다, 객체바인딩.
books 라는 문자열에는 현재 번지값이 들어가 있다.
어레이리스트 List<>의 번지값이 books에 문자열로 들어가 있다는 것.
"books" 만 가져가면, "books"는 books 의 번지를 가리키고 있기 때문에 언제든지 List<> 의 책 정보에 접근할 수 있는 것.
그래서 객체 바인딩(model.addAttribute(~))을 하고, home.html 로 포워딩이 되면은 templates에서 home.html 로 view 를 만들어주면 된다.
view 를 만들자.
home.html, template.html, 그리고 fragments 라는 디렉토리 안에 header.html, footer.html 을 만든다.
그리고 각각의 코드를 작성해보자면,
home.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Thymeleaf Bootstrap Template</title>
<!-- Bootstrap CSS -->
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<!-- Header Section -->
<div th:replace="fragments/header:: header"></div>
<!-- Main Content Section -->
<div class="container my-5">
<div class="container">
<h1 class="my-4">Books List</h1>
<div class="row">
<div class="col-md-4" th:each="book : ${books}">
<div class="card mb-4 shadow-sm">
<div class="card-body">
<h5 class="card-title" th:text="${book.subject}">Book Subject</h5>
<h6 class="card-subtitle mb-2 text-muted" th:text="${book.author}">Author Name</h6>
<p class="card-text">
Price: $<span th:text="${book.price}">10</span><br>
Pages: <span th:text="${book.page}">200</span><br>
Created At: <span th:text="${book.createdAt}">2024-08-04</span>
</p>
<a href="#" class="btn btn-primary">View Details</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Footer Section -->
<div th:replace="fragments//footer:: footer"></div>
</body>
</html>
template.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Thymeleaf Bootstrap Template</title>
<!-- Bootstrap CSS -->
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<!-- Header Section -->
<div th:replace="fragments/header :: header"></div>
<!--MainContentSection-->
<div class="containermy-5">
</div>
<!-- Footer Section -->
<div th:replace="fragments/footer :: footer"></div>
</body>
</html>
fragments/header.html
<!-- Header Section -->
<header>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">My Website</a>
<button class="navbar-toggler" type="button" data-toggle="collapse"
data-target="#navbarNav" aria-controls="navbarNav" aria
expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a class="nav-link" href="#" th:href="@{/}">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" th:href="@{/about}">About</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" th:href="@{/contact}">Contact</a>
</li>
</ul>
</div>
</nav>
</header>
fragments/footer.html
<!-- Footer Section -->
<footer class="bg-light text-center py-4">
<div class="container">
<p class="mb-0">© 2024 My Website. All Rights Reserved.</p>
<p class="small">Designed by My Name</p>
</div>
</footer>
<!-- Bootstrap JS and dependencies -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
이 코드를 내가 작성한 건 아니고 강의에서 제공해주는대로 복붙했다.
일단은, 지금 이 부분이 그렇게 중요해보이지는 않으니 나중에 html, view 작성 방법에 대해서 자세하게 공부하게 되면 그 때 하는 걸로 하고, 지금은 실행을 해서 나온 결과물을 보고 마치도록 하겠다.
Bootstrap 이라서 화면 크기가 줄어드면 그에 맞게 반응해서 보여준다.
'Spring Boot (+ RESTful)' 카테고리의 다른 글
Spring Boot - 자동 구성 사용자 정의 / # Project 03 - 1:N (OneToMany) DB/Entity Modeling (1) | 2024.12.17 |
---|---|
Spring Boot - Maven/Gradle을 사용한 종속성 관리 (1) | 2024.12.16 |
Spring Boot - application.properties / 실습 - #Project 02 (DB 연결, Entity 만들기) (2) | 2024.12.16 |
Spring Boot - 자동 구성 메커니즘 Auto Configuration / 실습 - #Project 01 (1) | 2024.12.15 |
Spring Boot - Starter / MVN Repository / (0) | 2024.12.15 |