강의/Java Spring Boot

Spring Boot Starter Data JPA 추가, 그리고 H2 데이터베이스 활용

studylida 2025. 1. 26. 03:22

지금까지는 데이터베이스가 없어 프로그램을 재시작하면 그동안 했던 데이터가 모조리 사라졌다.

 

이걸 방지하기 위해서 H2라는 데이터베이스를 사용할 것이고, 이것과 프로그램이 통신할 있도록 JPA 사용할 것이다.

 

언제나처럼 pom.xml에서 의존성을 추가해보자.

 

```

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-data-jpa</artifactId>

</dependency>

 

<dependency>

<groupId>com.h2database</groupId>

<artifactId>h2</artifactId>

<scope>runtime</scope>

</dependency>

```

 

이걸 추가하고 실행해보면 h2 데이터베이스와 관련한 로그가 여럿 떠있는 확인할 있다.

 

로그를 보면 H2 콘솔을 다루기 위해 /h2-console 들어갈 있고, 데이터베이스는 jdbc:h2:mem:으로 시작하는 url 통해 접속할 있음을 있다. url 계속 변하기 때문에, application.properties `spring.datasource.url="jdbc:h2:mem:testdb"`라는 코드를 추가하여 이를 `jdbc:h2:mem:testdb` 고정할 있다.

 

이러고 h2-console 통해 콘솔에 접속하여, 사용자이름을 sa, 그리고 비밀번호를 비워두고 connect 누르면 오류가 발생하는 확인할 있다.

 

이는 우리가 아무것도 설정하지 않았기 때문인데, 이런 상황에서는 가지가 자동으로 설정된다. 번째는 모든 URL 보호한다는 것이고, 번째는 승인되지 않은 요청에 대해 로그인 양식을 표시한다는 것이다. 이는 기본 기능들로, 우리가 H2 콘솔에 접근하기 위해서는 CSRF(요청위조) 비활성화해야 한다. 또한 HTML H2 frame 활용하는데, Spring Security 기본적으로 frame 허용하지 않기 때문에 이를 허용해줘야 한다.

 

위에서 말한 설정하기 위해 우리는 SecurityFilterChain 설정해줄 것이다. Shift+Left Click으로 자세한 정보를 확인할 있는데, HttpServletRequest 매칭될 있는 필터 체인을 정의한다고 나와 있다. 요청이 들어오면 가장 먼저 해당 체인이 요청을 처리하게 되는데, 예를 들어 우리가 list-todos 요청을 전송했을 , 사용자가 인증되었는지 확인하고, 인증되지 않았다면 로그인 페이지가 나오게 하는 것도 이녀석이 하는 일이다.

 

일단 코드 먼저 보고, 밑의 설명을 보자. 설명은 뤼튼에서 가져왔다.

 

 

```

@Bean

public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

 

http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated());

http.formLogin(withDefaults());

 

http.csrf().disable();

http.headers().freamOptions().disable();

 

return https.build();

}

```

 

이 코드는 Spring Security에서 HTTP 요청에 대한 보안 설정을 구성하는 `SecurityFilterChain`을 정의하는 예제입니다. 각 부분을 자세히 설명하겠습니다.

 

### 코드 설명

 

1. **@Bean**: 이 애너테이션은 해당 메서드가 Spring의 빈으로 등록된다는 것을 의미합니다. Spring 컨테이너가 이 메서드를 호출하여 반환된 객체를 관리합니다.

 

2. **public SecurityFilterChain filterChain(HttpSecurity http)**: `SecurityFilterChain`을 생성하는 메서드입니다. `HttpSecurity` 객체를 매개변수로 받아서 보안 설정을 구성합니다.

 

3. **http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())**:

   - 이 코드 블록은 모든 HTTP 요청에 대해 인증을 요구하도록 설정합니다.

   - `anyRequest()`는 모든 요청을 의미하며, 이 요청에 대해서는 `authenticated()`를 호출하여 인증이 필요하다고 지정합니다.

 

4. **http.formLogin(withDefaults())**:

   - 이 설정은 기본 로그인 폼을 사용하도록 합니다. 사용자가 로그인할 수 있는 기본 양식이 제공됩니다.

   - `withDefaults()`는 기본적인 로그인 설정을 적용합니다.

 

5. **http.csrf().disable()**:

   - CSRF(Cross-Site Request Forgery) 공격 방지를 위한 보안 기능을 비활성화합니다.

   - 일반적으로 CSRF 보호는 활성화하는 것이 좋지만, 특정 상황에서 비활성화할 수 있습니다. 이 경우는 주의가 필요합니다.

 

6. **http.headers().frameOptions().disable()**:

   - 이 설정은 X-Frame-Options 헤더를 비활성화합니다. 이 헤더는 사이트가 다른 사이트의 iframe에 포함되는 것을 방지합니다.

   - 이 설정을 비활성화하는 것은 보안적으로 위험할 수 있으므로, 필요한 경우에만 사용해야 합니다.

 

7. **return http.build()**:

   - 설정이 완료된 후, `http.build()`를 호출하여 `SecurityFilterChain` 객체를 생성하고 반환합니다.

 

### 요약

 

이 코드는 Spring Security를 사용하여 웹 애플리케이션의 보안 설정을 구성하는 것입니다. 모든 요청에 대해 인증을 요구하고, 기본 로그인 폼을 제공하며, CSRF 보호와 X-Frame-Options 헤더를 비활성화하는 설정을 포함하고 있습니다.

 

이 설정은 일반적으로 보안에 대한 주의가 필요하며, 각 기능이 어떤 의미를 갖는지 이해한 후에 적용하는 것이 좋습니다.

 

이런 자료를 참고했어요.

[1] velog - Spring Boot 3.x + Security 6.x 기본 설정 및 변화 (https://velog.io/@kide77/Spring-Boot-3.x-Security-%EA%B8%B0%EB%B3%B8-%EC%84%A4%EC%A0%95-%EB%B0%8F-%EB%B3%80%ED%99%94)

[2] Spring - Authorize HttpServletRequests :: Spring Security (https://docs.spring.io/spring-security/reference/servlet/authorization/authorize-http-requests.html)

[3] velog - [Spring Security] Config Refactoring (https://velog.io/@csh0034/Spring-Security-Config-Refactoring)

[4] Spring - Configuration Migrations :: Spring Security (https://docs.spring.io/spring-security/reference/5.8/migration/servlet/config.html)

 

궁금한 있으면 Java Configuration :: Spring Security에서 천천히 하나하나 읽어보면서 배우자.

 

이제 준비는 되었으니 데이터베이스에 데이터를 채우고, 연결하는 작업을 하려고 한다.

 

데이터베이스에 Todo 테이블을 만들어주기 위해서 @Entity 어노테이션을 활용할 있다. 그냥 Todo 클래스 코드 위에 @Entity라고 붙여주면 끝이다. 외에도 여러 어노테이션을 통해 기본키 같은 설정할 있다.

 

```

@Entity//(name="TodoABC")

public class Todo {

 

    public Todo() {

        ;

    }

 

    @Id

    @GeneratedValue

    private int id;

 

    //@Column(name="name")

    private String username;

 

    @Size(min=10, message = "Enter atleast 10 characters")

    private String description;

    private LocalDate targetDate;

    private boolean done;

 

....

```

 

이렇게 @Entity 어노테이션을 통해 Bean 데이터베이스 테이블에 매핑할 있다. 여기서 Spring Boot 자동 설정 기능이 놀라운 점은, Entity 발견하면 자동으로 H2 테이블을 생성한다는 것이다.

 

만약 다른 테이블로 매핑하고 싶다면 `@Entity(name="TodoABC")`라고 적으면 되고, 열의 이름을 바꾸고 싶다면 `@Column(name="name")` 같이 적을 있다.

 

열이름을 보면 TARGET_DATE라고 명명된 열을 있다. 이는 targetDate에서 대문자를 기준으로 띄어쓰기를 알아서 진행해 명명했기 때문이다. 데이터베이스에서는 일반적으로 밑줄을 사용해 단어를 구분한다.

 

우리가 예전에 하드코딩 했던 것처럼 시작할 기본적으로 들어가있는 값을 채우려고 한다. 이는 data.sql 파일을 통해 가능하다. 경로는 `src/main/resources/data.sql`이다.

 

여기에 아래와 같이 쿼리문을 작성하여 값을 채울 있다. 아마 인텔리제이에서는 여기서 추가적으로 해야 데이터베이스에 값을 넣게 설정이 되었던 같은데 기억이 난다. 나중에 다시 해보겠다.

 

```

insert into todo (ID, USERNAME, DESCRIPTION, TARGET_DATE, DONE)

values (10001, 'in28minutes', 'Get AWS Certified', CURRENT_DATE, false),

       (10002, 'in28minutes', 'Get SQLD Certified', CURRENT_DATE, false),

       (10003, 'in28minutes', 'Get Azure Certified', CURRENT_DATE, false),

       (10004, 'in28minutes', 'Get CCNA Certified', CURRENT_DATE, false);

```

 

일단 이대로 코딩을 하고, 저장을 실행을 하면 TODO 테이블을 발견할 없다며 오류가 발생할 것이다.

 

이는 기본적으로 data.sql 엔터티가 처리되기 전에 실행되기 때문이다. 이를 해결하기 위해 application.properties에서 `spring.jpa.defer-datasource-initialization`라는 코드를 추가한다. 이클립스에서는 이렇게 하고 실행하면 되는 확인할 있다. 인텔리제이는.... 모르겠다 알아서 해봐라.

 

이제 우리는 데이터베이스를 이용한 CRUD 구현하려고 한다. 이는 Spring Data JPA에서 Repository라는 기능을 이용해 가능하다. 일단 Repository 클래스가 아니라 인터페이스다. 우리는 이걸 만들어서 무언가 해볼 것이다. 인터페이스의 이름은 TodoRepository라고 하자. 경로는 `com/in28minutes/springboot/myfirstwebapp/todo` 되도록 하자.

 

이렇게 만들고, 이것이 엔터티를 관리하도록 하기 위해서는 `public interface TodoRepository extends JPARE<Todo, Integer>` 같이 확장을 해주어야 한다. 괄호 번째 매개변수는 그것이 관리하는 Bean 의미하고, 번째 매개변수는 id 의미한다.

 

이제 우리는 TodoController 가서 repository 인스턴스를 생성해 작업을 건데, 기존에 작성했던 코드를 남겨두기 위해 새파일로 복붙하고, 기존의 TodoController @Controller 어노테이션은 주석 처리를 해서 Spring 인식하지 못하도록 해주자. 파일의 이름은 TodoControllerJpa라고 하자.

 

TodoControllerJpa TodoRepository 인스턴스를 생성해주자. 인스턴스로 있는 기능들을 자세히 알아보고 싶다면 JPA Query Methods :: Spring Data JPA 여기서 확인할 있다.

 

일단 지금까지는 우리가 하드코딩 해둔 것에서 데이터를 받아왔다. 이를 수정하기 위해 TodoRepository 사용할 것이다. 메서드를 정의할 건데, 아래 코드를 보면 알겠지만 정의해놓는 수준으로만 짜놓으면 Spring 알아서 데이터를 데이터베이스에서 가져온다. 일단 코드를 보자.

 

```

public interface TodoRepository extends JpaRepository<Todo, Integer> {

public List<Todo> findByUsername(String username);

}

```

 

username 자동으로 가져올 있는 기능이 제공되는 이유는, username Todo Bean 있는 속성이기 때문이다.

 

그리고 기존의 코드에서 가서 TodoService대신 TodoRepository 넣어주면 된다.

 

```

@RequestMapping("list-todo")

public String listAllTodos(ModelMap model) {

String username = getLoggedinUsername(model);

 

List<Todo> todos = todoRepository.findByUsername("in28minutes");

model.addAttribute("todos", todos);

 

return "listTodos";

}

```

 

그리고 이대로 실행하면 엔터티에 기본 생성자가 없어서 오류가 날텐데 그거 추가해주고 다시 실행하면 데이터베이스에서 받아오는 확인할 있다.

 

이번에는 addNewTodo 메서드를 살펴볼 건데, 일단 코드를 먼저 보자.

 

```

@RequestMapping(value="add-todo", method=RequestMethod.POST)

public String addNewTodo(ModelMap model, @Valid Todo todo, BindResult result) {

if(result.hasError()) {

return "todo";

}

 

String username = getLoggedinUsername(model);

todo.serUsername(username);

todoRepository.save(todo);

 

// todoService.addTodo(username, todo.getDescription(), todo.getTargetDate(), todo.isDone());

 

return "redirect:list-todos";

}

```

 

주석 처리해놓은 보면 알겠지만, 이전에는 우리가 개별 속성을 파라미터를 통해 받았던 확인할 있다. 그런데 지금 todoRepository 그렇게 하지 않고, Bean 값을 넣어주고, Bean 넘겨주는 확인할 있다.

 

이렇게 하냐면 이것이 권장되는 방식이기 때문이다. todo 올바른 값으로 채우고, 다음 save() 메서드를 호출하도록 하자.

 

놀랍게도 이렇게 하면 끝이다. 다른 메서드들도 이렇게 완성시켜보자.

 

```

@RequestMapping("delete-todo")

public String deleteTodo(@RequestParam int id) {

todoRepository.deleteById(id);

return "redirect:list-todos";

}

 

```

 

```

@RequestMapping(value="update-todo" method=RequestMethod.GET)

public String showUpdateTodoPage(@RequestParam int id, ModelMap model) {

Todo todo = todoRepository.findById(id).get();

model.addAttribute("todo", todo);

 

return "todo";

}

 

@RequestMapping(value="update-todo", method=RequestMethod.POST)

public String updateTodoPage(ModelMap model, @Valid Todo todo, BindResult result) {

if(result.hasError()) {

return "todo";

}

 

String username = String username = getLoggedinUsername(model);

todo.setUsername(username);

todoRepository.save(todo);

return "redirect:list-todos";

}

```

 

여기까지 하면 끝이다! 이제 여기서부터는 선택과정이다.

 

이렇게 데이터베이스와 연결은 해봤는데, 실제로 우리가 사용하는 MySQL이나 PostgerSQL 같은 데이터베이스와 연결하려면 어떻게 해야 하는지 알아보자.

 

일단 도커를 깔아야 한다. 관리자 권한으로 깔아야 하니 주의 하자.

 

깔았으면 깔렸는지 확인할 `docker version`이라고 입력해보자. 뭔가 촤라락 뜨면 것이다.

 

되었다면 아래 명령어를 cmd에서 실행해보자.

 

```

docker run --detach

--env MYSQL_ROOT_PASSWORD=dummypassword

--env MYSQL_USER=todos-user

--env MYSQL_PASSWORD=dummytodos

--env MYSQL_DATABASE=todos

--name mysql

--publish 3306:3306

 

mysql:8-oracle

```

 

명령어를 실행하면 Docker 이미지가 로컬에 없다면서 설치되는 확인할 있다.

 

완료되고 나서 `docker container ls` 명령어를 실행하면 MySQL 컨테이너가 실행되고 있는 확인할 있다.

 

이미지라는 뭔지 처음 듣는 사람이 있을텐데, 특정 애플리케이션을 실행하기 위한 모든 갖추어진 파일이라고 생각하면 된다. 밀키트 같은 느낌이라 이해하면 편하다.

 

이제 pom.xml application.properties 수정할 것이다.

 

h2데이터베이스를 쓰지 않을테니 관련 의존성을 주석처리해서 지우고, 아래 코드를 추가해주자.

 

```

<dependency>

<groupId>com.mysql</groupId>

<artifactId>mysql-connector-j</artifactId>

<version>9.0.0</version>

</dependency>

```

 

강의에서는 다른 코드 추가하던데, 안되길래 Maven Repository: Search/Browse/Explore (mvnrepository.com)에서 따로 찾아서 추가했다. 글을 읽는 사람들도 의존성 추가하는데 안되면 저기서 찾아서 추가해라. 그런데 말하는 너무 늦었나...? 앞의 수정해서 저거 미리 말해놔야겠다....

 

그리고 application.properties 있는 코드를 일부는 아래와 같이 주석처리하고 코드를 추가하자.

 

```

#spring.datasource.url=jdbc:h2:mem:testdb

 

spring.jpa.hibernate.ddl-auto=update

spring.datasource.url=jdbc:mysql://localhost:3306/todos

spring.datasource.username=todos-user

spring.datasource.password=dummytodos

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect

 

#todos-user@localhost:3306

```

 

`spring.jpa.hibernate.ddl-auto=update` 실제 데이터베이스를 사용할 때는 테이블이 자동으로 생성되지 않기 때문에 Spring Boot에게 자동으로 생성할 것을 부탁하는 코드이다.

 

`spring.datasource.url=jdbc:mysql://localhost:3306/todos` 코드를 보면 알겠지만 우리는 MySQL 접속할 http 사용하지 않고 jdbc 사용한다.

 

`spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect` 어떤 데이터베이스에 맞춰 쿼리를 생성할 건지 있게 설정해주는 것이다. 데이터베이스 마다 쿼리문에 차이가 있어 코드가 필요하다.

 

이제 실행해보면 당연하지만 아무 투두도 없는 확인할 있다. 작동하는지 확인하기 위해 하나 추가해보면 올라가는 확인할 있다.

 

그런데 이게 진짜 MySQL 연결된 맞나 믿음이 있다. 그런 당신을 위해서 mysqlsh라는 있다. MySQL Shell이라는 뜻이다.

 

설치한 뒤에 cmd 가서 mysqlsh 실행하고 `\conncet` 실행해서 연결해주자. 비밀번호를 입력하고, 스키마라는 묻는데, 우리가 만든 데이터베이스 어떤 거에 연결할 거냐고 묻는 거다. 우리는 todos 연결할 것이다.

 

이제 `\sql` 입력해서 쿼리문을 입력할 있는 모드로 전환하고 `SELECT * FROM TODO;` 입력하면 우리가 아까 추가한 데이터가 있는 확인할 있다.

 

!