오랜만에 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
확장팩 설치
- 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를 수정해야 합니다:
- @RestController를 @Controller로 변경합니다.
- 메서드 반환 타입을 String으로 변경하고, 뷰 이름을 반환합니다.
- 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 접속
'프로그래밍 > Java' 카테고리의 다른 글
인텔리제이 2020.3 에서 java 17 -> java 11 spring boot 3 -> 2 (0) | 2024.08.12 |
---|---|
[spring boot JPA] object references an unsaved transient instance - save the transient instance before flushing 오류 원인 (0) | 2023.08.25 |
spring boot 와 JPA + mysql 사용한 두개 테이블 조인 Insert (0) | 2023.08.23 |
spring boot 와 JPA 사용한 프로젝트 전체 구조 (0) | 2023.08.23 |
IntelliJ 이용하여 스프링 부트 + JPA + Mysql + thymleaf 게시판 CRUD (0) | 2023.08.23 |