<artifactId> spring-boot-starter-parent </artifactId> : pom.xml
<artifactId> spring-boot-dependencies </artifactId>
: spring-boot-starter-parent.pom
<artifactId> spring-boot-dependencies </artifactId>
: spring-boot-dependencies.pom
-Spring Boot에서 의존성 관리를 내부적으로 해줌
의존성 관계 설정 필요 줄음
의존성 버전 변경
<properties>
<spring-framework.version>6.1.x</spring-framework.version>
</properties>
src/main/java : 자바 소스 파일
src/main/resources/application : 속성 값
src/main/resources/static : html, css 정적 파일
src/main/resources/templates : tsp, thymeleaf 동적 파일
src/test/java : 자바 테스트 파일
@SpringBootApplication
📋 실습 📋 MySpringBoot3Application.java
package com.basic.myspringboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class MySpringBoot3Application {
public static void main(String[] args) {
SpringApplication.run(MySpringBoot3Application.class, args);
}
@Bean
public String hello() {
return new String("Hello 스프링부트");
}
}
,--. ,--. ,-----. ,--.
| `.' |,--. ,--.| |) /_ ,---. ,---. ,-' '-.
| |'.'| | \ ' / | .-. \| .-. | .-. |'-. .-'
| | | | \ ' | '--' /' '-' ' '-' ' | |
`--' `--'.-' / `------' `---' `---' `--'
`---'
Application Info : ${application.title} ${application.version}
Powered by Spring Boot ${spring-boot.version}
// application.title = pom.xml 파일의 <name>MySpringBoot3</name>
// application.version = pom.xml 파일의 <version>0.0.1-SNAPSHOT</version> (개발 프로젝트이 버전 설정 가능)
SpringApplication 객체를 통해 어플리케이션 타입 지정 가능
종류
스프링 부트를 웹 어플리케이션 프로젝트 ➡️ 일반 프로젝트 용도로 사용 용도 변경
📋 실습 📋 MySpringBoot3Application.java
package com.basic.myspringboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import javax.swing.*;
@SpringBootApplication
public class MySpringBoot3Application {
public static void main(String[] args) {
// SpringApplication.run(MySpringBoot3Application.class, args);
SpringApplication application = new SpringApplication(MySpringBoot3Application.class);
// WebApplication Type을 변경하기 위한 목적
application.setWebApplicationType(WebApplicationType.SERVLET);
// None : 더이상 WebApplication이 아님
application.run(args);
}
}
SpringApplication 실행된 후에 arguments 값을 받거나, 무엇을 실행하고 싶을 때 사용
application이 실행되는지 인지하는 역할
tomcat 구동 확인하는 listner와 유사
📋 실습 📋 MyRunner.java
package com.basic.myspringboot.runner;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(1)
public class MyRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
// ApplicationArguments는 main메서드의 (String[] args) argument를 전달 받음
System.out.println("===> MyRunner.run");
}
}
properties의 값은 @Value 어노테이션 사용
환경변수를 이용해 application.properties에서 설정
📋 실습 📋 application.properties
#server.port=8087
#스프링
myboot.name=\uc2a4\ud504\ub9c1
myboot.age=${random.int(1,100)}
myboot.fullName=${myboot.name} Boot
package com.basic.myspringboot.runner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(1)
public class MyRunner implements ApplicationRunner {
@Value("${myboot.name}")
private String name;
@Value("${myboot.age}")
private int age;
@Value("${myboot.fullName}")
private String fullName;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("myboot.name = " + name);
System.out.println("myboot.age = " + age);
System.out.println("myboot.fullName = " + fullName);
}
}
환경변수 키 값을 받아와 environmet의 getProperty 메소드 이용하여 환경변수 값 얻어옴
📋 실습 📋 MyRunner.java
package com.basic.myspringboot.runner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
@Component
@Order(1)
public class MyRunner implements ApplicationRunner {
@Autowired
private Environment environment;
@Override
public void run(ApplicationArguments args) throws Exception {
// 포트 번호 받아오기
System.out.println("Port Number = " + environment.getProperty("local.server.port"));
}
}
myboot.name=test
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
package com.basic.myspringboot.config;
import com.basic.myspringboot.dto.Customer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Profile("test") // 현재 어떤 환경인지 properties 파일에 설정 필요
@Configuration
public class TestConfig {
@Bean
public Customer customer() {
return Customer.builder() // CustomerBuilder inner class
.name("테스트모드")
.age(10)
.build(); // customer로 바꿔주는 기능
}
}
package com.basic.myspringboot.config;
import com.basic.myspringboot.dto.Customer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Profile("prod") // 현재 어떤 환경인지 properties 파일에 설정 필요
@Configuration
public class ProdConfig {
@Bean
public Customer customer() {
return Customer.builder() // CustomerBuilder inner class
.name("운영모드")
.age(50)
.build(); // customer로 바꿔주는 기능
}
}
현재 활성화 중인 환경 설정
spring.profiles.active=test
package com.basic.myspringboot.runner;
import com.basic.myspringboot.dto.Customer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
@Component
@Order(1)
public class MyRunner implements ApplicationRunner {
@Autowired
private Customer customer;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("Customer 현재 모드 = " + customer.getName());
}
}
jar 실행
DB도 모드에 따라 구분
--debug
: 일부 핵심 라이브러리만 디버깅 모드)--trace
: 전부 다 디버깅 모드)myboot.name=\uc2a4\ud504\ub9c1 TEST Mode
# 개발 log level = debug
logging.level.com.basic.myspringboot=debug
myboot.name=\uc2a4\ud504\ub9c1 PROD Mode
# 운영 log level = info
logging.level.com.basic.myspringboot=info
package com.basic.myspringboot.runner;
import com.basic.myspringboot.dto.Customer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
@Component
@Order(1)
public class MyRunner implements ApplicationRunner {
@Value("${myboot.name}")
private String name;
@Value("${myboot.age}")
private int age;
@Value("${myboot.fullName}")
private String fullName;
@Autowired
private Environment environment;
@Autowired
private Customer customer;
//로거 생성
Logger logger = LoggerFactory.getLogger(MyRunner.class);
@Override
public void run(ApplicationArguments args) throws Exception {
// info
logger.info("Logger 클래스 이름 {}", logger.getClass().getName()); // ch.qos.logback.classic.Logger
logger.info("Customer 현재 모드 = {}", customer.getName());
logger.info("Port Number = {}", environment.getProperty("local.server.port"));
// 환경변수 받아오기
logger.info("myboot.name = {}", name);
logger.info("myboot.age = {}", age);
logger.info("myboot.fullName = {}", fullName);
// debug
// ApplicationArguments는 main메서드의 (String[] args) argument를 전달 받음
logger.debug("VM Argument foo = {} Program argument bar = {}",
args.containsOption("foo")
, args.containsOption("bar")
);
}
}
// 하단에 추가
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- log4j2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
Hibernate implements JPA
사용자가 Repository 인터페이스에 정해진 규칙대로 finder 메서드 작성
➡️ Spring이 해당 메서드 이름에 적합한 쿼리를 수행하는 구현체 만들어서 Bean으로 등록
JPA 기능
스프링 데이터 JPA 의존성 추가
<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>
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
localhost:8080/h2-console
입력package com.basic.myspringboot.runner;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
@Component
@Order(1)
@Slf4j // lombok에서 제공하고, 로깅퍼사드 기능 (로거 객체 만들지 않고 log로 사용 가능)
public class DatabaseRunner implements ApplicationRunner {
@Autowired
DataSource dataSource;
@Override
public void run(ApplicationArguments args) throws Exception {
log.info("DataSource 구현 클래스명 {}",dataSource.getClass().getName());
try (Connection connection = dataSource.getConnection()) {
DatabaseMetaData metaData = connection.getMetaData();
log.info("DB Product Name = {}", metaData.getDatabaseProductName());
log.info("DB URL = {}",metaData.getURL());
log.info("DB Username = {}",metaData.getUserName());
}
}
}
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>2.7.3</version>
</dependency>
# root 계정으로 접속하여 사용자 계정과 DB 생성
mysql -u root –p
maria 입력 // password 입력
MariaDB [(none)]> show databases; // 데이터베이스 목록 확인
MariaDB [(none)]> use mysql; // mysql DB 사용
MariaDB [mysql]> create database boot_db; // boot_db DB 생성
MariaDB [mysql]> CREATE USER 'boot'@'%' IDENTIFIED BY 'boot'; // boot user 생성, boot password 지정
MariaDB [mysql]> GRANT ALL PRIVILEGES ON boot_db.* TO 'boot'@'%'; // boot DB의 권한 허용
MariaDB [mysql]> flush privileges; // grant 사용시 권한 적용을 위한 명령어
MariaDB [mysql]> select user, host from user; // 계정 조회, user는 system table
MariaDB [mysql]> exit; // 접속 종료
# boot 사용자 계정으로 접속한다.
mysql -u boot –p
boot 입력 // password 입력
use boot_db;
# MariaDB 접속 정보
spring.datasource.url=jdbc:mariadb://127.0.0.1:3306/boot_db
spring.datasource.username=boot
spring.datasource.password=boot
spring.datasource.driverClassName=org.mariadb.jdbc.Driver%
# JPA를 사용한 데이터베이스 초기화
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
# DB Dialect 설정
spring.jpa.database-platform=org.hibernate.dialect.MariaDBDialect
create table users(
id int(10) not null auto_increment primary key, // auto-increment : 자동으로 sequence한 값 증가, primary key : 기본키
userid varchar(100) not null ,
name varchar(100) not null ,
gender varchar(10),
city varchar(100)
);
alter table users add unique index users_userid_idx(userid); // unique : 중복 안됨
show index from users;
insert into users(userid,name,gender,city) values ('gildong','홍길동','남','서울');
commit;
insert into users(userid,name,gender,city) values ('dooly','둘리','여','부산');
commit; // mariaDB는 자동 commit
create table customer(
id int(10) not null auto_increment primary key,
name varchar(100) not null,
email varchar(100) not null,
age int(10),
entryDate date,
UNIQUE KEY uk_name (email)
);
alter table customer add unique(id);
insert into customer(name, email, age, entryDate) values ('gildong', 'gildong@naver.com', 20, '2023-10-01');
insert into customer(name, email, age, entryDate) values ('dooly', 'dooly@google.com', 25, '2023-10-05');
insert into customer(name, email, age, entryDate) values ('huidong', 'huidong@google.com', 18, '2023-09-05');
insert into customer(name, email, age, entryDate) values ('micole', 'micole@naver.com', 28, '2022-10-10');
insert into customer(name, email, age, entryDate) values ('ddochi', 'ddochi@google.com', 20, '2023-05-05');
commit;
@Entity
@Id
@GenerateValue
@Column
@CreationTimeStamp
LocalDateTime
: 현재 시각 설정 가능package com.basic.myspringboot.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
public class Account {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(unique = true, nullable = false)
private String username;
@Column(nullable = false)
private String password;
}
➡️ T 는 Entity 클래스의 Type
➡️ Optional은 null이 될 수도 있는 객체
➡️ pk를 사용해서만 조회 가능 (다른 컬럼으로 조회하고 싶을 경우 따로 sql문 작성 필요)
➡️ UPDATE : entity 객체 생성 후 setter 메서드 호출하여 값 변경하고 save
package com.basic.myspringboot.repository;
import com.basic.myspringboot.entity.Account;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface AccountRepository extends JpaRepository<Account, Long> {
// < Entity 클래스, PK값 >
// Insert, Delete, Select만 존재
// select * from account where username = 'spring'
Optional<Account> findByUsername(String username);
}
// 객체를 생성한 상태 (비영속)
Member member = new Member();
member.setId("member");
member.setUsername("회원");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
// 객체를 저장한 상태 (영속)
em.persist(member);
// 회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
em.detach(member);
em.remove(member);
em.setFlushMode(FlushModeType.COMMIT)
)개념
필요 이유
REST 구성요소
REST 제약조건
@RestController
@RequestBody
@ResponseBody
@RequestMapping(value="경로", method=RequestMethod.GET)
@RequestMapping(value="경로", method=RequestMethod.POST)
@PostMapping("경로")
@GetMapping("경로")
@DeleteMapping("경로")
@PutMapping("경로")
@PatchMapping("경로")
package com.basic.myspringboot.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
import java.time.LocalDateTime;
@Entity
@Table(name = "users")
@Getter @Setter
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(unique = true, nullable = false)
private String email;
@Column(nullable = false, updatable = false)
@CreationTimestamp
private LocalDateTime createdAt = LocalDateTime.now();
}
package com.basic.myspringboot.repository;
import com.basic.myspringboot.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
List<User> findByName(String name);
}
controller 패키지 생성
📋 실습 📋 UserBasicRestController.java
package com.basic.myspringboot.controller;
import com.basic.myspringboot.entity.User;
import com.basic.myspringboot.exception.BusinessException;
import com.basic.myspringboot.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
import static org.springframework.util.ClassUtils.isPresent;
@RestController
@RequestMapping("/users")
public class UserBasicRestController {
@Autowired
private UserRepository userRepository;
@PostMapping
public User create(@RequestBody User user) {
return userRepository.save(user);
}
@GetMapping
public List<User> getUsers() {
return userRepository.findAll();
}
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
Optional<User> optionalUser = userRepository.findById(id);
// 하단과 동일 코드
// if(optionalUser.isPresent()) {
// User user = optionalUser.get();
// return user;
// }
// orElseThrow(Supplier) Supplier의 추상메서드가 T get()
User user = optionalUser.orElseThrow(() -> new BusinessException("User Not Found", HttpStatus.NOT_FOUND));
return user;
}
// 그냥 (/{email}) 할 경우 숫자인지 문자열인지 인식 못함
@GetMapping("/email/{email}")
public User getUserByEmail(@PathVariable String email) {
return userRepository.findByEmail(email)
.orElseThrow(() -> new BusinessException("요청하신 email에 해당하는 User가 없습니다", HttpStatus.NOT_FOUND));
}
}
import org.springframework.http.HttpStatus;
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L;
private String message;
private HttpStatus httpStatus;
public BusinessException(String message) {
//417
this(message, HttpStatus.EXPECTATION_FAILED);
}
public BusinessException(String message, HttpStatus httpStatus) {
this.message = message;
this.httpStatus = httpStatus;
}
public String getMessage() {
return this.message;
}
public HttpStatus getHttpStatus() {
return httpStatus;
}
}
import org.springframework.http.HttpStatus;
public class SystemException extends RuntimeException {
private static final long serialVersionUID = 1L;
private String message;
private HttpStatus httpStatus;
private Throwable throwable;
public SystemException(Exception e) {
this(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
public SystemException(String message) {
this(message, HttpStatus.INTERNAL_SERVER_ERROR);
}
public SystemException(String message, Throwable t) {
this.message = message;
this.throwable =t;
}
public SystemException(Throwable t) {
this.throwable = t;
}
public SystemException(String message, HttpStatus httpStatus) {
this.message = message;
this.httpStatus = httpStatus;
}
public String getMessage() {
return this.message;
}
public HttpStatus getHttpStatus() {
return httpStatus;
}
public Throwable getThrowable() {
return this.throwable;
}
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class DefaultExceptionAdvice {
private final Logger LOGGER = LoggerFactory.getLogger(DefaultExceptionAdvice.class);
@ExceptionHandler(BusinessException.class)
protected ResponseEntity<Object> handleException(BusinessException e) {
Map<String, Object> result = new HashMap<String, Object>();
result.put("message", "[안내] " + e.getMessage());
result.put("httpStatus", e.getHttpStatus().value());
return new ResponseEntity<>(result, e.getHttpStatus());
}
@ExceptionHandler(SystemException.class)
protected ResponseEntity<Object> handleException(SystemException e) {
Map<String, Object> result = new HashMap<String, Object>();
result.put("message", "[시스템 오류] " + e.getMessage());
result.put("httpStatus", e.getHttpStatus().value());
return new ResponseEntity<>(result, e.getHttpStatus());
}
//숫자타입의 값에 문자열타입의 값을 입력으로 받았을때 발생하는 오류
@ExceptionHandler(HttpMessageNotReadableException.class)
protected ResponseEntity<Object> handleException(HttpMessageNotReadableException e) {
Map<String, Object> result = new HashMap<String, Object>();
result.put("message", e.getMessage());
result.put("httpStatus", HttpStatus.BAD_REQUEST.value());
return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(Exception.class)
protected ResponseEntity<Object> handleException(Exception e) {
Map<String, Object> result = new HashMap<String, Object>();
ResponseEntity<Object> ret = null;
if (e instanceof BusinessException) {
BusinessException b = (BusinessException) e;
result.put("message", "[안내]\n" + e.getMessage());
result.put("httpStatus", b.getHttpStatus().value());
} else if ( e instanceof SystemException) {
SystemException s = (SystemException)e;
result.put("message", "[시스템 오류]\n" + s.getMessage());
result.put("httpStatus", s.getHttpStatus().value());
ret = new ResponseEntity<>(result, s.getHttpStatus());
LOGGER.error(s.getMessage(), s);
} else {
String msg = "예상치 못한 문제가 발생했습니다.\n관리자에게 연락 하시기 바랍니다.";
result.put("message", msg);
result.put("httpStatus", HttpStatus.INTERNAL_SERVER_ERROR.value());
ret = new ResponseEntity<>(result, HttpStatus.INTERNAL_SERVER_ERROR);
e.printStackTrace();
LOGGER.error(e.getMessage(), e);
}
return ret;
}
}
📖 참고 📖
1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣
⬆️⬇️➡️
📖 참고 📖
1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣
⬆️⬇️➡️
📖 참고 📖
1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣
⬆️⬇️➡️