프로그래밍/Java

[vscode] java17 + springboot3.1 + mysql8 + mybatis3.0 + themleaf

소행성왕자 2024. 11. 26. 11:09

오랜만에 java 프로젝트 생성해봅니다.

이번엔 intelliJ IDEA 대신 vscode 로 셋팅해 봅니다.

Mysql 테이블 생성 및 데이터 insert

CREATE TABLE users (
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(50) NOT NULL,
  email VARCHAR(100) UNIQUE NOT NULL
);

INSERT INTO users (name, email) VALUES
('John Doe', 'john.doe@example.com'),
('Jane Smith', 'jane.smith@example.com'),
('Mike Johnson', 'mike.johnson@example.com'),
('Emily Brown', 'emily.brown@example.com'),
('David Lee', 'david.lee@example.com');

 

vscode 설치

https://code.visualstudio.com/Download

 

Download Visual Studio Code - Mac, Linux, Windows

Visual Studio Code is free and available on your favorite platform - Linux, macOS, and Windows. Download Visual Studio Code to experience a redefined code editor, optimized for building and debugging modern web and cloud applications.

code.visualstudio.com

확장팩 설치

  • Extension Pack for Java
  • Spring Boot Extension Pack

 

vscode 항상 새탭으로 열기

파일 > 기본설정 > 설정 > 워크벤치 >  편집기 관리 > 

  • Enable Preview 체크해제
  • Enable Preview from Quick open 체크해제

 

demo project 생성

> java: create java project  생성

  • spring boot
  • gradle project 
  • 3.1 (선택이 안되면 3.4 선택후 build.gradle 에서 수정)
  • java 
  • com.example 
  • demo 
  • war 
  • 17 
  • spring boot devTools 
  • Lombok 
  • spring web 
  • thymleaf  

spring boot 와 mybatis 버전 호환으로 인해 spring boot 3.1 로 변경

build.gradle

plugins {
	id 'java'
	id 'war'
	id 'org.springframework.boot' version '3.1.0'
	id 'io.spring.dependency-management' version '1.1.6'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(17)
	}
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.boot:spring-boot-devtools'
	implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.0'
	implementation 'com.fasterxml.jackson.core:jackson-databind'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
	runtimeOnly 'mysql:mysql-connector-java:8.0.25'

}

tasks.named('test') {
	useJUnitPlatform()
}
  • 수정 - id 'org.springframework.boot' version '3.1.0'
  • 추가 - implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.0'
  • 추가 - implementation 'cohttp://m.fasterxml.jackson.core:jackson-databind'
  • 추가 - runtimeOnly 'mysql:mysql-connector-java:8.0.25'

수정한 build.gradle 반영 : java: clean java Language server Workspace

 

application.properties 수정

spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=newpassword
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

mybatis.mapper-locations=classpath:/mapper/*.xml
mybatis.type-aliases-package=com.example.demo.model


spring.application.name=demo
spring.devtools.restart.enabled=true
spring.devtools.livereload.enabled=true

logging.level.root=INFO

 

계층형 아키텍처

Controller

  • Controller
  • 웹 요청을 처리하고, 적절한 서비스 메서드를 호출하여 응답을 생성합니다.
  • 주로 HTTP 요청과 응답을 관리합니다.

Service (interface)

  • 비즈니스 로직을 구현하는 계층입니다. 
  • 트랜잭션 관리 등의 복잡한 로직을 처리합니다.
  • 느슨한 결합 - 인터페이스를 사용하면 구현 클래스와 클라이언트 코드 간의 결합도를 낮출 있습니다. 클라이언트는 구체적인 구현에 의존하지 않고 인터페이스에만 의존하게 됩니다
  • 다형성 - 여러 구현 클래스를 쉽게 교체할 수 있어, 새로운 기능이나 다른 구현이 필요할 때 유연하게 대응할 수 있습니다.
  • 단일책임원칙(SRP) - 인터페이스의 각 메서드가 하나의 책임만을 가지도록 설계합니다. 이는 코드의 가독성과 유지보수성을 향상시킵니다.
  • 개방폐쇄원칙(OCP) - 인터페이스를 사용함으로써 기존 코드를 수정하지 않고도 새로운 구현을 추가할 수 있습니다. 이는 확장성을 크게 높여줍니다

ServiceImpl (구현)

  • Service 인터페이스를 구현하는 클래스입니다

DAO (Data Access Object)

  • 데이터베이스와의 직접적인 상호작용을 담당합니다.
  • SQL 쿼리를 실행하고 데이터베이스로부터 데이터를 가져오거나 수정합니다.

Mapper

  • MyBatis와 같은 ORM 프레임워크를 사용할 때, SQL 쿼리를 XML 파일이나 어노테이션으로 관리하는 인터페이스입니다.
  • DAO 대신 사용되며, 데이터베이스 쿼리를 캡슐화합니다.

이러한 구조는 코드의 모듈화를 촉진하여 각 계층이 독립적으로 개발되고 테스트될 수 있도록 합니다.

이를 통해 시스템의 복잡성을 줄이고, 변경이 필요한 경우에도 특정 계층만 수정하면 되므로 유지보수가 용이해집니다.

 

프로젝트 트리 구조

src/main/java/com/example/demo
├── controller
│   └── UserController.java
├── service
│   └── UserService.java
    └── UserServiceImpl.java
├── mapper
│   └── UserMapper.java
└── model (vo)
    └── User.java

src/main/resources
└── mapper
    └── UserMapper.xml

controller - UserController.java

package com.example.demo.contoller;

import com.example.demo.model.User;
import com.example.demo.service.UserService;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/users") // 기본 URL 경로 설정
public class UserController {
    private final UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.getUserById(id);
    }

    @GetMapping // 모든 사용자 조회
    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }
}

service - UserService.java (interface)

package com.example.demo.service;

import com.example.demo.model.User;
import java.util.List;

public interface UserService {
    List<User> getAllUsers();
    User getUserById(Long id);
}

service - UserServiceIml.java

package com.example.demo.service;

import com.example.demo.mapper.UserMapper;
import com.example.demo.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Service
public class UserServiceImpl implements UserService {
    private final UserMapper userMapper;
    private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);

    @Autowired
    public UserServiceImpl(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Override
    public List<User> getAllUsers() {
        List<User> users = userMapper.getAllUsers();
        return users;
    }

    @Override
    public User getUserById(Long id) {
        User user = userMapper.getUserById(id);
        logger.info("Retrieved user: {}", user); // 로그 출력
        return user;
    }
}

mapper - UserMapper.java

package com.example.demo.mapper;

import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.example.demo.model.User;

@Mapper
public interface UserMapper {
    // 모든 사용자 조회
    List<User> getAllUsers();

    // ID로 사용자 조회
    User getUserById(Long id);

    // 사용자 삽입
    void insertUser(User user);

    // 사용자 업데이트
    void updateUser(User user);

    // 사용자 삭제
    void deleteUser(Long id);
}

model - User.java 

package com.example.demo.model;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
@NoArgsConstructor // 기본 생성자
@AllArgsConstructor // 모든 필드를 포함하는 생성자
public class User {
    private Long id;
    private String name;
    private String email;
}

/* lombok 사용으로 인해 주석처리
public class User {
    private Long id;
    private String name;
    private String email;



    public User(Long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }

    // Getter 메서드
    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }

    // Setter 메서드
    public void setId(Long id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "User{id=" + id + ", name='" + name + '\'' + ", email='" + email + '\'' + '}';
    }
}
*/

resources/mapper - UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">

    <select id="getAllUsers" resultType="com.example.demo.model.User">
        SELECT * FROM users
    </select>

    <select id="getUserById" parameterType="long" resultType="com.example.demo.model.User">
        SELECT * FROM users WHERE id = #{id}
    </select>

    <insert id="insertUser" parameterType="com.example.demo.model.User">
        INSERT INTO users (name, email) VALUES (#{name}, #{email})
    </insert>

    <update id="updateUser" parameterType="com.example.demo.model.User">
        UPDATE users SET name=#{name}, email=#{email} WHERE id=#{id}
    </update>

    <delete id="deleteUser" parameterType="long">
        DELETE FROM users WHERE id=#{id}
    </delete>

</mapper>

준비는 끝났으니 spring boot 시작해봅시다.

DemoApplication.java 에서 Run 실행합니다

에러없이 정상적으로 실행

웹브라우져로 실행

  • http://localhost:8080/users
  • http://localhost:8080/users/2

 

thymleaf 적용

Thymeleaf를 적용하려면 다음과 같이 UserController를 수정해야 합니다:

  1. @RestController를 @Controller로 변경합니다.
  2. 메서드 반환 타입을 String으로 변경하고, 뷰 이름을 반환합니다.
  3. Model 객체를 파라미터로 추가하여 데이터를 뷰에 전달합니다.

수정된 UserController 코드:

package com.example.demo.contoller;

import com.example.demo.model.User;
import com.example.demo.service.UserServiceImpl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Controller
@RequestMapping("/users")
public class UserController {
    private final UserServiceImpl userService;

    @Autowired
    public UserController(UserServiceImpl userService) {
        this.userService = userService;
    }

    @GetMapping("/{id}")
    public String getUser(@PathVariable Long id, Model model) {
        User user = userService.getUserById(id);
        model.addAttribute("user", user);
        return "user-details"; // user-details.html 뷰를 반환
    }

    @GetMapping
    public String getAllUsers(Model model) {
        List<User> users = userService.getAllUsers();
        model.addAttribute("users", users);
        return "user-list"; // user-list.html 뷰를 반환
    }
}

Thymeleaf 템플릿을 사용하여 user-details.html user-list.html 파일을 작성

파일생성 경로 : resources/templates/

user-details.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>User Details</title>
</head>
<body>
    <h1>User Details</h1>
    <div>
        <p><strong>ID:</strong> <span th:text="${user.id}"></span></p>
        <p><strong>Name:</strong> <span th:text="${user.name}"></span></p>
        <p><strong>Email:</strong> <span th:text="${user.email}"></span></p>
    </div>
    <a href="/users">Back to User List</a>
</body>
</html>

user-list.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>User List</title>
</head>
<body>
    <h1>User List</h1>
    <table border="1">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Email</th>
                <th>Details</th>
            </tr>
        </thead>
        <tbody>
            <tr th:each="user : ${users}">
                <td th:text="${user.id}"></td>
                <td th:text="${user.name}"></td>
                <td th:text="${user.email}"></td>
                <td><a th:href="@{/users/{id}(id=${user.id})}">View Details</a></td>
            </tr>
        </tbody>
    </table>
</body>
</html>

http://localhost:8080/users 접속

이 슈

  • 최신 spring boot 3.4 와 mybatis 3.0  작동 되지 않고 오류 발생
  • spring boot 3.1 로 내리고 mybatis 3.0 정상 작동 함

앞으로 해야 될 것

  • table join 할때 vo 추가

phones table 생성 및 데이터 insert

phones 테이블에 user_id 필드를 추가하여 users 테이블의 id 필드를 참조하는 외래 키로 설정합니다.

CREATE TABLE phones (
    phone_id INT(11) AUTO_INCREMENT PRIMARY KEY,
    phone_number VARCHAR(15),
    user_id INT,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE
);

INSERT INTO phones (phone_number, user_id) VALUES
('123-456-7890', 1),
('234-567-8901', 2),
('345-678-9012', 3),
('456-789-0123', 4),
('567-890-1234', 5);

users 테이블 phones 테이블 Join Query

SELECT users.id, users.name, users.email, phones.phone_number
FROM users
LEFT JOIN phones ON users.id = phones.user_id;

UserConstroller.java 추가

@GetMapping("/phones")
    public String getAllUserPhones(Model model) {
        List<UserPhonesVO> userPhones = userService.getAllUsersPhones();
        model.addAttribute("userPhones", userPhones);
        return "userPhones-list"; 
    }

package com.example.demo.contoller;

import com.example.demo.model.User;
import com.example.demo.model.UserPhonesVO;
import com.example.demo.service.UserServiceImpl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Controller
@RequestMapping("/users")
public class UserController {
    private final UserServiceImpl userService;

    @Autowired
    public UserController(UserServiceImpl userService) {
        this.userService = userService;
    }

    @GetMapping("/{id}")
    public String getUser(@PathVariable Long id, Model model) {
        User user = userService.getUserById(id);
        model.addAttribute("user", user);
        return "user-details"; // user-details.html 뷰를 반환
    }

    @GetMapping
    public String getAllUsers(Model model) {
        List<User> users = userService.getAllUsers();
        model.addAttribute("users", users);
        return "user-list"; // user-list.html 뷰를 반환
    }

    @GetMapping("/phones")
    public String getAllUserPhones(Model model) {
        List<UserPhonesVO> userPhones = userService.getAllUsersPhones();
        model.addAttribute("userPhones", userPhones);
        return "userPhones-list"; 
    }
}

UserPhonesVO.java 생성

package com.example.demo.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserPhonesVO {
    private Long id;
    private String name;
    private String email;
    private String phoneNumber;
}

UserService.java interface 추가

List<UserPhonesVO> getAllUsersPhones();

package com.example.demo.service;

import com.example.demo.model.User;
import com.example.demo.model.UserPhonesVO;

import java.util.List;

public interface UserService {
    List<User> getAllUsers();
    User getUserById(Long id);
    List<UserPhonesVO> getAllUsersPhones();
}

UserServiceImpl.java 추가

@Override
    public List<UserPhonesVO> getAllUsersPhones() {
        List<UserPhonesVO> usersPhones = userPhonesMapper.getAllUsersPhones();
        return usersPhones;
    }

package com.example.demo.service;

import com.example.demo.mapper.UserMapper;
import com.example.demo.model.User;
import com.example.demo.mapper.UserPhonesMapper;
import com.example.demo.model.UserPhonesVO;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Service
public class UserServiceImpl implements UserService {
    private final UserMapper userMapper;
    private final UserPhonesMapper userPhonesMapper;
    private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);

    @Autowired
    public UserServiceImpl(UserMapper userMapper, UserPhonesMapper userPhonesMapper) {
        this.userMapper = userMapper;
        this.userPhonesMapper = userPhonesMapper;
    }

    @Override
    public List<User> getAllUsers() {
        List<User> users = userMapper.getAllUsers();
        return users;
    }

    @Override
    public User getUserById(Long id) {
        User user = userMapper.getUserById(id);
        logger.info("Retrieved user: {}", user); // 로그 출력
        return user;
    }

    @Override
    public List<UserPhonesVO> getAllUsersPhones() {
        List<UserPhonesVO> usersPhones = userPhonesMapper.getAllUsersPhones();
        return usersPhones;
    }
}

UserPhonesMapper.java  생성

package com.example.demo.mapper;

import com.example.demo.model.UserPhonesVO;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserPhonesMapper {
    List<UserPhonesVO> getAllUsersPhones();
}

UserPhonesMapper.xml  생성

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserPhonesMapper">

    <select id="getAllUsersPhones" resultType="com.example.demo.model.UserPhonesVO">
        SELECT u.id AS id, u.name, u.email, p.phone_number AS phoneNumber
        FROM users u
        LEFT JOIN phones p ON u.id = p.user_id
    </select>

</mapper>

userPhones-list.html 생성

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>userPhones List</title>
</head>
<body>
    <h1>userPhones List</h1>
    <table border="1">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Email</th>
                <th>phoneNumber</th>
                <th>Details</th>
            </tr>
        </thead>
        <tbody>
            <tr th:each="user : ${userPhones}">
                <td th:text="${user.id}"></td>
                <td th:text="${user.name}"></td>
                <td th:text="${user.email}"></td>
                <td th:text="${user.phoneNumber != null ? user.phoneNumber : 'N/A'}"></td>
                <td><a th:href="@{/users/{id}(id=${user.id})}">View Details</a></td>
            </tr>
        </tbody>
    </table>
</body>
</html>

http://localhost:8080/users/phones 접속