package jdbc;
추가 # 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;
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
package jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class DBConn {
public static void main(String[] args) {
final String driver = "org.mariadb.jdbc.Driver"; // java.sql의 driver를 구현한 것 (mariadb jar 안에 존재)
final String DB_IP = "localhost";
final String DB_PORT = "3306";
final String DB_NAME = "boot_db"; // DB이름으로 변경
final String DB_URL =
"jdbc:mariadb://" + DB_IP + ":" + DB_PORT + "/" + DB_NAME;
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// mariaDB의 경우 new Driver(), oracle의 경우 newOracleDriver()로 사용
// new를 사용할 경우 벤더 중립적이 되지 못함
// 기존 코드는 따로 설정 파일로 뺄 수도 있음
// 1. Driver class Loading
Class.forName(driver);
System.out.println("DB_URL = " + DB_URL);
// 2. DB와 연결을 담당하는 Connection 객체 생성
conn = DriverManager.getConnection(DB_URL, "boot", "boot");
System.out.println("Connection className = " + conn.getClass().getName());
// Connection className = org.mariadb.jdbc.MariaDbConnection
if (conn != null) {
System.out.println("DB 접속 성공");
}
} catch (ClassNotFoundException e) { // class 예외 처리
System.out.println("드라이버 로드 실패");
e.printStackTrace();
} catch (SQLException e) { // getConnection 예외 처리
System.out.println("DB 접속 실패");
e.printStackTrace();
}
try {
// String sql = "select * from users"; // 테이블명 변경
String sql = "select * from users where userid = ?"; // where문 추가
// 3. SQL문을 DB에게 전달해주는 역할을 하는 Statement 생성
pstmt = conn.prepareStatement(sql);
System.out.println("Statement Class Name = " + pstmt.getClass().getName());
// Statement Class Name = org.mariadb.jdbc.ClientSidePreparedStatement
pstmt.setString(1, "dooly"); // parameter index 1부터 시작 (? 처리 : preapareStatement의 set변수타입 설정)
// 4. SQL문 실행결과를 담는 역할을 하는 ResultSet 생성
rs = pstmt.executeQuery();
System.out.println("ResultSet Class Name = " + rs.getClass().getName());
// ResultSet Class Name = org.mariadb.jdbc.internal.com.read.resultset.SelectResultSet
String userId = null;
String name = null;
String gender = null;
String city = null;
while (rs.next()) { // 메모리의 ResultSet 접근 (다 읽으면 true -> false 출력 )
userId = rs.getString("userid"); // 컬럼명 변경 (컬럼 index도 가능)
name = rs.getString("name"); // getString : 해당 컬럼의 값 가져오기
gender = rs.getString("gender");
city = rs.getString("city");
System.out.print(userId);
System.out.print(name);
System.out.print(gender);
System.out.print(city);
System.out.println();
}
} catch (SQLException e) {
System.out.println("error: " + e);
} finally {
try {
if (rs != null) {
rs.close();
}
if (pstmt != null) {
pstmt.close();
}
if (conn != null && !conn.isClosed()) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
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),
addr varchar(100),
entryDate date,
UNIQUE KEY uk_name (email)
);
// alter table customer add unique index customer_email_idx(email);
alter table customer add unique(id);
Connection n = new Oracle Connection()
할 수도 있지만 특정 DB에 종속됨 package chap10.dao;
import chap10.entity.Member;
import common.DBService;
import java.sql.*;
public class MemberDAO {
public boolean insertMember(Member member) {
boolean result = false;
int rowcnt = 0;
Connection conn = null;
Statement stmt = null;
// 1. Connection Pool에서 connection 얻기
conn = DBService.getConnection();
try {
//2. Statememt 객체 생성하기
stmt = conn.createStatement();
//3. Query 작성
String query = "INSERT INTO CS_MEMBER VALUES('" +
member.getTfMemberID() + "','" +
member.getTfName() + "','" +
member.getTfPassword() + "','" +
member.getTfAddress() + "','" +
member.getTfPhone() + "','" +
member.getSelPasswordQuestion() + "','" +
member.getTfPasswordAnswer() + "','" +
member.getRdMarriage() + "','" +
member.getChkHobby() + "','" +
member.getEtc() +"')";
//4. SQL문전송하기
rowcnt = stmt.executeUpdate( query );
//5. 결과 처리
if ( rowcnt > 0 )
result = true;
else
System.out.println ( "insert시 에러발생" );
}
catch( Exception e ) {
System.out.println( e );
}
finally {
// 6. Statement close
if ( stmt != null ) {
try {
stmt.close();
} catch ( SQLException e ) {
e.printStackTrace();
}
}
//7. DBService에게 connection 반환하기
DBService.releaseConnection( conn );
}
return result;
}
}
// Connection Pool : 미리 필요한 만큼 Connection을 미리 만들어 둠
// public void startServer() {
// System.out.println("2. Connection Pool Init");
//
// try {
// connectionPool = new ConnectionPool(
// jdbcUrl, jdbcUserID, jdbcPassword,
// initNumConnection, maxNumConnection,
// true, 10000
// );
// } catch(Exception e) {
// System.out.println("__ Pool Init Fail: " + e.getMessage());
// }
// }
Source -> Generate Constructor Using Fields
package vo;
public class UserVO {
private int id;
private String UserId;
private String name;
private String gender;
private String city;
public UserVO() { // 기본 생성자
}
public UserVO(int id, String userId, String name, String gender, String city) { // argument 인자 받는 생성자
super();
this.id = id;
UserId = userId;
this.name = name;
this.gender = gender;
this.city = city;
}
public int getId() {
return id;
}
public String getUserId() {
return UserId;
}
public String getName() {
return name;
}
public String getGender() {
return gender;
}
public String getCity() {
return city;
}
}
package dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import vo.UserVO;
public class UserDAO {
private Connection connection;
public UserDAO(String driverClass, String url, String username, String password) {
// 1. Driver class Loading (1번만 실행 필요)
try {
Class.forName(driverClass);
// 2. DB와 연결을 담당하는 Connection 객체 생성
connection = DriverManager.getConnection(url, username, password);
} catch (Exception e) {
e.printStackTrace();
}
}
public void connectionClose() { // // connection은 close 필요
try {
if (connection != null) connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
// where 조건 조회
public UserVO getUser(String userId) {
PreparedStatement pStmt = null;
UserVO userVO = null;
String sql = "select * from users where userid = ?";
// 3. SQL문을 DB에게 전달해주는 역할을 하는 Statement 생성
try {
pStmt = connection.prepareStatement(sql);
pStmt.setString(1, userId);
ResultSet rs = pStmt.executeQuery();
if (rs.next()) {
userVO = new UserVO(rs.getInt("id"),
rs.getString("userId"),
rs.getString("name"),
rs.getString("gender"),
rs.getString("city"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (pStmt != null) pStmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return userVO;
}
// 목록 조회
public List<UserVO> getUserList() {
PreparedStatement pStmt = null;
List<UserVO> userList = new ArrayList<>();
String sql = "select * from users order by id";
try {
pStmt = connection.prepareStatement(sql);
ResultSet rs = pStmt.executeQuery();
while (rs.next()) {
UserVO userVO = new UserVO(rs.getInt("id"),
rs.getString("userId"),
rs.getString("name"),
rs.getString("gender"),
rs.getString("city"));
userList.add(userVO);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (pStmt != null) pStmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return userList;
}
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
// contentType : 브라우저상에 응답 결과에 대한 content type
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Main Page</title>
</head>
<body>
<h1>사용자 관리</h1>
<ul>
<li><a href="userList.do" >사용자 목록</a></li> // a태그에 url-pattern 작성
</ul>
</body>
</html>
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID" version="3.1">
<display-name>MyDynamicWe</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>UserListServlet</servlet-name>
<servlet-class>controller.UserListServlet</servlet-class>
<init-param>
<param-name>driverClass</param-name>
<param-value>org.mariadb.jdbc.Driver</param-value>
</init-param>
<init-param>
<param-name>dbUrl</param-name>
<param-value>jdbc:mariadb://localhost:3306/boot_db</param-value>
</init-param>
<init-param>
<param-name>dbUsername</param-name>
<param-value>boot</param-value>
</init-param>
<init-param>
<param-name>dbPassword</param-name>
<param-value>boot</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>UserListServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
실행
controller 패키지 생성
package controller;
import java.io.IOException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class UserListServlet
*/
// @WebServlet("/userList")
public class UserListServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public UserListServlet() {
super();
}
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println(">> init");
String driver = config.getInitParameter("driverClass"); // org.mariadb.jdbc.Driver (web.xml의 param.value)
String url = config.getInitParameter("dbUrl");
String username = config.getInitParameter("dbUsername");
String password = config.getInitParameter("dbPassword");
System.out.println(driver);
System.out.println(url);
System.out.println(username);
System.out.println(password);
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println(">> doGet");
// response.getWriter() : 응답에 대한 string 생성
response.getWriter().append("Served at: ").append(request.getContextPath());
}
@Override
public void destroy() {
System.out.println(">> destroy");
super.destroy();
}
}
package controller;
import java.io.IOException;
import java.util.List;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import dao.UserDAO;
import vo.UserVO;
/**
* Servlet implementation class UserListServlet
*/
// @WebServlet("/userList")
public class UserListServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
UserDAO userDao;
/**
* @see HttpServlet#HttpServlet()
*/
public UserListServlet() {
super();
}
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println(">> init");
String driver = config.getInitParameter("driverClass"); // org.mariadb.jdbc.Driver (web.xml의 param.value)
String url = config.getInitParameter("dbUrl");
String username = config.getInitParameter("dbUsername");
String password = config.getInitParameter("dbPassword");
System.out.println(driver);
System.out.println(url);
System.out.println(username);
System.out.println(password);
userDao = new UserDAO(driver, url, username, password); // 객체 대입
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println(">> doGet");
// response.getWriter() : 응답에 대한 string 생성
response.getWriter().append("Served at: ").append(request.getContextPath());
List<UserVO> userList = userDao.getUserList(); // 객체 값 부르기
System.out.println(userList);
}
@Override
public void destroy() {
System.out.println(">> destroy");
super.destroy();
}
}
UserVO.java 수정
@Override
public String toString() {
return "UserVO [id=" + id + ", UserId=" + UserId + ", name=" + name + ", gender=" + gender + ", city=" + city
+ "]";
}
화면에 사용자 목록 뿌려줄 jsp 파일 생성
Servlet Request : setAttribute/getAttribute 메서드 (key/value를 string으로 가지고, object 객체 저장)
Request Dispatcher : forward, include 메서드 (resource를 보내줌)
UserListServlet.java 수정
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println(">> doGet");
// response(응답) 데이터를 utf-8로 인코딩
response.setContentType("text/html; charset=UTF-8");
// response.getWriter() : 응답에 대한 string 생성
response.getWriter().append("Served at: ").append(request.getContextPath());
// UserDAO를 호출해서 DB 데이터를 가져오기
List<UserVO> userList = userDao.getUserList();
// Request 객체에 userList를 저장하기
request.setAttribute("users", userList); // key : users, value : userList
// RequestDispatcher 생성하기
RequestDispatcher dispatcher = request.getRequestDispatcher("userList.jsp"); //userList.jsp 포워딩 필요
// userList.jsp 페이지로 포워딩하기
dispatcher.forward(request, response); // 전달 받은 인자값 그대로 forward
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>사용자 리스트</h2>
${users}
<!-- ${users}는 key값이고, 하단과 동일-->
<!-- (List<UserVO> request.getAttribute("users"); -->
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> // corlibrary 사용을 위해 상단에 코드 추가
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>사용자 리스트</h2>
<%-- ${users} --%>
<table border="1">
<tr>
<th>ID</th>
<th>UserId</th>
<th>Name</th>
<th>Gender</th>
<th>City</th>
</tr>
<c:forEach var="user" items="${users}">
<%-- user는 UserVO를 의미 --%>
<tr>
<td>${user.id}</td>
<td>${user.userId}</td>
<td>${user.name}</td>
<td>${user.gender}</td>
<td>${user.city}</td>
</tr>
</c:forEach>
</table>
</body>
</html>
springfwxml.zip
springfwconfi.zip (No XML)
myspringfw.zip
mavem / gradle : 빌드 라이브러리
Spring framework beans factory
Setter Injection : <property> 태그
pom.xml
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
package myspring.di.xml;
public interface Printer {
public void print(String message);
}
package myspring.di.xml;
public class ConsolePrinter implements Printer {
public ConsolePrinter() {
System.out.println(this.getClass().getName() + "생성자 호출됨");
}
public void print(String message) {
System.out.println(message);
}
}
package myspring.di.xml;
public class StringPrinter implements Printer {
private StringBuffer buffer = new StringBuffer();
public StringPrinter() {
System.out.println(this.getClass().getName() + "생성자 호출됨");
}
public void print(String message) {
this.buffer.append(message);
}
public String toString() {
return this.buffer.toString();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- StringPrinter 클래스를 Spring Bean으로 등록 -->
<!-- getBean() 메서드 사용시 bean의 id 사용 -->
<bean id="strPrinter" class="myspring.di.xml.StringPrinter" />
<!-- ConsolePrinter 클래스를 Spring Bean으로 등록 -->
<!-- 인터페이스는 작성할 필요X -->
<bean id="conPrinter" class="myspring.di.xml.ConsolePrinter" />
<!-- Hello 클래스를 Spring Bean으로 등록 -->
<bean id="hello" class="myspring.di.xml.Hello">
<property name="name" value="스프링"/>
<!-- value는 setName의 값으로 들어감 -->
<property name="printer" ref="strPrinter" />
</bean>
</beans>
BeanFactory factory = new GenericXmlApplicationContext(“classpath:spring-beans.xml “);
ApplicationContext context = new GenericXmlApplicationContext(“classpath:spring-beans.xml “);
UserDao userDao = (UserDao) context.getBean(“userDao”); // getBean의 "userDao"는 Bean 등록할 때의 id값
package myspring.di.xml;
import static org.junit.jupiter.api.Assertions.*; // static 메서드를 테스트케이스에 많이 사용하여 static import를 사용하여 Assertions.생략 가능
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.GenericXmlApplicationContext;
public class HelloBeansJunitTest {
@Test
void hello() {
// 1. Spring Bean Container 객체 생성
// classpath에 xml 파일 경로 지정
BeanFactory factory = new GenericXmlApplicationContext("classpath:spring-beans.xml");
// 2. Container가 생성한 Bean을 요청하기
Hello hello1 = (Hello) factory.getBean("hello");
Hello hello2 = factory.getBean("hello", Hello.class); // 해당 방법 권장
// 3. HelloBean의 레퍼런스 비교하기
System.out.println(hello1 == hello2); // 싱글톤인지 아닌지 확인 목적
assertSame(hello1, hello2); // 주소가 같으면 테스트 성공으로 표시
assertEquals("Hello 스프링", hello2.sayHello()); // 예상한 값과 일치한지 확인
hello2.print();
Printer printer = factory.getBean("strPrinter", Printer.class);
assertEquals("Hello 스프링", printer.toString());
public class Hello {
String name;
Printer printer;
List<String> names;
public Hello() {
System.out.println(this.getClass().getName() + "생성자 호출됨");
}
public Hello(String name, Printer printer) {
System.out.println(this.getClass().getName() + "오버로딩된 생성자 호출됨");
this.name = name;
this.printer = printer;
}
...
}
<bean id="helloC" class="myspring.di.xml.Hello">
<!-- Constructor Injection -->
<constructor-arg index="0" value="생성자"/>
<constructor-arg index="1" ref="conPrinter"/>
</bean>
package myspring.di.xml;
import static org.junit.jupiter.api.Assertions.*; // static 메서드를 테스트케이스에 많이 사용하여 static import를 사용하여 Assertions.생략 가능
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.GenericXmlApplicationContext;
public class HelloBeansJunitTest {
BeanFactory factory;
@BeforeEach
void init() {
// 1. Spring Bean Container 객체 생성
// classpath에 xml 파일 경로 지정
factory = new GenericXmlApplicationContext("classpath:spring-beans.xml");
}
@Test
void 생성자주입테스트() {
Hello bean = factory.getBean("helloC", Hello.class);
assertEquals("Hello 생성자", bean.sayHello());
bean.print();
}
@Test @Disabled
void hello() {
// 2. Container가 생성한 Bean을 요청하기
Hello hello1 = (Hello) factory.getBean("hello");
Hello hello2 = factory.getBean("hello", Hello.class); // 해당 방법 권장
// 3. HelloBean의 레퍼런스 비교하기
System.out.println(hello1 == hello2); // 싱글톤인지 아닌지 확인 목적
assertSame(hello1, hello2); // 주소가 같으면 테스트 성공으로 표시
assertEquals("Hello 스프링", hello2.sayHello()); // 예상한 값과 일치한지 확인
hello2.print();
Printer printer = factory.getBean("strPrinter", Printer.class);
assertEquals("Hello 스프링", printer.toString());
}
}
public List<String> getNames() {
return this.names;
}
public void setNames(List<String> list) {
System.out.println("Hello setNames() " + list);
this.names = list;
}
<bean id="helloC" class="myspring.di.xml.Hello">
<!-- Constructor Injection -->
<constructor-arg index="0" value="생성자"/>
<constructor-arg index="1" ref="conPrinter"/>
<property name="names">
<list>
<value>Spring Framework</value>
<value>Spring Boot</value>
<value>Spring Cloud</value>
</list>
</property>
</bean>
@Test
void 생성자주입테스트() {
Hello bean = factory.getBean("helloC", Hello.class);
assertEquals("Hello 생성자", bean.sayHello());
bean.print();
List<String> names = bean.getNames();
assertEquals(3, names.size());
assertEquals("Spring Boot", names.get(1));
}
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
<!-- scope : test라고 이름 지어진 폴더에만 적용 -->
</dependency>
package myspring.di.xml;
import static org.junit.jupiter.api.Assertions.*;
import javax.annotation.Resource;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
// Spring Bean Container 객체 생성 대신 @ExtendWith 사용 (싱글톤의 Application Context 보장)
// classpath로 경로 설정 대신 @ContextConfiguration (스프링 빈 설정 파일의 위치)
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "classpath:spring-beans.xml")
public class HelloBeanSpringTest {
// 1/ getBean 대신에 @Autowired 의존 받고 싶은 타입을 자동 연결
// type이 동일할 경우 Bean의 변수명과 Bean의 id가 동일한 것을 찾아옴 (동일한게 없을 경우 찾을 수 없다고 표시)
@Autowired
Hello hello;
@Autowired
// 2. Qualifier : 범위 한정자 (Autowired와 함께 기재)
@Qualifier("helloC")
Hello hello2;
// 3. 원하는 Bean의 id 값으로 가져옴
@Resource(name = "helloC")
Hello hello3;
@Autowired
@Qualifier("strPrinter")
Printer printer;
//@Autowired
//StringPrinter printer;
@Test
void helloC() {
assertEquals("Hello 생성자", hello2.sayHello());
assertEquals("Hello 생성자", hello3.sayHello());
}
@Test // @Disabled
void hello() {
assertEquals("Hello 스프링", hello.sayHello()); // import static으로 인해 Assertions.assertEquals와 동일
hello.print();
assertEquals("Hello 스프링", printer.toString());
}
}
XML 설정 단독 사용
XML 존재 이유
@Value
@Component : pacakage의 위치 알려줌
@Component와 @Bean의 차이점
myspring.di.xml 복사하여 mysping.di.annot로 붙여넣기
package myspring.di.annot;
import org.springframework.stereotype.Component;
// target : 클래스 위에 선언할 수 있음
// retention : runtime에 실행됨을 보장
// id 설정 안하면 소문자 클래스명으로 자동 설정됨
@Component("stringPrinter")
public class StringPrinter implements Printer {
private StringBuffer buffer = new StringBuffer();
public StringPrinter() {
System.out.println(this.getClass().getName() + "생성자 호출됨");
}
public void print(String message) {
this.buffer.append(message);
}
public String toString() {
return this.buffer.toString();
}
}
package myspring.di.annot;
import org.springframework.stereotype.Component;
@Component("consolePrinter")
public class ConsolePrinter implements Printer {
public ConsolePrinter() {
System.out.println(this.getClass().getName() + "생성자 호출됨");
}
public void print(String message) {
System.out.println(message);
}
}
package myspring.di.annot;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
// xml파일의 bean 태그와 동일
@Component("helloBean")
public class Hello {
// property 속성의 value와 동일
@Value("어노테이션")
String name;
// preoverty 속성의 ref와 동일
@Autowired
@Qualifier("strPrinter")
Printer printer;
List<String> names;
public Hello() {
System.out.println(this.getClass().getName() + "생성자 호출됨");
}
public Hello(String name, Printer printer) {
System.out.println(this.getClass().getName() + "오버로딩된 생성자 호출됨");
this.name = name;
this.printer = printer;
}
public List<String> getNames() {
return this.names;
}
public void setNames(List<String> list) {
System.out.println("Hello setNames() " + list);
this.names = list;
}
// 어노테이션 사용시 없어도 됨
// public void setName(String name) {
// System.out.println("Hello setName() " + name);
// this.name = name;
// }
//
// public void setPrinter(Printer printer) {
// System.out.println("Hello setPrinter " + printer.getClass().getName());
// this.printer = printer;
// }
public String sayHello() {
return "Hello " + name;
}
public void print() {
this.printer.print(sayHello());
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 어느테이션이 선언된 클래스들을 스캔하기 위한 설정 -->
<!-- context라는 prefix 설정하는 이유
- namespace : 기능별로 xml의 태그명이 똑같더라도 namespace가 다르면 구분 가능
- beans는 default namespace라서 prefix 없이 사용
- context는 :context라는 prefix가 있어서 태그명에 작성해야함-->
<context:component-scan base-package="myspring.di.annot"/>
</beans>
package myspring.di.annot;
import javax.annotation.Resource;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
// file:src/main/resources/spring-beans-annot.xml와 classpath:spring-beans-annot.xml 동일
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "classpath:spring-beans-annot.xml")
public class AnnotatedHelloBeanTest {
// Hello가 종류가 1개밖에 없어서 Bean의 id와 일치하지 않아도 상관 없음 (type으로 찾기 때문에)
@Autowired
Hello hello;
@Resource(name="stringPrinter")
Printer printer;
@Test
public void helloBean() {
Assertions.assertEquals("Hello 어노테이션", hello.sayHello());
hello.print();
Assertions.assertEquals("Hello 어노테이션", printer.toString());
}
}
package myspring.di.annot;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component("helloCons")
public class HelloCons {
// @Value("어노테이션")
String name;
// @Autowired
// @Qualifier("stringPrinter")
Printer printer;
List<String> names;
public HelloCons() {
System.out.println(this.getClass().getName() + "생성자 호출됨");
}
// 생성자 통해서 injection 받기
// 생성자를 argument에 적용
@Autowired
public HelloCons(@Value("annot생성자") String name, @Qualifier("consolePrinter") Printer printer) {
System.out.println(this.getClass().getName() + "오버로딩된 생성자 호출됨");
this.name = name;
this.printer = printer;
}
public List<String> getNames() {
return this.names;
}
public void setNames(List<String> list) {
System.out.println("Hello setNames() " + list);
this.names = list;
}
// 어노테이션 사용시 없어도 됨
// public void setName(String name) {
// System.out.println("Hello setName() " + name);
// this.name = name;
// }
//
// public void setPrinter(Printer printer) {
// System.out.println("Hello setPrinter " + printer.getClass().getName());
// this.printer = printer;
// }
public String sayHello() {
return "Hello " + name;
}
public void print() {
this.printer.print(sayHello());
}
}
@Component
@Repository
@Service
@Controller
no XML
package myspring.di.annot.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
//xml을 대신해서 설정 역할을 하는 클래스
@Configuration
@ComponentScan(basePackages = {"myspring.di.annot"})
public class AnnotatedHelloConfig {
}
package myspring.di.annot.config;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import myspring.di.annot.Hello;
// Bean 컨테이너 종류가 바뀌어서 loader를 통해 가져옴
// AnnotationConfigContextLoader.class는 AnnotationConfigApplicationContext 라는 Spring Bean Container를 로딩해주는 Loader 클래스
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = AnnotatedHelloConfig.class, loader = AnnotationConfigContextLoader.class)
public class AnnotatedHelloConfigTest {
@Autowired
Hello hello;
@Test
public void hello() {
System.out.println(hello.sayHello()); // Hello 어노테이션
}
}
@Configuration과 @Bean 차이점
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
src/main/java -> myspring.di.xml.config 패키지 생성 -> XmlHelloConfig 생성
package myspring.di.xml.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import myspring.di.xml.ConsolePrinter;
import myspring.di.xml.Hello;
import myspring.di.xml.Printer;
import myspring.di.xml.StringPrinter;
@Configuration
public class XmlHelloConfig {
/*
* <bean id="strPrinter" class="myspring.di.xml.StringPrinter" />
*/
// qualifier 하지 않아도 메서드 이름이 bean의 id값
@Bean
public Printer strPrinter() {
return new StringPrinter();
}
@Bean
public Printer conPrinter() {
return new ConsolePrinter();
}
@Bean
public Hello hello() {
Hello hello = new Hello();
hello.setName("Java컨피그");
hello.setPrinter(strPrinter());
return hello;
}
}
package myspring.di.xml.config;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import myspring.di.xml.Hello;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = XmlHelloConfig.class)
public class XmlHelloConfigTest {
@Autowired
Hello hello;
@Test
public void hello() {
System.out.println(hello.sayHello()); // Hello Java컨피그
}
}
xml 파일 안에 sql + VO 객체 정의
DB 연결하기 위한 과정
1) mariadb
2) HikariCP
3) SpringJDBC
2) org.springframework.jdbc의 SimpleDriverDataSource
3) Spring JDBC -> pom.xml 추가
spring bean configurantion 파일 -> spring-beans-user.xml -> beans(.xsd) + context(.xsd) + p 선택
db.driverClass=org.mariadb.jdbc.Driver
db.url=jdbc:mariadb://127.0.0.1:3306/boot_db?useUnicode=true&charaterEncoding=utf-8&useSSL=false&serverTimezone=UTC
db.username=boot
db.password=boot
myname=Spring
myprinter=printer
value1=JUnit
value2=AOP
value3=DI
printer1=stringPrinter
printer2=consolePrinter
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- Properties file 정보 설정 -->
<context:property-placeholder location="classpath:value.properties"/>
<!-- DataSource 구현체인 HikariDataSource를 SpringBean으로 등록 -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource"
p:driverClassName="${db.driverClass}"
p:jdbcUrl="${db.url}"
p:username="${db.username}"
p:password="${db.password}"
/>
</beans>
package myspring.user;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "classpath:spring-beans-user.xml")
public class UserDBTest {
@Autowired
DataSource dataSource;
@Test
public void conn() {
try {
Connection connection = dataSource.getConnection();
DatabaseMetaData metaData = connection.getMetaData();
System.out.println("DB Product Name : " + metaData.getDatabaseProductName()); //MariaDB
System.out.println("DB Driver : " + metaData.getDriverName()); // MariaDB Connector/J
System.out.println("DB URL : " + metaData.getURL()); // jdbc:mariadb://127.0.0.1/boot_db?user=boot&password=***&...
System.out.println("DB Username : " + metaData.getUserName()); // boot
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
setDataSource()
메서드의 인자로 hikaridatasource 들어옴setConfigLocation()
으로 MyBatisConfig(sqlMapConfig) 파일 연결setMapperLocations()
mapping (UserMapper) 파일 연결<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- Properties file 정보 설정 -->
<context:property-placeholder location="classpath:value.properties"/>
<!-- DataSource 구현체인 HikariDataSource를 SpringBean으로 등록 -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource"
p:driverClassName="${db.driverClass}"
p:jdbcUrl="${db.url}"
p:username="${db.username}"
p:password="${db.password}"
/>
<!-- Mybatis-spring의 SqlSessionFactoryBean을 SpringBean으로 등록 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml" />
<property name="mapperLocations">
<list>
<value>classpath:mybatis/*Mapper.xml</value>
</list>
</property>
</bean>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- Properties file 정보 설정 -->
<context:property-placeholder location="classpath:value.properties"/>
<!-- DataSource 구현체인 HikariDataSource를 SpringBean으로 등록 -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource"
p:driverClassName="${db.driverClass}"
p:jdbcUrl="${db.url}"
p:username="${db.username}"
p:password="${db.password}"
/>
<!-- Mybatis-spring의 SqlSessionFactoryBean을 SpringBean으로 등록 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml" />
<property name="mapperLocations">
<list>
<value>classpath:mybatis/*Mapper.xml</value>
</list>
</property>
</bean>
<!-- SqlSessionTemplate -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sqlSessionFactory"/>
</bean>
</beans>
package myspring.user;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import myspring.user.vo.UserVO;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "classpath:spring-beans-user.xml")
public class UserDBTest {
@Autowired
DataSource dataSource;
@Autowired
SqlSessionFactory sessionFactory;
@Autowired
SqlSession sqlSession;
@Test
public void session() {
UserVO user = sqlSession.selectOne("userNS.selectUserById", "dooly");
System.out.println(user);
}
@Test
public void sessionFactory() {
System.out.println(sessionFactory.getClass().getName()); // injection 잘 되었는지 확인 org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
}
@Test
public void conn() {
try {
Connection connection = dataSource.getConnection();
DatabaseMetaData metaData = connection.getMetaData();
System.out.println("DB Product Name : " + metaData.getDatabaseProductName()); //MariaDB
System.out.println("DB Driver : " + metaData.getDriverName()); // MariaDB Connector/J
System.out.println("DB URL : " + metaData.getURL()); // jdbc:mariadb://127.0.0.1/boot_db?user=boot&password=***&...
System.out.println("DB Username : " + metaData.getUserName()); // boot
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} // conn
}
package myspring.user.dao.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import myspring.user.vo.UserVO;
public interface UserMapper {
//@Select("select * from users where userid=#{id}")
//UserVO selectUserById(@Param("id") String id);
UserVO selectUserById(String id);
List<UserVO> selectUserList();
void insertUser(UserVO userVO);
void updateUser(UserVO userVO);
void deleteUser(String id);
}
setBasePackage()
: 패키지 위치 알려줌setSqlSessionTemplateBeanName()
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- Properties file 정보 설정 -->
<context:property-placeholder location="classpath:value.properties"/>
<!-- DataSource 구현체인 HikariDataSource를 SpringBean으로 등록 -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource"
p:driverClassName="${db.driverClass}"
p:jdbcUrl="${db.url}"
p:username="${db.username}"
p:password="${db.password}"
/>
<!-- Mybatis-spring의 SqlSessionFactoryBean을 SpringBean으로 등록 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml" />
<property name="mapperLocations">
<list>
<value>classpath:mybatis/*Mapper.xml</value>
</list>
</property>
</bean>
<!-- SqlSessionTemplate -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sqlSessionFactory"/>
</bean>
<!-- Mybatis-Spring의 MapperScannerConfigurer을 SpringBean으로 등록 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 참조하는 것 없어서 bean id 없어도 됨 -->
<property name="basePackage" value="myspring.user.dao.mapper" />
<property name="sqlSessionTemplateBeanName" value="sqlSession"/>
</bean>
</beans>
package myspring.user;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import myspring.user.dao.mapper.UserMapper;
import myspring.user.vo.UserVO;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "classpath:spring-beans-user.xml")
public class UserDBTest {
@Autowired
DataSource dataSource;
@Autowired
SqlSessionFactory sessionFactory;
@Autowired
SqlSession sqlSession;
@Autowired
UserMapper userMapper;
@Test
public void mapper() {
// id가 메서드 이름이 되어 argument 전달
UserVO user = userMapper.selectUserById("dooly");
System.out.println(user);
}
@Test @Disabled
public void session() {
// mapper인터페이스를 사용하지 않아 sqlsession의 메서드의 인자에 문자열로 namespace.SQL id 입력
UserVO user = sqlSession.selectOne("userNS.selectUserById", "dooly");
System.out.println(user);
}
@Test
public void sessionFactory() {
System.out.println(sessionFactory.getClass().getName()); // injection 잘 되었는지 확인 org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
}
@Test
public void conn() {
try {
Connection connection = dataSource.getConnection();
DatabaseMetaData metaData = connection.getMetaData();
System.out.println("DB Product Name : " + metaData.getDatabaseProductName()); //MariaDB
System.out.println("DB Driver : " + metaData.getDriverName()); // MariaDB Connector/J
System.out.println("DB URL : " + metaData.getURL()); // jdbc:mariadb://127.0.0.1/boot_db?user=boot&password=***&...
System.out.println("DB Username : " + metaData.getUserName()); // boot
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} // conn
}
customerDAO
UserDBTest.java수정
package myspring.user;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import myspring.user.dao.mapper.UserMapper;
import myspring.user.service.UserService;
import myspring.user.vo.UserVO;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "classpath:spring-beans-user.xml")
public class UserDBTest {
@Autowired
DataSource dataSource;
@Autowired
SqlSessionFactory sessionFactory;
@Autowired
SqlSession sqlSession;
@Autowired
UserMapper userMapper;
@Autowired
UserService userService;
@Test
public void service() {
UserVO user = userService.getUser("dooly");
System.out.println(user);
}
@Test @Disabled
public void mapper() {
// id가 메서드 이름이 되어 argument 전달
UserVO user = userMapper.selectUserById("dooly");
System.out.println(user);
}
@Test @Disabled
public void session() {
// mapper인터페이스를 사용하지 않아 sqlsession의 메서드의 인자에 문자열로 namespace.SQL id 입력
UserVO user = sqlSession.selectOne("userNS.selectUserById", "dooly");
System.out.println(user);
}
@Test
public void sessionFactory() {
System.out.println(sessionFactory.getClass().getName()); // injection 잘 되었는지 확인 org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
}
@Test
public void conn() {
try {
Connection connection = dataSource.getConnection();
DatabaseMetaData metaData = connection.getMetaData();
System.out.println("DB Product Name : " + metaData.getDatabaseProductName()); //MariaDB
System.out.println("DB Driver : " + metaData.getDriverName()); // MariaDB Connector/J
System.out.println("DB URL : " + metaData.getURL()); // jdbc:mariadb://127.0.0.1/boot_db?user=boot&password=***&...
System.out.println("DB Username : " + metaData.getUserName()); // boot
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} // conn
}
요청 받는 controller를 Front Controller 패턴 사용
web.xml 설정
(기존) JVM -> Spring FrameWork -> App (+ 설정 파일)
(변경) JVM -> tomcat -> Spring FrameWork -> App
(spring 설정 파일을 tomcat에 알려주면, 해석해서 처리해줌)
spring web mvc -> pom.xml 추가
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID" version="3.1">
<display-name>CustomerSpringWeb</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<!-- needed for ContextLoaderListener -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-beans-user.xml</param-value>
</context-param>
<!-- Bootstraps the root web application context before servlet initialization -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- The front controller of this Spring Web application, responsible for handling all application requests -->
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-beans-user.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Map all requests to the DispatcherServlet for handling -->
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
tomcat에 구동
2) UserController 작성
myspring.user.controller 패키지 생성
동일한 객체 2번 호출하는 이유
resources 폴더에 beans-web.xml 복사 -> resources 폴더에 붙여넣기
beans-web.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
<context:component-scan base-package="myspring.user">
<!-- 상단 코드에서 myspring.user 패키지를 모두 불러왔지만, 해당 presentation layer를 위한 xml 설정파일이기 때문에 myspring.user 하단의 .controller만 include -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- Spring MVC에 필요한 Bean들을 자동으로 등록해주는 태그-->
<mvc:annotation-driven />
<!-- DispatcherServlet의 변경된 url-pattern 때문에 필요한 태그 설정 -->
<mvc:default-servlet-handler/>
<!-- 아래 주석은 Controller에서 포워딩 되는 .jsp 확장자를 생략할 수 있다. -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/" />
<property name="suffix" value=".jsp" />
</bean>
<!-- annotation-driven 태그에서 내부적으로 처리해주는 설정 -->
<!-- <bean id="jsonHttpMessageConverter" -->
<!-- class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" /> -->
<!-- <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> -->
<!-- <property name="messageConverters"> -->
<!-- <list> -->
<!-- <ref bean="jsonHttpMessageConverter"/> -->
<!-- </list> -->
<!-- </property> -->
<!-- </bean> -->
</beans>
<context:component-scan base-package="myspring.user" >
<!-- 하단의 myspring.user에서 controller만 제외하고 scan -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:beans-web.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
이제 객체 생성 1번만 발생한 것을 확인
index.jsp 수정
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>user</title>
</head>
<body>
<h1>사용자 관리 메인</h1>
<ul>
<li><a href="userList.do">User 리스트</a></li>
</ul>
</body>
</html>
package myspring.user.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import myspring.user.service.UserService;
import myspring.user.vo.UserVO;
@Controller
public class UserController {
@Autowired
private UserService userService;
public UserController() {
System.out.println(this.getClass().getName() + "생성자 호출됨");
}
// db에서 가져오고 화면에 보이는 것도 함께 설정
@RequestMapping("/userList.do")
public ModelAndView userList() {
// service 불러와서 리스트로 받기
// 뿌려줄 jsp 페이지를 ModelAndView 객체에 담음 (viewName=jsp파일 이름(jsp확장자 없이 이름만 기재), modelName=키값(forEach구문의 items), modelList=서비스에서 받아온 list 기재)
// key 값(userList)과 일치하여 list 변수명 바꾸기
List<UserVO> userVOList = userService.getUserList();
// ModelAndView(viewName, keyName, valueObject)
return new ModelAndView("userList", "userList", userVOList);
}
}
package myspring.user.controller;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import myspring.user.service.UserService;
import myspring.user.vo.UserVO;
@Controller
public class UserController {
@Autowired
private UserService userService;
public UserController() {
System.out.println(this.getClass().getName() + "생성자 호출됨");
}
// db에서 가져오고 화면에 보이는 것도 함께 설정
// View와 Model을 한꺼번에 전달하는 방법
@RequestMapping("/userList.do")
public ModelAndView userList() {
// service 불러와서 리스트로 받기
// 뿌려줄 jsp 페이지를 ModelAndView 객체에 담음 (viewName=jsp파일 이름(jsp확장자 없이 이름만 기재), modelName=키값(forEach구문의 items), modelList=서비스에서 받아온 list 기재)
// key 값(userList)과 일치하여 list 변수명 바꾸기
List<UserVO> userVOList = userService.getUserList();
// ModelAndView(viewName, keyName, valueObject)
return new ModelAndView("userList", "userList", userVOList);
}
//getUser.do?id=dooly
// View와 Model을 분리해서 전달하는 방법
@RequestMapping("/getUser.do")
public String getUser(@RequestParam("id") String userId, Model model) {
// @requestparam을 이용하여 ?(쿼리 스트링) 다음의 id 값 가져올 수 있음
// 받아온 userVO를 model에 담아줌
UserVO userVO = userService.getUser(userId);
model.addAttribute("user", userVO);
// 페이지 이름 return
return "userInfo";
}
}
설치
StarUML
IntelliJ
// pom.xml에 dependency 추가
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
explore
download
IntelliJ
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>Hello 스프링부트</h2>
</body>
</html>
<context:component-scan base-package="myspring.customer">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- Spring MVC에 필요한 Bean들을 자동으로 등록해주는 태그-->
<mvc:annotation-driven />
<!-- DispatcherServlet의 변경된 url-pattern 때문에 필요한 태그 설정 -->
<mvc:default-servlet-handler/>
<!-- 아래 주석은 Controller에서 포워딩 되는 .jsp 확장자를 생략할 수 있다. -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/" />
<property name="suffix" value=".jsp" />
</bean>
스프링 부트가 제공해주는 기존의 버전을 pom.xml에서 원하는 버전으로 변경 가능
기존의 spring-boot-starter-parent/spring-boot-dependencies의 코드 복사 -> pom.xml에 추가
<properties>
<java.version>17</java.version>
<!-- spring framework 버전 바꾸기 가능 (spring-boot-dependencies에서 추가)-->
<spring-framework.version>6.0.11</spring-framework.version>
</properties>
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 스프링부트");
}
}
server.port=8087
WebApplicationType (일반 프로젝트 용도로 변경하고자 할 경우)
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);
}
}
resources/banner.txt (배너 변경)
,--. ,--. ,-----. ,--.
| `.' |,--. ,--.| |) /_ ,---. ,---. ,-' '-.
| |'.'| | \ ' / | .-. \| .-. | .-. |'-. .-'
| | | | \ ' | '--' /' '-' ' '-' ' | |
`--' `--'.-' / `------' `---' `---' `--'
`---'
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> (개발 프로젝트이 버전 설정 가능)
java home과 path 설정
// 경로 복사 후 설정
set JAVA_HOME = C:\Users\Administrator\Desktop\ucamp\springboot\graalvm-jdk-17_windows-x64_bin\graalvm-jdk-17.0.8+9.1
// path 설정 (%는 기존 path 유지 후 새로 추가 목적)
set PATH = %PATH%;%JAVA_HOME%\bin;
// 자바 버전 확인
java -version
변경된 설정 방법
.jar 생성
// terminal에 실행
mvnw package
java -jar .\target\MySpringBoot3-0.0.1-SNAPSHOT.jar
ApplicationRuneer 인터페이스
ApplicationRuneer는 org.springframework.boot 패키지 하단에 존재
application이 실행되는지 인지하는 역할 (tomcat 구동 확인하는 listner와 유사)
SpringApplication이 시작되면 run메서드가 실행됨
-> 인터페이스 implement 받고, run 메서드 오버라이딩 해서 사용 (오버라이딩시 람다식 사용 가능)
-> run 메서드를 만들고, @Order 사용하여 여러개의 run의 순서 설정 가능 (순서가 낮을 수록 우선순위 높음)
runner 패키지 생성
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");
}
}
argument 전달 확인
argument 전달
환경 변수 get/set 방식
value
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);
// ApplicationArguments는 main메서드의 (String[] args) argument를 전달 받음
System.out.println("VM Argument foo = " + args.containsOption("foo"));
System.out.println("Program argument bar = " + args.containsOption("bar"));
}
}
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 {
@Value("${myboot.name}")
private String name;
@Value("${myboot.age}")
private int age;
@Value("${myboot.fullName}")
private String fullName;
@Autowired
private Environment environment;
@Override
public void run(ApplicationArguments args) throws Exception {
// 포트 번호 받아오기
System.out.println("Port Number = " + environment.getProperty("local.server.port"));
// 환경변수 받아오기
System.out.println("myboot.name = " + name);
System.out.println("myboot.age = " + age);
System.out.println("myboot.fullName = " + fullName);
// ApplicationArguments는 main메서드의 (String[] args) argument를 전달 받음
System.out.println("VM Argument foo = " + args.containsOption("foo"));
System.out.println("Program argument bar = " + args.containsOption("bar"));
}
}
java -jar jar파일 --myboot.name=이름 4위
java -jar .\target\MySpringBoot3-0.0.1-SNAPSHOT.jar --myboot.name=Spring
github 연결
개발/스테이징/테스트/운영 등 상황에 맞게 환경설정 파일 다르게 구분 가능
com.basic.myspringboot.dto 패키지 생성
빌더 패턴
com.basic.myspringboot.config 패키지 생성
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로 바꿔주는 기능
}
}
#server.port=8087
# 스프링 유니코드 작성
myboot.name=\uc2a4\ud504\ub9c1
myboot.age=${random.int(1,50)}
myboot.fullName=${myboot.name} Boot
# 현재 활성화 중인 환경 설정
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 {
@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;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("Customer 현재 모드 = " + customer.getName());
// 포트 번호 받아오기
System.out.println("Port Number = " + environment.getProperty("local.server.port"));
// 환경변수 받아오기
System.out.println("myboot.name = " + name);
System.out.println("myboot.age = " + age);
System.out.println("myboot.fullName = " + fullName);
// ApplicationArguments는 main메서드의 (String[] args) argument를 전달 받음
System.out.println("VM Argument foo = " + args.containsOption("foo"));
System.out.println("Program argument bar = " + args.containsOption("bar"));
}
}
// 커맨드 라인이 우선순위 높아서 prod로 적용 확인
java -jar .\target\MySpringBoot3-0.0.1-SANPSHOT.jar --spring.profiles.active=prod
DB에 따라 모드 구분
application-test.properties 생성
myboot.name=\uc2a4\ud504\ub9c1 TEST Mode
myboot.name=\uc2a4\ud504\ub9c1 PROD Mode
mvnw package
java -jar .\target\MySpringBoot3-0.0.1-SANPSHOT.jar --spring.profiles.active=prod
로깅 퍼사드
로깅
스트링 부트 로깅
로그 레벨 종류
application-test.properties
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>
ORM(Object Relational Mapping)
JPA(Java Persistence API)
myboot.name=\uc2a4\ud504\ub9c1 TEST Mode
# 개발 log level = debug
logging.level.com.basic.myspringboot=debug
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class
-name=org.h2.Driver
spring.datasource.username=sa
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());
}
}
}
localhost:8080/h2-console
입력Spring Boot 데이터_Entity 클래스
com.basic.myspringboot.entity 클래스 생성
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;
}
ACCOUNT 테이블 확인
MariaDB 연결 설정
myboot.name=\uc2a4\ud504\ub9c1 PROD Mode
# 운영 log level = info
logging.level.com.basic.myspringboot=info
# 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
Data Sources and Drivers
spring.jpa.hibernate.ddl-auto
spring.jpa.show-sql=true
myboot.name=\uc2a4\ud504\ub9c1 PROD Mode
# 운영 log level = info
logging.level.com.basic.myspringboot=info
# 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
myboot.name=\uc2a4\ud504\ub9c1 PROD Mode
# 운영 log level = info
logging.level.com.basic.myspringboot=info
# 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
//Primary Key의 값 자동 증가 전략 4가지
public enum GenerationType {
/**
* Indicates that the persistence provider must assign
* primary keys for the entity using an underlying
* database table to ensure uniqueness.
*/
TABLE,
/**
* Indicates that the persistence provider must assign
* primary keys for the entity using a database sequence.
* Sequence 를 제공하는 Oracle
*/
SEQUENCE,
/**
* Indicates that the persistence provider must assign
* primary keys for the entity using a database identity column.
* Column Auto Increment를 제공하는 MySql, MariaDB
*/
IDENTITY,
/**
* Indicates that the persistence provider should pick an
* appropriate strategy for the particular database. The
* <code>AUTO</code> generation strategy may expect a database
* resource to exist, or it may attempt to create one. A vendor
* may provide documentation on how to create such resources
* in the event that it does not support schema generation
* or cannot create the schema resource at runtime.
*/
AUTO (default)
}
Dialect(방언, 사투리)
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDB103Dialect
Hibernate Dialect Api doc
https://docs.jboss.org/hibernate/orm/6.2/javadocs/org/hibernate/dialect/package-summary.html
GenerationType.Auto
MariaDB [boot_db]> desc account;
+----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| id | bigint(20) | NO | PRI | NULL | |
| password | varchar(255) | YES | | NULL | |
| username | varchar(255) | YES | UNI | NULL | |
+----------+--------------+------+-----+---------+-------+
account_seq Sequence 추가로 생성된다.
MariaDB [boot_db]> desc account_seq;
+-----------------------+---------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------------------+---------------------+------+-----+---------+-------+
| next_not_cached_value | bigint(21) | NO | | NULL | |
| minimum_value | bigint(21) | NO | | NULL | |
| maximum_value | bigint(21) | NO | | NULL | |
| start_value | bigint(21) | NO | | NULL | |
| increment | bigint(21) | NO | | NULL | |
| cache_size | bigint(21) unsigned | NO | | NULL | |
| cycle_option | tinyint(1) unsigned | NO | | NULL | |
| cycle_count | bigint(21) | NO | | NULL | |
+-----------------------+---------------------+------+-----+---------+-------+
GenerationType.Identity ( auto increment column )
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| password | varchar(255) | YES | | NULL | |
| username | varchar(255) | YES | UNI | NULL | |
+----------+--------------+------+-----+---------+----------------+
@Table(“user”)
Repository 인터페이스
Account findByUsername(String username);
안해도됨repository 패키지 생성
CrudRepository의 메소드에 대한 설명
T 는 Entity 클래스의 Type
등록: <S extends T> S save(S entity);
리스트 조회: Iterable<T> findAll(); (CrudRepository)
List<T> findAll(); (JpaRepository)
PK로 조회: Optional<T> findById(ID id);
삭제: void delete(T entity);
void deleteById(ID id);
PK존재여부: boolean existsById(ID id);
select * from accounts where id = 3L
select * from accounts where username = ‘spring’
select * from users where email=’aa@a.com’ 1개의 entity
select * from users where name=’spring’ 여러개의 entity
Iterable
https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html
Optional
https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Optional.html
JPA Repository query(finder) method 메소드 명 규칙
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods
Query Method 규칙
JPQL
select u from User u where u.emailAddress = ?1 and u.lastname = ?2
- Select u : *
- From User : User는 Table이 아닌 Entity
AccountRepository.java 수정
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);
}
REST 사용 이유
REST 제약 조건
Spring Boot Web MVC
LocalDateTime
: 현재 시각 설정 가능 account.java Entity 생성
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.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByName(String name);
}
WebMVC 관련 어노테이션 설명
@RestController = @Controller + @ResponseBody
Response (응답) : @ResponseBody
: Java Object -> JSON 변환 처리는 Jackson이 담당 함
: 변환된 데이터를 응답(response) body에 담아 주는 역할
Request (요청) : @RequestBody
: JSON -> Java Object 변환 처리는 Jackson이 담당함
: 변환된 데이터를 요청(request)에 담아서 컨트롤러의 메서드의 아규먼트로 매핑 해주는 역할
실질적인 변환 처리가 누가 할까요? Jackson
Java => Json : Serialization (직렬화)
Json => Java : DeSerialization (역직렬화)
ResponseEntity
: Body + Http Status Code + Header 한번에 담아서 응답을 주는 객체
https://www.baeldung.com/spring-response-entity
@PathVariable
Put과 Patch 차이점
: put - 모든 항목 전체수정
: patch - 부분 항목 수정
등록
POST
http://localhost:8080/users
header
content-type:application/json
body
{
"name":"스프링",
"email":"spring@a.com"
}
Id로 조회 GET
http://localhost:8080/users/{id}
email로 조회 GET
http://localhost:8080/users/email/spring@a.com/
목록조회 GET
http://localhost:8080/users
수정 PATCH
header
content-type:application/json
body
http://localhost:8080/users/{email}/
{
"name":"Spring"
}
수정 PUT
header
content-type:application/json
body
http://localhost:8080/users/1
{
"name":"Spring",
"email":"spring@a.com"
}
삭제 DELETE
http://localhost:8080/users/1
ResponseEntity 클래스 https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/ResponseEntity.html
=> Body + Status Code + Header
http://localhost:8080/users/xml
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>${jackson-bom.version}</version>
</dependency>
Thymeleaf Docs
https://www.thymeleaf.org/doc/tutorials/3.1/usingthymeleaf.html
Thymeleaf-Spring Docs
https://www.thymeleaf.org/doc/tutorials/3.1/thymeleafspring.html
등록
조회
개별 조회
exception (에러 처리)
advice
NoSuchElementException
발생해서 isPresent() 필요)UserBasicRestController.java 수정
package com.basic.myspringboot.controller;
import com.basic.myspringboot.entity.User;
import com.basic.myspringboot.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
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(Optional<User>.isPresent()) {
// User user = optionalUser.get();
// return user;
// }
// orElseThrow(Supplier) Supplier의 추상메서드가 T get()
User user = optionalUser.orElseThrow();
return user;
}
}
상세 정보 조회
없을 경우 에러 처리
UserRepository 수정
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);
}
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));
}
}
삭제
삭제하기 전에 존재하는지 확인 필요
조회 먼저 수행
UserBasicRestController.java 수정
ctrl + shift + f10 : Java run
ctrl + shift + t : 테스트 케이스 추가
ctrl + alt + o(오우) : auto import
ctrl + alt + v : return type 자동 생성 ( eclipse 는 alt + shift + l(엘) )
ctrl + alt + shift + l(대문자엘) : code format
alt + insert : generate constructor, getter, setter
alt + enter : import , create new class(interface, enum)
alt + shift + insert : column selection mode ( eclipse alt + shift + a )
ctrl + alt + m : extract method
ctrl + alt + n : inline method
ctrl + o (오우) : override method
ctrl + i (아이) : implements method
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.http.ResponseEntity;
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));
}
@GetMapping("/name/{name}")
public List<User> getUserByName(@PathVariable String name) {
return userRepository.findByName(name);
}
@DeleteMapping("/{id}")
public ResponseEntity<?> deleteUser(@PathVariable Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new BusinessException("User Not Found", HttpStatus.NOT_FOUND));
userRepository.delete(user);
// user를 삭제해도 가능
//return ResponseEntity.ok(user);
return ResponseEntity.ok(id + " User가 삭제 되었습니다");
}
@PutMapping("/{userId}")
public User updateUser(@PathVariable("userId") Long id, @RequestBody User userDetail) {
User user = userRepository.findById(id)
.orElseThrow(() -> new BusinessException("User Not Found", HttpStatus.NOT_FOUND));
// 수정하려는 값을 저장
user.setName(userDetail.getName());
user.setEmail(userDetail.getEmail());
// set 한 값을 DB에 반영
User updatedUser = userRepository.save(user);
return updatedUser;
}
}
package com.basic.myspringboot.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@NoArgsConstructor // 기본 생성자 생성
@AllArgsConstructor
@Getter @Setter
public class UserReqDTO {
private String name;
private String email;
}
package com.basic.myspringboot.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.time.LocalDateTime;
@NoArgsConstructor // 기본 생성자 생성
@AllArgsConstructor
@Getter @Setter
public class UserResDTO {
private Long id;
private String name;
private String email;
private LocalDateTime createdAt = LocalDateTime.now();
}
// 하단에 추가
<!-- https://mvnrepository.com/artifact/org.modelmapper/modelmapper -->
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>3.1.1</version>
</dependency>
// 하단처럼 객체 생성하지 않고 @Bean 사용
ModelMapper modelMapper = new ModelMapper();
OrderDTO orderDTO = modelMapper.map(order, OrderDTO.class);
package com.basic.myspringboot;
import org.modelmapper.ModelMapper;
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);
}
@Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
return modelMapper;
}
}
@Transactional(readOnly = true)
public List<UserResDto> getUsers() {
List<User> userList = userRepository.findAll(); //DB에서 읽어옴
List<UserResDto> userResDtoList = userList.stream() //Stream<User> //Stream에 담음
.map(user -> modelMapper.map(user, UserResDto.class))//Stream<UserResDto> // Entity를 Dto로 담음
.collect(toList());//List<UserResDto> // Stream을 List로 변경
return userResDtoList;
}
service 패키지 생성
UserService.java 생성
@Autowired 쓰지 않고, 변수 생성한 후 생성자 생성해서 injection 가능
새로운 변수를 추가할 경우 생성자에 추가해줘야함
-> lobok의 @RequiredArgsConstructor 사용
트랜잭션의 4가지 특징 (ACID)
트랜잭션
Transactional Propagation(트랜잭션 전파속성)
Transaction Propagation
https://helloino.tistory.com/127
Required
None ===> T1
T1 ===> T1 (기존 트랜잭션이 유지됨)
Requires_new
None ===> T2
T1 ===> T2 (항상 새로운 트랜잭션이 시작됨)
Mandatory
None ===> Exception이 발생함
T1 ===> T1 (기존 트랜잭션이 유지됨)
-----------------------------------------------
package com.basic.myspringboot.service;
import com.basic.myspringboot.dto.UserReqDTO;
import com.basic.myspringboot.dto.UserResDTO;
import com.basic.myspringboot.entity.User;
import com.basic.myspringboot.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor // lombok에 final로 선언된 변수들의 생성자 만들어줌
@Transactional
public class UserService {
// 해당 방식은 Setter Injection
// @Autowired
// private UserRepository userRepository;
// Constructor Injection 방식
// @Autowired 사용하지 않고 injection
// final은 선언과 동시에 초기화 필요 (생성자를 통해 초기화해도 됨)
private final UserRepository userRepository;
private final ModelMapper modelMapper;
// injection 받는 객체 늘어날 경우 개발자가 계속 추가 필요
// -> lobok의 @RequiredArgsConstructor 사용
// public UserService(UserRepository userRepository, ModelMapper modelMapper) {
// this.userRepository = userRepository;
// this.modelMapper = modelMapper;
// }
public UserResDTO saveUser(UserReqDTO userReqDto) {
//reqDto => entity 매핑
User user = modelMapper.map(userReqDto, User.class);
// DB에 저장
User savedUser = userRepository.save(user);
//entity => resDto 매핑
return modelMapper.map(savedUser, UserResDTO.class);
}
}
package com.basic.myspringboot.controller;
import com.basic.myspringboot.dto.UserReqDTO;
import com.basic.myspringboot.dto.UserResDTO;
import com.basic.myspringboot.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/users") // url 똑같으면 겹치니까 다르게 작성
@RequiredArgsConstructor
public class UserRestController {
private final UserService userService;
// 등록
@PostMapping
public UserResDTO saveUser(@RequestBody UserReqDTO userReqDTO) {
return userService.saveUser(userReqDTO); // service에서 모두 처리되어 controller에서는 호출만 수행
}
}
등록 test
조회
readOnly
를 성능에 도움을 주기 위해 사용UserService.java
package com.basic.myspringboot.service;
import com.basic.myspringboot.dto.UserReqDTO;
import com.basic.myspringboot.dto.UserResDTO;
import com.basic.myspringboot.entity.User;
import com.basic.myspringboot.exception.BusinessException;
import com.basic.myspringboot.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor // lombok에 final로 선언된 변수들의 생성자 만들어줌
@Transactional
public class UserService {
// 해당 방식은 Setter Injection
// @Autowired
// private UserRepository userRepository;
// Constructor Injection 방식
// @Autowired 사용하지 않고 injection
// final은 선언과 동시에 초기화 필요 (생성자를 통해 초기화해도 됨)
private final UserRepository userRepository;
private final ModelMapper modelMapper;
// injection 받는 객체 늘어날 경우 개발자가 계속 추가 필요
// -> lobok의 @RequiredArgsConstructor 사용
// public UserService(UserRepository userRepository, ModelMapper modelMapper) {
// this.userRepository = userRepository;
// this.modelMapper = modelMapper;
// }
// 등록
public UserResDTO saveUser(UserReqDTO userReqDto) {
//reqDto => entity 매핑
User user = modelMapper.map(userReqDto, User.class);
// DB에 저장
User savedUser = userRepository.save(user);
//entity => resDto 매핑
return modelMapper.map(savedUser, UserResDTO.class);
}
// 조회
public UserResDTO getUserById(Long id) {
User userEntity = userRepository.findById(id) // return type : Optional<User>
.orElseThrow(() -> new BusinessException(id + "User Not Found", HttpStatus.NOT_FOUND));
// Entity -> ResDTO로 변환
UserResDTO userResDTO = modelMapper.map(userEntity, UserResDTO.class);
return userResDTO;
}
}
package com.basic.myspringboot.controller;
import com.basic.myspringboot.dto.UserReqDTO;
import com.basic.myspringboot.dto.UserResDTO;
import com.basic.myspringboot.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/users") // url 똑같으면 겹치니까 다르게 작성
@RequiredArgsConstructor
public class UserRestController {
private final UserService userService;
// 등록
@PostMapping
public UserResDTO saveUser(@RequestBody UserReqDTO userReqDTO) {
return userService.saveUser(userReqDTO); // service에서 모두 처리되어 controller에서는 호출만 수행
}
// 조회
@GetMapping("/{id}")
public UserResDTO getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
}
조회 test
java.util.List 안에 stream Collection 있음
collection을 stream으로 변환 필요
@Transactional(readOnly = true)
public List<UserResDto> getUsers() {
List<User> userList = userRepository.findAll();
List<UserResDto> userResDtoList = userList.stream() //Stream<User>
.map(user -> modelMapper.map(user, UserResDto.class))//Stream<UserResDto> 변환
.collect(toList());//List<UserResDto> 변환
return userResDtoList;
}
목록 조회
package com.basic.myspringboot.service;
import com.basic.myspringboot.dto.UserReqDTO;
import com.basic.myspringboot.dto.UserResDTO;
import com.basic.myspringboot.entity.User;
import com.basic.myspringboot.exception.BusinessException;
import com.basic.myspringboot.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toList; // Collectors.toList()에서 Collectors 생략 가능
@Service
@RequiredArgsConstructor // lombok에 final로 선언된 변수들의 생성자 만들어줌
@Transactional
public class UserService {
// 해당 방식은 Setter Injection
// @Autowired
// private UserRepository userRepository;
// Constructor Injection 방식
// @Autowired 사용하지 않고 injection
// final은 선언과 동시에 초기화 필요 (생성자를 통해 초기화해도 됨)
private final UserRepository userRepository;
private final ModelMapper modelMapper;
// injection 받는 객체 늘어날 경우 개발자가 계속 추가 필요
// -> lobok의 @RequiredArgsConstructor 사용
// public UserService(UserRepository userRepository, ModelMapper modelMapper) {
// this.userRepository = userRepository;
// this.modelMapper = modelMapper;
// }
// 등록
public UserResDTO saveUser(UserReqDTO userReqDto) {
//reqDto => entity 매핑
User user = modelMapper.map(userReqDto, User.class);
// DB에 저장
User savedUser = userRepository.save(user);
//entity => resDto 매핑
return modelMapper.map(savedUser, UserResDTO.class);
}
// 조회
@Transactional(readOnly = true) // 조회 메서드인 경우에 readOnly=true를 설정하면 성능 향상에 도움
public UserResDTO getUserById(Long id) {
User userEntity = userRepository.findById(id) // return type : Optional<User>
.orElseThrow(() -> new BusinessException(id + "User Not Found", HttpStatus.NOT_FOUND));
// Entity -> ResDTO로 변환
UserResDTO userResDTO = modelMapper.map(userEntity, UserResDTO.class);
return userResDTO;
}
// 전체 목록 조회
@Transactional(readOnly = true)
public List<UserResDTO> getUsers() {
List<User> userList = userRepository.findAll(); // List<User>
// List<User> -> List<UserResDTO>
List<UserResDTO> userResDTOList = userList.stream() // List<User> -> Stream<User>
// map(Function) Function의 추상메서드 : R apply (T t)
.map(user -> modelMapper.map(user, UserResDTO.class)) // Stream<User> -> Stream<UserResDTO>
.collect(toList());// Stream<UserResDTO> -> List<UserResDTO>
return userResDTOList;
}
}
package com.basic.myspringboot.controller;
import com.basic.myspringboot.dto.UserReqDTO;
import com.basic.myspringboot.dto.UserResDTO;
import com.basic.myspringboot.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/users") // url 똑같으면 겹치니까 다르게 작성
@RequiredArgsConstructor
public class UserRestController {
private final UserService userService;
// 등록
@PostMapping
public UserResDTO saveUser(@RequestBody UserReqDTO userReqDTO) {
return userService.saveUser(userReqDTO); // service에서 모두 처리되어 controller에서는 호출만 수행
}
// 조회
@GetMapping("/{id}")
public UserResDTO getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
// 전체 목록 조회
@GetMapping
public List<UserResDTO> getUsers() {
return userService.getUsers();
}
}
전체 목록 조회 test
람다식 : 추상메서드 인터페이스를 오버라이딩 할 때 사용 가능?
수정
public UserResDto updateUser(String email, UserReqDto userReqDto) {
User existUser = userRepository.findByEmail(email)
.orElseThrow(() ->
new BusinessException(email + " User Not Found", HttpStatus.NOT_FOUND));
//setter method 호출
existUser.setName(userReqDto.getName());
return modelMapper.map(existUser, UserResDto.class);
}
package com.basic.myspringboot.service;
import com.basic.myspringboot.dto.UserReqDTO;
import com.basic.myspringboot.dto.UserResDTO;
import com.basic.myspringboot.entity.User;
import com.basic.myspringboot.exception.BusinessException;
import com.basic.myspringboot.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toList; // Collectors.toList()에서 Collectors 생략 가능
@Service
@RequiredArgsConstructor // lombok에 final로 선언된 변수들의 생성자 만들어줌
@Transactional
public class UserService {
// 해당 방식은 Setter Injection
// @Autowired
// private UserRepository userRepository;
// Constructor Injection 방식
// @Autowired 사용하지 않고 injection
// final은 선언과 동시에 초기화 필요 (생성자를 통해 초기화해도 됨)
private final UserRepository userRepository;
private final ModelMapper modelMapper;
// injection 받는 객체 늘어날 경우 개발자가 계속 추가 필요
// -> lobok의 @RequiredArgsConstructor 사용
// public UserService(UserRepository userRepository, ModelMapper modelMapper) {
// this.userRepository = userRepository;
// this.modelMapper = modelMapper;
// }
// 등록
public UserResDTO saveUser(UserReqDTO userReqDto) {
//reqDto => entity 매핑
User user = modelMapper.map(userReqDto, User.class);
// DB에 저장
User savedUser = userRepository.save(user);
//entity => resDto 매핑
return modelMapper.map(savedUser, UserResDTO.class);
}
// 조회
@Transactional(readOnly = true) // 조회 메서드인 경우에 readOnly=true를 설정하면 성능 향상에 도움
public UserResDTO getUserById(Long id) {
User userEntity = userRepository.findById(id) // return type : Optional<User>
.orElseThrow(() -> new BusinessException(id + "User Not Found", HttpStatus.NOT_FOUND));
// Entity -> ResDTO로 변환
UserResDTO userResDTO = modelMapper.map(userEntity, UserResDTO.class);
return userResDTO;
}
// 전체 목록 조회
@Transactional(readOnly = true)
public List<UserResDTO> getUsers() {
List<User> userList = userRepository.findAll(); // List<User>
// List<User> -> List<UserResDTO>
List<UserResDTO> userResDTOList = userList.stream() // List<User> -> Stream<User>
// map(Function) Function의 추상메서드 : R apply (T t)
.map(user -> modelMapper.map(user, UserResDTO.class)) // Stream<User> -> Stream<UserResDTO>
.collect(toList());// Stream<UserResDTO> -> List<UserResDTO>
return userResDTOList;
}
// 수정
public UserResDTO updateUser(String email, UserReqDTO userReqDto) {
User existUser = userRepository.findByEmail(email)
.orElseThrow(() ->
new BusinessException(email + " User Not Found", HttpStatus.NOT_FOUND));
// Dirty Checking 변경 감지를 해서 setter method만 호출해도 update query가 실행됨
existUser.setName(userReqDto.getName());
return modelMapper.map(existUser, UserResDTO.class); // User -> UserResDTO
}
}
package com.basic.myspringboot.controller;
import com.basic.myspringboot.dto.UserReqDTO;
import com.basic.myspringboot.dto.UserResDTO;
import com.basic.myspringboot.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/users") // url 똑같으면 겹치니까 다르게 작성
@RequiredArgsConstructor
public class UserRestController {
private final UserService userService;
// 등록
@PostMapping
public UserResDTO saveUser(@RequestBody UserReqDTO userReqDTO) {
return userService.saveUser(userReqDTO); // service에서 모두 처리되어 controller에서는 호출만 수행
}
// 조회
@GetMapping("/{id}")
public UserResDTO getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
// 전체 목록 조회
@GetMapping
public List<UserResDTO> getUsers() {
return userService.getUsers();
}
// 수정
@PatchMapping("/{email}")
public UserResDTO updateUser(@PathVariable String email, @RequestBody UserReqDTO userReqDTO) {
return userService.updateUser(email, userReqDTO);
}
}
package com.basic.myspringboot.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.DynamicUpdate;
import java.time.LocalDateTime;
@Entity
@Table(name = "users")
@Getter @Setter
@DynamicUpdate
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();
}
변경 감지 (Dirty Checking)
@Transactional
삭제
package com.basic.myspringboot.service;
import com.basic.myspringboot.dto.UserReqDTO;
import com.basic.myspringboot.dto.UserResDTO;
import com.basic.myspringboot.entity.User;
import com.basic.myspringboot.exception.BusinessException;
import com.basic.myspringboot.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toList; // Collectors.toList()에서 Collectors 생략 가능
@Service
@RequiredArgsConstructor // lombok에 final로 선언된 변수들의 생성자 만들어줌
@Transactional
public class UserService {
// 해당 방식은 Setter Injection
// @Autowired
// private UserRepository userRepository;
// Constructor Injection 방식
// @Autowired 사용하지 않고 injection
// final은 선언과 동시에 초기화 필요 (생성자를 통해 초기화해도 됨)
private final UserRepository userRepository;
private final ModelMapper modelMapper;
// injection 받는 객체 늘어날 경우 개발자가 계속 추가 필요
// -> lobok의 @RequiredArgsConstructor 사용
// public UserService(UserRepository userRepository, ModelMapper modelMapper) {
// this.userRepository = userRepository;
// this.modelMapper = modelMapper;
// }
// 등록
public UserResDTO saveUser(UserReqDTO userReqDto) {
//reqDto => entity 매핑
User user = modelMapper.map(userReqDto, User.class);
// DB에 저장
User savedUser = userRepository.save(user);
//entity => resDto 매핑
return modelMapper.map(savedUser, UserResDTO.class);
}
// 조회
@Transactional(readOnly = true) // 조회 메서드인 경우에 readOnly=true를 설정하면 성능 향상에 도움
public UserResDTO getUserById(Long id) {
User userEntity = userRepository.findById(id) // return type : Optional<User>
.orElseThrow(() -> new BusinessException(id + "User Not Found", HttpStatus.NOT_FOUND));
// Entity -> ResDTO로 변환
UserResDTO userResDTO = modelMapper.map(userEntity, UserResDTO.class);
return userResDTO;
}
// 전체 목록 조회
@Transactional(readOnly = true)
public List<UserResDTO> getUsers() {
List<User> userList = userRepository.findAll(); // List<User>
// List<User> -> List<UserResDTO>
List<UserResDTO> userResDTOList = userList.stream() // List<User> -> Stream<User>
// map(Function) Function의 추상메서드 : R apply (T t)
.map(user -> modelMapper.map(user, UserResDTO.class)) // Stream<User> -> Stream<UserResDTO>
.collect(toList());// Stream<UserResDTO> -> List<UserResDTO>
return userResDTOList;
}
// 수정
public UserResDTO updateUser(String email, UserReqDTO userReqDto) {
User existUser = userRepository.findByEmail(email)
.orElseThrow(() ->
new BusinessException(email + " User Not Found", HttpStatus.NOT_FOUND));
// Dirty Checking 변경 감지를 해서 setter method만 호출해도 update query가 실행됨
existUser.setName(userReqDto.getName());
return modelMapper.map(existUser, UserResDTO.class); // User -> UserResDTO
}
// 삭제
public void deleteUser(Long id) {
User user = userRepository.findById(id) //Optional<User>
.orElseThrow(() ->
new BusinessException(id + " User Not Found", HttpStatus.NOT_FOUND));
userRepository.delete(user);
}
}
package com.basic.myspringboot.controller;
import com.basic.myspringboot.dto.UserReqDTO;
import com.basic.myspringboot.dto.UserResDTO;
import com.basic.myspringboot.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/users") // url 똑같으면 겹치니까 다르게 작성
@RequiredArgsConstructor
public class UserRestController {
private final UserService userService;
// 등록
@PostMapping
public UserResDTO saveUser(@RequestBody UserReqDTO userReqDTO) {
return userService.saveUser(userReqDTO); // service에서 모두 처리되어 controller에서는 호출만 수행
}
// 조회
@GetMapping("/{id}")
public UserResDTO getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
// 전체 목록 조회
@GetMapping
public List<UserResDTO> getUsers() {
return userService.getUsers();
}
// 수정
@PatchMapping("/{email}")
public UserResDTO updateUser(@PathVariable String email, @RequestBody UserReqDTO userReqDTO) {
return userService.updateUser(email, userReqDTO);
}
// 삭제
@DeleteMapping("/{id}")
public ResponseEntity<?> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return ResponseEntity.ok(id + " User가 삭제처리 되었습니다.");
}
}
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>market-wp.com</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">
<style>
#oopss {
background: linear-gradient(-45deg, #fff300, #efe400);
position: fixed;
left: 0px;
top: 0;
width: 100%;
height: 100%;
line-height: 1.5em;
z-index: 9999;
}
#oopss #error-text {
font-size: 40px;
display: flex;
flex-direction: column;
align-items: center;
font-family: 'Shabnam', Tahoma, sans-serif;
color: #000;
direction: rtl;
}
#oopss #error-text img {
margin: 85px auto 20px;
height: 342px;
}
#oopss #error-text span {
position: relative;
font-size: 3.3em;
font-weight: 900;
margin-bottom: 50px;
}
#oopss #error-text p.p-a {
font-size: 19px;
margin: 30px 0 15px 0;
}
#oopss #error-text p.p-b {
font-size: 15px;
}
#oopss #error-text .back {
background: #fff;
color: #000;
font-size: 30px;
text-decoration: none;
margin: 2em auto 0;
padding: .7em 2em;
border-radius: 500px;
box-shadow: 0 20px 70px 4px rgba(0, 0, 0, 0.1), inset 7px 33px 0 0px #fff300;
font-weight: 900;
transition: all 300ms ease;
}
#oopss #error-text .back:hover {
-webkit-transform: translateY(-13px);
transform: translateY(-13px);
box-shadow: 0 35px 90px 4px rgba(0, 0, 0, 0.3), inset 0px 0 0 3px #000;
}
@font-face {
font-family: Shabnam;
src: url("https://cdn.rawgit.com/ahmedhosna95/upload/ba6564f8/fonts/Shabnam/Shabnam-Bold.eot");
src: url("https://cdn.rawgit.com/ahmedhosna95/upload/ba6564f8/fonts/Shabnam/Shabnam-Bold.eot?#iefix") format("embedded-opentype"), url("https://cdn.rawgit.com/ahmedhosna95/upload/ba6564f8/fonts/Shabnam/Shabnam-Bold.woff") format("woff"), url("https://cdn.rawgit.com/ahmedhosna95/upload/ba6564f8/fonts/Shabnam/Shabnam-Bold.woff2") format("woff2"), url("https://cdn.rawgit.com/ahmedhosna95/upload/ba6564f8/fonts/Shabnam/Shabnam-Bold.ttf") format("truetype");
font-weight: bold;
}
@font-face {
font-family: Shabnam;
src: url("https://cdn.rawgit.com/ahmedhosna95/upload/ba6564f8/fonts/Shabnam/Shabnam.eot");
src: url("https://cdn.rawgit.com/ahmedhosna95/upload/ba6564f8/fonts/Shabnam/Shabnam.eot?#iefix") format("embedded-opentype"), url("https://cdn.rawgit.com/ahmedhosna95/upload/ba6564f8/fonts/Shabnam/Shabnam.woff") format("woff"), url("https://cdn.rawgit.com/ahmedhosna95/upload/ba6564f8/fonts/Shabnam/Shabnam.woff2") format("woff2"), url("https://cdn.rawgit.com/ahmedhosna95/upload/ba6564f8/fonts/Shabnam/Shabnam.ttf") format("truetype");
font-weight: normal;
}
</style>
</head>
<body>
<div id='oopss'>
<div id='error-text'>
<img src="https://cdn.rawgit.com/ahmedhosna95/upload/1731955f/sad404.svg" alt="404">
<span>404 PAGE</span>
<p class="p-a">
. The page you were looking for could not be found</p>
<p class="p-b">
... Back to previous page
</p>
<a href='#' class="back">... Back to previous page</a>
</div>
</div>
</body>
</html>
("/m/**") : 웹 상의 경로
target 폴더 -> classes 하단에 mobile 생성됨
WebConfig.java 생성
package com.basic.myspringboot.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/mobile/**")
//반드시 mobile 다음에 / 을 주어야 한다.
.addResourceLocations("classpath:/mobile/")
.setCachePeriod(20);//20초
}
}
gradle
yaml 문법
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
다국어 언어 지원
{...}
사용Vriable
${ }
사용Selection Variable
*{ }
사용Link URL
@{ }
사용Value 연결 방식
UserController.java 생성
package com.basic.myspringboot.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/userspage")
public class UserController {
@GetMapping("/first")
public String leaf(Model model) {
model.addAttribute("name","스프링부트");
return "leaf";
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Spring Boot Thymeleaf</title>
</head>
<body>
<h1 th:text="${name}">네임</h1>
<h1>Hello, <span th:text="${name}">스팬태그</span></h1>
<h1>Hello, [[${name}]]</h1>
</body>
</html>
실행
http://localhost:8080/first
Thymeleaf 수동으로 설정하기 (기존 자동 설정)
redirect:
사용 package com.basic.myspringboot.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class IndexController {
@GetMapping("/")
public String index() {
return "redirect:/userspage/first"; // leaf.html 요청되어 출력
}
}
package com.basic.myspringboot.controller;
import com.basic.myspringboot.dto.UserResDTO;
import com.basic.myspringboot.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
@Controller
@RequestMapping("/userspage")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/first")
public String leaf(Model model) {
model.addAttribute("name","스프링부트");
return "leaf";
}
@GetMapping("/index")
public ModelAndView index() {
List<UserResDTO> userResDTOList = userService.getUsers();
return new ModelAndView("index", "users", userResDTOList); // viewName, modelName, modelObject
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<meta charset="UTF-8">
<body>
<table>
<tr>
<th>Name</th>
<th>Email</th>
</tr>
<tr th:each="user : ${users}">
<td th:text="${user.name}"></td>
<td th:text="${user.email}"></td>
</tr>
</table>
</body>
</html>
package com.basic.myspringboot.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class IndexController {
@GetMapping("/")
public String index() {
return "redirect:/userspage/index";
// return "redirect:/userspage/first"; // leaf.html 요청되어 출력
}
}
thymeleaf each index
index.html 수정
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<meta charset="UTF-8">
<body>
<table>
<tr>
<th>Seq</th>
<th>Name</th>
<th>Email</th>
<th>EntryDate</th>
</tr>
<tr th:each="user, userStat : ${users}">
<td th:text="${userStat.count}"></td> <!-- id 연속적인 수로 출력 -->
<td th:text="${user.name}"></td>
<td th:text="${user.email}"></td>
<!-- <td th:text="${user.createdAt}"></td>-->
<td th:text="${#temporals.format(user.createdAt, 'yyyy-MM-dd hh:mm')}"></td>
</tr>
</table>
</body>
</html>
Validation(입력항목 검증) 표준 스펙
Java Bean Validation
jakarta.validation.*
Jakarta API Docs
https://jakarta.ee/specifications/platform/8/apidocs/
Java Bean Validation의 구현체
Hibernate Validator
https://github.com/hibernate/hibernate-validator
https://hibernate.org/validator/
Hibernate Validator API
https://docs.jboss.org/hibernate/validator/6.2/api/
Spring Framework5 API Docs
https://docs.spring.io/spring-framework/docs/5.3.24/javadoc-api/
jakarta validation의 어노테이션
@NotEmpty vs @NotBlank
@NotEmpty : white space(“ “) 는 허용
@NotBlank : 문자열의 공백을 제거(trim)하고 체크 하므로
white space(“ “) 허용하지 않음 trim() + notEmpty()
UserDto ${userDto}
package com.basic.myspringboot.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@NoArgsConstructor // 기본 생성자 생성
@AllArgsConstructor
@Getter @Setter
public class UserReqDTO {
@NotEmpty(message = "Name은 필수 입력 항목입니다") // " " 허용
private String name;
@NotBlank(message = "Email은 필수 입력 항목입니다") // " " 허용하지 않음
private String email;
}
왜 GetMapping에서 User user(UserReqDTO) 인자로 받아야하는지
@Valid는 입력 조건에 맞게 체크
org.springframework.validation Errors
getFieldError
: 객체에 담아서 전송 getFieldErrors
: 여러 에러를 객체에 담음getGlobalError
: 개별 항목이 아니라 다중 필드의 에러를 체크해야할 경우 사용 getObjectName
: 담고 있는 객체 (UserReqDTO)의 이름 hasErrors()
: 에러 객체에 정보가 저장했는지(에러가 발생했는지) 확인#fields : 에러에 대한 객체를 thymeleaf 쪽에서 만들어줌 (=bindresult)
templates/index.html 수정 (타임리프 하단에 작성)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<meta charset="UTF-8">
<body>
<table>
<tr>
<th>Seq</th>
<th>Name</th>
<th>Email</th>
<th>EntryDate</th>
</tr>
<tr th:each="user, userStat : ${users}">
<td th:text="${userStat.count}"></td> <!-- id 연속적인 수로 출력 -->
<td th:text="${user.name}"></td>
<td th:text="${user.email}"></td>
<!-- <td th:text="${user.createdAt}"></td>-->
<td th:text="${#temporals.format(user.createdAt, 'yyyy-MM-dd hh:mm')}"></td>
</tr>
</table>
<p>
<a herf="/userspage/signup">Insert</a> <!-- 추가 -->
</p>
</body>
</html>
package com.basic.myspringboot.controller;
import com.basic.myspringboot.dto.UserReqDTO;
import com.basic.myspringboot.dto.UserResDTO;
import com.basic.myspringboot.service.UserService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
@Controller
@RequestMapping("/userspage")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/first")
public String leaf(Model model) {
model.addAttribute("name","스프링부트");
return "leaf";
}
@GetMapping("/index")
public ModelAndView index() {
List<UserResDTO> userResDTOList = userService.getUsers();
return new ModelAndView("index", "users", userResDTOList); // viewName, modelName, modelObject
}
// 등록 페이지를 호출 해주는 메서드
@GetMapping("/signup")
public String showSignUpForm(UserReqDTO user) {
return "add-user";
}
// 입력 항목 검증을 한 후 등록 처리 메서드
@PostMapping("/adduser")
public String addUser(@Valid UserReqDTO user, BindingResult result, Model model) {
// 입력 항목 검증 오류가 발생했는지 확인
if (result.hasErrors()) {
return "add-user";
}
// 등록 요청
userService.saveUser(user);
model.addAttribute("users", userService.getUsers());
return "index";
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<meta charset="UTF-8">
<body>
<!-- th:action="@{/adduser}"를 th:action="@{/userspage/adduser}"로 변경 -->
<!-- th:object="${user}" 의 user는 user객체의 클래스 이름 (소문자 변환 필요)-->
<form action="#" th:action="@{/userspage/adduser}" th:object="${userReqDTO}" method="post">
<label for="name">Name</label>
<input type="text" th:field="*{name}" id="name">
<span th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></span>
<br/>
<label for="email">Email</label>
<input type="text" th:field="*{email}" id="email">
<span th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></span>
<br/>
<input type="submit" value="Add User">
</form>
</body>
</html>
package com.basic.myspringboot.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@NoArgsConstructor // 기본 생성자 생성
@AllArgsConstructor
@Getter @Setter
public class UserReqDTO {
@NotEmpty(message = "Name은 필수 입력 항목입니다") // " " 허용
private String name;
@NotBlank(message = "Email은 필수 입력 항목입니다") // " " 허용하지 않음
@Email(message = "Email 형식이 아닙니다")
private String email;
}
lable for="name" 구문으로 인해 id를 지정함
field
한글 인코딩 설정 필요 없음
package com.basic.myspringboot.controller;
import com.basic.myspringboot.dto.UserReqDTO;
import com.basic.myspringboot.dto.UserResDTO;
import com.basic.myspringboot.service.UserService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
@Controller
@RequestMapping("/userspage")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/first")
public String leaf(Model model) {
model.addAttribute("name", "스프링부트");
return "leaf";
}
@GetMapping("/index")
public ModelAndView index() {
List<UserResDTO> userResDTOList = userService.getUsers();
return new ModelAndView("index", "users", userResDTOList); // viewName, modelName, modelObject
}
// 등록 페이지를 호출 해주는 메서드
@GetMapping("/signup")
public String showSignUpForm(UserReqDTO user) {
return "add-user";
}
// 입력 항목 검증을 한 후 등록 처리 메서드
@PostMapping("/adduser")
public String addUser(@Valid UserReqDTO user, BindingResult result, Model model) {
// 입력 항목 검증 오류가 발생했는지 확인
if (result.hasErrors()) {
return "add-user";
}
// 등록 요청
userService.saveUser(user);
// model.addAttribute("users", userService.getUsers());
return "redirect:/userspage/index";
}
// 수정 페이지를 호출해주는 메서드
@GetMapping("/edit/{id}")
public String showUpdateForm(@PathVariable Long id, Model model) {
UserResDTO userResDTO = userService.getUserById(id);
model.addAttribute("user", userResDTO);
return "update-user";
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<meta charset="UTF-8">
<body>
<!-- th:object="${user}" DTO로 변환하면 에러남-->
<form action="#" th:action="@{/update/{id}(id=${user.id})}" th:object="${user}" method="post">
<label for="name">Name</label>
<input type="text" th:field="*{name}" id="name">
<span th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></span> <br />
<label for="email">Email</label>
<input type="text" th:field="*{email}" id="email">
<span th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></span> <br />
<input type="submit" value="Update User">
</form>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<meta charset="UTF-8">
<body>
<table>
<tr>
<th>Seq</th>
<th>Name</th>
<th>Email</th>
<th>EntryDate</th>
<th>Update</th>
</tr>
<tr th:each="user, userStat : ${users}">
<td th:text="${userStat.count}"></td> <!-- id 연속적인 수로 출력 -->
<td th:text="${user.name}"></td>
<td th:text="${user.email}"></td>
<!-- <td th:text="${user.createdAt}"></td>-->
<td th:text="${#temporals.format(user.createdAt, 'yyyy-MM-dd hh:mm')}"></td>
<td><a th:href="@{/userspage/edit/{id}(id=${user.id})}">Update</a></td>
</tr>
</table>
<p><a href="/userspage/signup">Insert</a></p>
</body>
</html>
package com.basic.myspringboot.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@NoArgsConstructor // 기본 생성자 생성
@AllArgsConstructor
@Getter @Setter
public class UserReqForm {
private Long id;
@NotEmpty(message = "Name은 필수 입력 항목입니다") // " " 허용
private String name;
@NotBlank(message = "Email은 필수 입력 항목입니다") // " " 허용하지 않음
@Email(message = "Email 형식이 아닙니다")
private String email;
}
package com.basic.myspringboot.service;
import com.basic.myspringboot.dto.UserReqDTO;
import com.basic.myspringboot.dto.UserReqForm;
import com.basic.myspringboot.dto.UserResDTO;
import com.basic.myspringboot.entity.User;
import com.basic.myspringboot.exception.BusinessException;
import com.basic.myspringboot.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toList; // Collectors.toList()에서 Collectors 생략 가능
@Service
@RequiredArgsConstructor // lombok에 final로 선언된 변수들의 생성자 만들어줌
@Transactional
public class UserService {
// 해당 방식은 Setter Injection
// @Autowired
// private UserRepository userRepository;
// Constructor Injection 방식
// @Autowired 사용하지 않고 injection
// final은 선언과 동시에 초기화 필요 (생성자를 통해 초기화해도 됨)
private final UserRepository userRepository;
private final ModelMapper modelMapper;
// injection 받는 객체 늘어날 경우 개발자가 계속 추가 필요
// -> lobok의 @RequiredArgsConstructor 사용
// public UserService(UserRepository userRepository, ModelMapper modelMapper) {
// this.userRepository = userRepository;
// this.modelMapper = modelMapper;
// }
// 등록
public UserResDTO saveUser(UserReqDTO userReqDto) {
//reqDto => entity 매핑
User user = modelMapper.map(userReqDto, User.class);
// DB에 저장
User savedUser = userRepository.save(user);
//entity => resDto 매핑
return modelMapper.map(savedUser, UserResDTO.class);
}
// 조회
@Transactional(readOnly = true) // 조회 메서드인 경우에 readOnly=true를 설정하면 성능 향상에 도움
public UserResDTO getUserById(Long id) {
User userEntity = userRepository.findById(id) // return type : Optional<User>
.orElseThrow(() -> new BusinessException(id + "User Not Found", HttpStatus.NOT_FOUND));
// Entity -> ResDTO로 변환
UserResDTO userResDTO = modelMapper.map(userEntity, UserResDTO.class);
return userResDTO;
}
// 전체 목록 조회
@Transactional(readOnly = true)
public List<UserResDTO> getUsers() {
List<User> userList = userRepository.findAll(); // List<User>
// List<User> -> List<UserResDTO>
List<UserResDTO> userResDTOList = userList.stream() // List<User> -> Stream<User>
// map(Function) Function의 추상메서드 : R apply (T t)
.map(user -> modelMapper.map(user, UserResDTO.class)) // Stream<User> -> Stream<UserResDTO>
.collect(toList());// Stream<UserResDTO> -> List<UserResDTO>
return userResDTOList;
}
// 수정
public UserResDTO updateUser(String email, UserReqDTO userReqDto) {
User existUser = userRepository.findByEmail(email)
.orElseThrow(() ->
new BusinessException(email + " User Not Found", HttpStatus.NOT_FOUND));
// Dirty Checking 변경 감지를 해서 setter method만 호출해도 update query가 실행됨
existUser.setName(userReqDto.getName());
return modelMapper.map(existUser, UserResDTO.class); // User -> UserResDTO
}
public void updateUserForm(UserReqForm userReqForm) {
User existUser = userRepository.findById(userReqForm.getId())
.orElseThrow(() ->
new BusinessException(userReqForm.getId() + " User Not Found", HttpStatus.NOT_FOUND));
existUser.setName(userReqForm.getName());
// 반환하지 않고 update만 진행
}
// 삭제
public void deleteUser(Long id) {
User user = userRepository.findById(id) //Optional<User>
.orElseThrow(() ->
new BusinessException(id + " User Not Found", HttpStatus.NOT_FOUND));
userRepository.delete(user);
}
}
package com.basic.myspringboot.controller;
import com.basic.myspringboot.dto.UserReqDTO;
import com.basic.myspringboot.dto.UserReqForm;
import com.basic.myspringboot.dto.UserResDTO;
import com.basic.myspringboot.service.UserService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
@Controller
@RequestMapping("/userspage")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/first")
public String leaf(Model model) {
model.addAttribute("name", "스프링부트");
return "leaf";
}
@GetMapping("/index")
public ModelAndView index() {
List<UserResDTO> userResDTOList = userService.getUsers();
return new ModelAndView("index", "users", userResDTOList); // viewName, modelName, modelObject
}
// 등록 페이지를 호출 해주는 메서드
@GetMapping("/signup")
public String showSignUpForm(UserReqDTO user) {
return "add-user";
}
// 입력 항목 검증을 한 후 등록 처리 메서드
@PostMapping("/adduser")
public String addUser(@Valid UserReqDTO user, BindingResult result, Model model) {
// 입력 항목 검증 오류가 발생했는지 확인
if (result.hasErrors()) {
return "add-user";
}
// 등록 요청
userService.saveUser(user);
return "redirect:/userspage/index";
}
// 수정 페이지를 호출해주는 메서드
@GetMapping("/edit/{id}")
public String showUpdateForm(@PathVariable Long id, Model model) {
UserResDTO userResDTO = userService.getUserById(id);
model.addAttribute("user", userResDTO);
return "update-user";
}
@PostMapping("/update/{id}")
public String updateUser(@PathVariable("id") long id, @Valid UserReqForm user, BindingResult result, Model model) {
if (result.hasErrors()) {
user.setId(id);
return "update-user";
}
userService.updateUserForm(user);
return "redirect:/userspage/index";
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<meta charset="UTF-8">
<body>
<!-- th:action="@{/update}를 th:action="@{/userspage/update}"로 수정-->
<!-- th:object="${user}" DTO로 변환하면 에러남-->
<form action="#" th:action="@{/userspage/update/{id}(id=${user.id})}" th:object="${user}" method="post">
<label for="name">Name</label>
<input type="text" th:field="*{name}" id="name">
<span th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></span> <br />
<label for="email">Email</label>
<input type="text" th:field="*{email}" id="email" readonly>
<span th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></span> <br />
<input type="submit" value="Update User">
</form>
</body>
</html>
package com.basic.myspringboot.controller;
import com.basic.myspringboot.dto.UserReqDTO;
import com.basic.myspringboot.dto.UserReqForm;
import com.basic.myspringboot.dto.UserResDTO;
import com.basic.myspringboot.service.UserService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
@Controller
@RequestMapping("/userspage")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/first")
public String leaf(Model model) {
model.addAttribute("name", "스프링부트");
return "leaf";
}
@GetMapping("/index")
public ModelAndView index() {
List<UserResDTO> userResDTOList = userService.getUsers();
return new ModelAndView("index", "users", userResDTOList); // viewName, modelName, modelObject
}
// 등록 페이지를 호출 해주는 메서드
@GetMapping("/signup")
public String showSignUpForm(UserReqDTO user) {
return "add-user";
}
// 입력 항목 검증을 한 후 등록 처리 메서드
@PostMapping("/adduser")
public String addUser(@Valid UserReqDTO user, BindingResult result, Model model) {
// 입력 항목 검증 오류가 발생했는지 확인
if (result.hasErrors()) {
return "add-user";
}
// 등록 요청
userService.saveUser(user);
return "redirect:/userspage/index";
}
// 수정 페이지를 호출해주는 메서드
@GetMapping("/edit/{id}")
public String showUpdateForm(@PathVariable Long id, Model model) {
UserResDTO userResDTO = userService.getUserById(id);
model.addAttribute("user", userResDTO);
return "update-user";
}
@PostMapping("/update/{id}")
public String updateUser(@PathVariable("id") long id, @Valid UserReqForm user, BindingResult result, Model model) {
if (result.hasErrors()) {
System.out.println(">> hasErrors user " + user);
// user.setId(id);
model.addAttribute("user", user);
return "update-user";
// return "redirect:/userspage/edit/{id}(id=${user.id})";
}
userService.updateUserForm(user);
return "redirect:/userspage/index";
}
// 삭제
@GetMapping("/delete/{id}")
public String deleteUser(@PathVariable("id") long id) {
userService.deleteUser(id);
return "redirect:/userspage/index";
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<meta charset="UTF-8">
<body>
<table>
<tr>
<th>Seq</th>
<th>Name</th>
<th>Email</th>
<th>EntryDate</th>
<th>Update</th>
<th>Delete</th>
</tr>
<tr th:each="user, userStat : ${users}">
<td th:text="${userStat.count}"></td> <!-- id 연속적인 수로 출력 -->
<td th:text="${user.name}"></td>
<td th:text="${user.email}"></td>
<!-- <td th:text="${user.createdAt}"></td>-->
<td th:text="${#temporals.format(user.createdAt, 'yyyy-MM-dd hh:mm')}"></td>
<td><a th:href="@{/userspage/edit/{id}(id=${user.id})}">Update</a></td>
<td><a th:href="@{/userspag/delete/{id}(id=${user.id})}">Delete</a></td>
</tr>
</table>
<p><a href="/userspage/signup">Insert</a></p>
</body>
</html>
@CrossOrigin
작성package com.basic.myspringboot.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/mobile/**")
//반드시 mobile 다음에 / 을 주어야 한다.
.addResourceLocations("classpath:/mobile/")
.setCachePeriod(20);//20초
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("*");;
}
}
Actuator
localhost:8080/actuator
http://localhost:8087/actuator/health
공개 옵션 조정
# 서버 포트 설정
server.port=8087
# 스프링 유니코드 작성
#myboot.name=\uc2a4\ud504\ub9c1 (applcation-prod/test.properties에서 설정)
myboot.age=${random.int(1,50)}
myboot.fullName=${myboot.name} Boot
# 현재 활성화 중인 환경 설정
spring.profiles.active=prod
#actuator의 모든 endpoint 공개하기
management.endpoints.web.exposure.include=*
http://localhost:8087/actuator/mappings
springbootadmin
실행localhost:8090
admin client 생성
# 서버 포트 설정
server.port=8087
# 스프링 유니코드 작성
#myboot.name=\uc2a4\ud504\ub9c1 (applcation-prod/test.properties에서 설정)
myboot.age=${random.int(1,50)}
myboot.fullName=${myboot.name} Boot
# 현재 활성화 중인 환경 설정
spring.profiles.active=prod
#actuator의 모든 endpoint 공개하기
management.endpoints.web.exposure.include=*
# admin 페이지를 위해 서버 포트 설정
spring.boot.admin.client.url=http://localhost:8090
Autentication Filter
가 동작함 (servlet filter) UsernamePasswordAuthneticationToken
호출하여 입력한 user/password를 AuthenticationToken
에 담음AuthenticationToken
을 AuthenticationManager
에 전달AutenticationProvider
에 토큰에서 꺼낸 id/password를 꺼내 UserDetailsService
에 전달UserDetailsService
은 사용자가 입력한 정보가 기존에 가진 정보와 동일한지 확인 UserDetailsService
를 implement한 InMemoryUserDetailes
이 동작# 서버 포트 설정
server.port=8087
# 스프링 유니코드 작성
#myboot.name=\uc2a4\ud504\ub9c1 (applcation-prod/test.properties에서 설정)
myboot.age=${random.int(1,50)}
myboot.fullName=${myboot.name} Boot
# 현재 활성화 중인 환경 설정
spring.profiles.active=prod
#actuator의 모든 endpoint 공개하기
management.endpoints.web.exposure.include=*
# admin 페이지를 위해 서버 포트 설정
#spring.boot.admin.client.url=http://localhost:8090
# 인증을 위한 user_name과 user_password 설정
spring.security.user.name=boot
spring.security.user.password=test1234
package com.basic.myspringboot.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
//authentication
public UserDetailsService userDetailsService(PasswordEncoder encoder) {
UserDetails admin = User.withUsername("adminboot")
.password(encoder.encode("pwd1"))
.roles("ADMIN") // Admin 권한 부여
.build();
UserDetails user = User.withUsername("userboot")
.password(encoder.encode("pwd2"))
.roles("USER") // User 권한 부여
.build();
return new InMemoryUserDetailsManager(admin, user);
}
}
package com.basic.myspringboot.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@GetMapping("/welcome")
public String welcome() {
return "Welcome this endpoint is not secure";
}
@Bean
//authentication
public UserDetailsService userDetailsService(PasswordEncoder encoder) {
UserDetails admin = User.withUsername("adminboot")
.password(encoder.encode("pwd1"))
.roles("ADMIN") // Admin 권한 부여
.build();
UserDetails user = User.withUsername("userboot")
.password(encoder.encode("pwd2"))
.roles("USER") // User 권한 부여
.build();
return new InMemoryUserDetailsManager(admin, user);
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> {
auth.requestMatchers("/users/welcome").permitAll() // 해당 경로라면 모든 권한 허용
.requestMatchers("/users/**").authenticated(); // 그 외 인증 필요
})
.formLogin(withDefaults())
.build();
}
}
package com.basic.myspringboot.controller;
import com.basic.myspringboot.dto.UserReqDTO;
import com.basic.myspringboot.dto.UserReqForm;
import com.basic.myspringboot.dto.UserResDTO;
import com.basic.myspringboot.service.UserService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
@Controller
@RequestMapping("/userspage")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/welcome")
public String welcome() {
return "Welcome this endpoint is not secure";
}
@GetMapping("/first")
public String leaf(Model model) {
model.addAttribute("name", "스프링부트");
return "leaf";
}
// 등록 전에 목록 출력
@GetMapping("/index")
public ModelAndView index() {
List<UserResDTO> userResDTOList = userService.getUsers();
return new ModelAndView("index", "users", userResDTOList); // viewName, modelName, modelObject
}
// 등록 페이지를 호출 해주는 메서드
@GetMapping("/signup")
public String showSignUpForm(UserReqDTO user) {
return "add-user";
}
// 입력 항목 검증을 한 후 등록 처리 메서드
@PostMapping("/adduser")
public String addUser(@Valid UserReqDTO user, BindingResult result, Model model) {
// 입력 항목 검증 오류가 발생했는지 확인
if (result.hasErrors()) {
return "add-user";
}
// 등록 요청
userService.saveUser(user);
return "redirect:/userspage/index";
}
// 수정 페이지를 호출해주는 메서드
@GetMapping("/edit/{id}")
public String showUpdateForm(@PathVariable Long id, Model model) {
UserResDTO userResDTO = userService.getUserById(id);
model.addAttribute("user", userResDTO);
return "update-user";
}
@PostMapping("/update/{id}")
public String updateUser(@PathVariable("id") long id, @Valid UserReqForm user, BindingResult result, Model model) {
if (result.hasErrors()) {
System.out.println(">> hasErrors user " + user);
// user.setId(id);
model.addAttribute("user", user);
return "update-user";
// return "redirect:/userspage/edit/{id}(id=${user.id})";
}
userService.updateUserForm(user);
return "redirect:/userspage/index";
}
// 삭제
@GetMapping("/delete/{id}")
public String deleteUser(@PathVariable("id") long id) {
userService.deleteUser(id);
return "redirect:/userspage/index";
}
}
package com.basic.myspringboot.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> {
auth.requestMatchers("/api/users/welcome").permitAll() // 해당 경로라면 모든 권한 허용
.requestMatchers("/api/users/**").authenticated(); // 그 외 인증 필요
})
.formLogin(withDefaults())
.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
//authentication
public UserDetailsService userDetailsService(PasswordEncoder encoder) {
UserDetails admin = User.withUsername("adminboot")
.password(encoder.encode("pwd1"))
.roles("ADMIN") // Admin 권한 부여
.build();
UserDetails user = User.withUsername("userboot")
.password(encoder.encode("pwd2"))
.roles("USER") // User 권한 부여
.build();
return new InMemoryUserDetailsManager(admin, user);
}
}
인증
formlogin
//.formLogin(withDefaults())
.formLogin(login -> login
.loginPage("/login")
.loginProcessingUrl("/login-process")
.usernameParameter("username")
.passwordParameter("password")
.defaultSuccessUrl("/users/index", true)
.permitAll()
)
.logout((logout) -> logout.logoutUrl("/app-logout")
.deleteCookies("JSESSIONID")
.logoutSuccessUrl("/")
)
.build
인증하다 실페 에러코드 : 401
인증한 후 권한이 없는 에러코드 : 403
SecurityConfig.java 수정
package com.basic.myspringboot.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity // 인가
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> {
auth.requestMatchers("/api/users/welcome", "/userspage/**").permitAll()
.requestMatchers("/api/users/**").authenticated();
})
.formLogin(withDefaults())
.build();
}
@Bean
public PasswordEncoder passwordEncoder () {
return new BCryptPasswordEncoder();
}
@Bean
//authentication
public UserDetailsService userDetailsService (PasswordEncoder encoder){
UserDetails admin = User.withUsername("adminboot")
.password(encoder.encode("pwd1"))
.roles("ADMIN")
.build();
UserDetails user = User.withUsername("userboot")
.password(encoder.encode("pwd2"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(admin, user);
}
}
package com.basic.myspringboot.controller;
import com.basic.myspringboot.dto.UserReqDTO;
import com.basic.myspringboot.dto.UserResDTO;
import com.basic.myspringboot.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/users") // url 똑같으면 겹치니까 다르게 작성
@RequiredArgsConstructor
public class UserRestController {
private final UserService userService;
@GetMapping("/welcome")
public String welcome() {
return "Welcome this endpoint is not secure";
} // UserController에서 UserRestController로 이동하니까 됨
// 등록
@PostMapping
public UserResDTO saveUser(@RequestBody UserReqDTO userReqDTO) {
return userService.saveUser(userReqDTO); // service에서 모두 처리되어 controller에서는 호출만 수행
}
// 조회
@GetMapping("/{id}")
@PreAuthorize("hasAuthority('ROLE_USER')")
public UserResDTO getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
// 전체 목록 조회
@GetMapping
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
public List<UserResDTO> getUsers() {
return userService.getUsers();
}
// 수정
@PatchMapping("/{email}")
public UserResDTO updateUser(@PathVariable String email, @RequestBody UserReqDTO userReqDTO) {
return userService.updateUser(email, userReqDTO);
}
// 삭제
@DeleteMapping("/{id}")
public ResponseEntity<?> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return ResponseEntity.ok(id + " User가 삭제처리 되었습니다.");
}
}
UserDetailsService가 username/pwd를 읽어서 DB에 있는지 확인한 후 결과를 User 객체에 저장
userDetails를 implement받은 userInfoUserDetails(User) 클래스에 DB에서 이메일 주소와 패스워드를 꺼내와 저장
Entity 생성 -> Repository 생성 -> DB에 암호화 하여 저장
securtiy 패키지 생성
UserInfo.java 생성
package com.basic.myspringboot.security.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Column(nullable = false)
private String name;
@Column(unique = true, nullable = false)
private String email;
private String password;
private String roles;
}
package com.basic.myspringboot.security.repository;
import com.basic.myspringboot.security.entity.UserInfo;
import org.springframework.data.repository.ListCrudRepository;
import java.util.Optional;
public interface UserInfoRepository extends ListCrudRepository<UserInfo, Integer> {
Optional<UserInfo> findByEmail(String email);
}
package com.basic.myspringboot.security.vo;
import com.basic.myspringboot.security.entity.UserInfo;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
public class UserInfoUserDetails implements UserDetails {
private String email;
private String password;
private List<GrantedAuthority> authorities;
public UserInfoUserDetails(UserInfo userInfo) {
email=userInfo.getEmail(); // email을 인증할 때 사용 목적
password=userInfo.getPassword(); // 암호화된 password
// 권한
// userInfo.getRoles() : table에 저장된 role 정보
// split(",") : , 기준으로 파싱하여 ROLE_ADMIN, ROLE_USER 가져옴
authorities= Arrays.stream(userInfo.getRoles().split(",")) // Stream<String>
// Stream<String> -> Stream<SimpleGrantedAuthority)
// 람다식 .map(SimpleGrantedAuthority::new) = .map(roleName -> new SimpleGrantedAuthority(roleName))
.map(SimpleGrantedAuthority::new) // Stream<SimpleGrantedAuthority)
.collect(Collectors.toList());
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
// providemanger에서 인증 할 때 하단 메서드 호출
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return email;
} // email을 이용하여 인증 처리
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
package com.basic.myspringboot.security.service;
import com.basic.myspringboot.security.entity.UserInfo;
import com.basic.myspringboot.security.repository.UserInfoRepository;
import com.basic.myspringboot.security.vo.UserInfoUserDetails;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class UserInfoUserDetailsService implements UserDetailsService {
@Autowired
private UserInfoRepository repository;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<UserInfo> optionalUserInfo = repository.findByEmail(username); // name -> email로 변경
return optionalUserInfo.map(userInfo -> new UserInfoUserDetails(userInfo))
//userInfo.map(UserInfoUserDetails::new)
.orElseThrow(() -> new UsernameNotFoundException("user not found " + username));
}
public String addUser(UserInfo userInfo) {
userInfo.setPassword(passwordEncoder.encode(userInfo.getPassword()));
UserInfo savedUserInfo = repository.save(userInfo);
return savedUserInfo.getName() + " user added!!";
}
}
package com.basic.myspringboot.security.controller;
import com.basic.myspringboot.security.entity.UserInfo;
import com.basic.myspringboot.security.service.UserInfoUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/userinfos")
public class UserInfoController {
@Autowired
private UserInfoUserDetailsService service;
@PostMapping("/new")
public String addNewUser(@RequestBody UserInfo userInfo) {
return service.addUser(userInfo);
}
}
package com.basic.myspringboot.security.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity // 인가
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> {
auth.requestMatchers("/api/users/welcome",
"/userspage/**",
"/userinfos/new").permitAll() // 경로 추가
.requestMatchers("/api/users/**").authenticated();
})
.formLogin(withDefaults())
.build();
}
@Bean
public PasswordEncoder passwordEncoder () {
return new BCryptPasswordEncoder();
}
@Bean
//authentication
public UserDetailsService userDetailsService (PasswordEncoder encoder){
UserDetails admin = User.withUsername("adminboot")
.password(encoder.encode("pwd1"))
.roles("ADMIN")
.build();
UserDetails user = User.withUsername("userboot")
.password(encoder.encode("pwd2"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(admin, user);
}
}
//// jwt (json web token) 처리 - 로그인 처리
/*
POST
http://localhost:8080/api/userinfos/login
{
"email":"admin@aa.com",
"password":"pwd1"
}
jwt : eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbkBhYS5jb20iLCJpYXQiOjE2OTc2MTAzNTQsImV4cCI6MTY5NzYxMjE1NH0._JIKGkjyItb-rB5MPebopyG5UPitaNFhcfCyCkv8KLg
*/
토큰으로 데이터 조회
토큰 발행 코드
1) UserInfoController.java
claims()
메서드를 이용해서 클레임 가져옴package com.basic.myspringboot.controller;
import com.basic.myspringboot.entity.UserInfo;
import com.basic.myspringboot.jwt.dto.AuthRequest;
import com.basic.myspringboot.jwt.service.JwtService;
import com.basic.myspringboot.repository.UserInfoRepository;
import com.basic.myspringboot.service.UserInfoUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/userinfos")
public class UserInfoController {
// @Autowired
// private UserInfoUserDetailsService service;
//// jwt 처리 - 로그인 처리하기 위해서 Injection 받음
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtService jwtService;
@Autowired
private UserInfoRepository repository;
@Autowired
private PasswordEncoder passwordEncoder;
// @PostMapping("/new")
// public String addNewUser(@RequestBody UserInfo userInfo) {
// return service.addUser(userInfo);
// }
@PostMapping("/new")
public String addNewUser(@RequestBody UserInfo userInfo){
userInfo.setPassword(passwordEncoder.encode(userInfo.getPassword()));
UserInfo savedUserInfo = repository.save(userInfo);
return savedUserInfo.getName() + " user added!!";
}
//// jwt (json web token) 처리 - 로그인 처리
/*
POST
http://localhost:8080/api/userinfos/login
{
"email":"admin@aa.com",
"password":"pwd1"
}
jwt : eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbkBhYS5jb20iLCJpYXQiOjE2OTc2MDg1NzUsImV4cCI6MTY5NzYxMDM3NX0.e6TNoZBOliNRJT7o20itAFX3gcnN_D3VHHWmQRdu3GQ
*/
@PostMapping("/login")
public String authenticateAndGetToken(@RequestBody AuthRequest authRequest) {
// authenticationManager.authenticate: authenticate 호출하여 직접 인증 처리하는 구문
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken( // 토큰에 email과 password 포함
authRequest.getEmail(),
authRequest.getPassword()
));
// sec:authorize="isAuthenticated()" : 인증이 되어 있는지 확인 (navbar.html)
if (authentication.isAuthenticated()) {
return jwtService.generateToken(authRequest.getEmail());
} else {
// email이나 pwd 틀릴 경우 해당 구문에 걸려 서버500 에러 메시지 출력
throw new UsernameNotFoundException("invalid user request !");
}
}
}
package com.basic.myspringboot.jwt.service;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtService {
public static final String SECRET = "5367566B59703373367639792F423F4528482B4D6251655468576D5A71347437";
private Key getSignKey() {
byte[] keyBytes = Decoders.BASE64.decode(SECRET);
return Keys.hmacShaKeyFor(keyBytes);
}
private Claims extractAllClaims(String token) {
return Jwts
.parserBuilder()
.setSigningKey(getSignKey())
.build()
.parseClaimsJws(token)
.getBody();
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
// 토큰 생성
public String generateToken(String userName){
Map<String,Object> claims=new HashMap<>();
return createToken(claims,userName);
}
private String createToken(Map<String, Object> claims, String userName) {
return Jwts.builder() //JwtBuilder
.setClaims(claims)
.setSubject(userName)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis()+1000*60*30)) // 만료시간 : 30분
.signWith(getSignKey(), SignatureAlgorithm.HS256) // 알고리즘 설정
.compact();
}
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
package com.basic.myspringboot.jwt.filter;
import com.basic.myspringboot.jwt.service.JwtService;
import com.basic.myspringboot.service.UserInfoUserDetailsService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter { // 요청마다 매번 실행되는 토큰
@Autowired
private JwtService jwtService;
@Autowired
private UserInfoUserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String authHeader = request.getHeader("Authorization"); // 유형이 필수로 지정되어야 해서 확인
String token = null;
String username = null;
//Authorization 헤더의 값이 Bearer로 시작하는지를 체크
if (authHeader != null && authHeader.startsWith("Bearer ")) {
token = authHeader.substring(7); // Bearer + token값으로 넘어오니까 토큰만 파싱 처리
username = jwtService.extractUsername(token);
}
// SecurityContextHolder.getContext().getAuthentication() : Authentication 가져옴
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// UserDetails : 인증 정보를 담고 있는 객체
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
//Token의 유효성 검증
if (jwtService.validateToken(token, userDetails)) { // validateToken 호출
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(userDetails,
null, userDetails.getAuthorities()); // 권한 정보 포함하여 토큰 생성
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken); // 검증 확인한 후 다시 Authentication에 저장
}
}
filterChain.doFilter(request, response);
}
}
package com.basic.myspringboot.config;
import com.basic.myspringboot.jwt.filter.JwtAuthenticationFilter;
import com.basic.myspringboot.service.UserInfoUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
//// jwt 처리 - 로그인 처리
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring()
.requestMatchers(PathRequest.toStaticResources().atCommonLocations());
//.requestMatchers("/resources/static/**");
}
@Bean
@Order(1)
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
return http.csrf(csrf -> csrf.disable())
.formLogin(form -> form.disable()) // form로그인 안씀
.httpBasic(basic -> basic.disable())
.authorizeHttpRequests( auth -> {
auth.requestMatchers("/api/users/welcome", "/api/userinfos/new",
"/api/userinfos/login").permitAll()
.requestMatchers("/api/users/**").authenticated();
})
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 세션 정책
// .httpBasic(withDefaults())
.authenticationProvider(authenticationProvider())
// 해당 필터보다 jwtAuthenticationFilter 이게 먼저 동작하도록 지정
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
@Bean
@Order(2)
public SecurityFilterChain formLoginFilterChain(HttpSecurity http, HandlerMappingIntrospector introspector)
throws Exception {
return http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> {
//auth.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
auth.requestMatchers("/login").permitAll()
// .requestMatchers("/api/users/**").authenticated()
.requestMatchers("/users/**").authenticated();
//.requestMatchers("/api/users/**").authenticated();
//.requestMatchers("/**").denyAll();
})
//.formLogin(withDefaults())
.formLogin(login -> login
.loginPage("/login")
.loginProcessingUrl("/login-process")
.usernameParameter("username")
.passwordParameter("password")
.defaultSuccessUrl("/users/index", true)
.permitAll()
)
.logout((logout) -> logout.logoutUrl("/app-logout")
.deleteCookies("JSESSIONID")
.logoutSuccessUrl("/")
)
.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService() {
return new UserInfoUserDetailsService();
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider
= new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
//// jwt 처리 - 로그인 처리하기 위함
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config)
throws Exception {
return config.getAuthenticationManager();
}
// @Bean
// //authentication
// public UserDetailsService userDetailsService(PasswordEncoder encoder) {
// UserDetails admin = User.withUsername("adminboot")
// .password(encoder.encode("pwd1"))
// .roles("ADMIN")
// .build();
// UserDetails user = User.withUsername("userboot")
// .password(encoder.encode("pwd2"))
// .roles("USER")
// .build();
// return new InMemoryUserDetailsManager(admin, user);
// }
}
CREATE TABLE DEPT
(
DEPT_ID bigint not null auto_increment primary key,
DEPT_CODE int(4) NOT NULL UNIQUE,
DEPT_NAME VARCHAR(30) NOT NULL
);
insert into DEPT (dept_code, dept_name) values (10,'경제학과');
insert into DEPT (dept_code, dept_name) values (20,'컴퓨터공학과');
insert into DEPT (dept_code, dept_name) values (30,'영어영문학과');
insert into DEPT (dept_code, dept_name) values (40,'건축공학과');
commit;
CREATE TABLE STUDENT
(
STU_ID bigint not null auto_increment primary key,
STU_CODE int(6) NOT NULL UNIQUE,
STU_NAME VARCHAR(100) NOT NULL,
STU_AGE int(3) NOT NULL,
STU_GRADE VARCHAR(50),
STU_DAYNIGHT VARCHAR(50),
DEPT_CODE int(4) NOT NULL,
FOREIGN KEY (DEPT_CODE) REFERENCES DEPT (DEPT_CODE)
);
insert into student(STU_CODE, STU_NAME, STU_AGE, STU_GRADE, STU_DAYNIGHT, DEPT_CODE) values (1002,'홍길동',20,'1학년','주간',30);
commit;
CREATE TABLE COURSE
(
COURSE_ID bigint not null auto_increment primary key,
COURSE_CODE int(4) NOT NULL UNIQUE,
COURSE_NAME VARCHAR(100),
COURSE_INSTRUCTOR VARCHAR(100)
);
insert into COURSE(COURSE_CODE, COURSE_NAME, COURSE_INSTRUCTOR) values (1000,'자바프로그래밍','김자바');
insert into COURSE(COURSE_CODE, COURSE_NAME, COURSE_INSTRUCTOR) values (2000,'파이썬프로그래밍','박파이썬');
commit;
CREATE TABLE COURSE_STATUS
(
STATUS_ID bigint not null auto_increment primary key,
STU_CODE int(6) NOT NULL,
COURSE_CODE int(4) NOT NULL,
COURSE_SCORE int(4) NOT NULL,
FOREIGN KEY (STU_CODE) REFERENCES STUDENT(STU_CODE),
FOREIGN KEY (COURSE_CODE) REFERENCES COURSE(COURSE_CODE)
);
insert into COURSE_STATUS(STU_CODE, COURSE_CODE, COURSE_SCORE) values (1002,1000,90);
insert into COURSE_STATUS(STU_CODE, COURSE_CODE, COURSE_SCORE) values (1002,2000,80);
commit;
DEPT와 STUDENT
COURSE
COURSE_STATUS
StudentMapper.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="studentNS"> -->
<mapper namespace="myspring.student.dao.mapper.StudentMapper">
<!-- UserMapper.xml : <select id="selectUserById" parameterType="string" resultType="User"> -->
<resultMap id="studentDeptResultMap" type="Student">
<id property="id" column="stu_id" javaType="Long" jdbcType="NUMERIC" />
<result property="code" column="stu_code" javaType="Integer" jdbcType="NUMERIC" />
<result property="name" column="stu_name" javaType="String" jdbcType="VARCHAR" />
<result property="age" column="stu_age" javaType="Integer" jdbcType="NUMERIC" />
<result property="grade" column="stu_grade" javaType="String" jdbcType="VARCHAR" />
<result property="daynight" column="stu_daynight" javaType="String" jdbcType="VARCHAR" />
<association property="dept" column="dept_code" javaType="Dept" resultMap="deptResultMap" />
</resultMap>
<resultMap id="studentCourseStatusResultMap" type="Student">
<id property="id" column="stu_id" javaType="Long" jdbcType="NUMERIC" />
<result property="code" column="stu_code" javaType="Integer" jdbcType="NUMERIC" />
<result property="name" column="stu_name" javaType="String" jdbcType="VARCHAR" />
<result property="age" column="stu_age" javaType="Integer" jdbcType="NUMERIC" />
<result property="grade" column="stu_grade" javaType="String" jdbcType="VARCHAR" />
<result property="daynight" column="stu_daynight" javaType="String" jdbcType="VARCHAR" />
<association property="dept" column="dept_code" javaType="Dept" resultMap="deptResultMap" />
<collection property="courseStatus" ofType="CourseStatus" resultMap="coursestatusResultMap" />
</resultMap>
<resultMap id="studentResultMap" type="Student">
<id property="id" column="stu_id" javaType="Long" jdbcType="NUMERIC" />
<result property="code" column="stu_code" javaType="Integer" jdbcType="NUMERIC" />
<result property="name" column="stu_name" javaType="String" jdbcType="VARCHAR" />
<result property="age" column="stu_age" javaType="Integer" jdbcType="NUMERIC" />
<result property="grade" column="stu_grade" javaType="String" jdbcType="VARCHAR" />
<result property="daynight" column="stu_daynight" javaType="String" jdbcType="VARCHAR" />
</resultMap>
<resultMap id="deptResultMap" type="Dept">
<id property="id" column="dept_id" javaType="Long" jdbcType="NUMERIC" />
<result property="code" column="dept_code" javaType="Integer" jdbcType="NUMERIC" />
<result property="name" column="dept_name" javaType="String" jdbcType="VARCHAR" />
</resultMap>
<resultMap id="courseResultMap" type="Course">
<id property="id" column="course_id" javaType="Long" jdbcType="NUMERIC" />
<result property="code" column="course_code" javaType="Integer" jdbcType="NUMERIC" />
<result property="name" column="course_name" javaType="String" jdbcType="VARCHAR" />
<result property="instructor" column="course_instructor" javaType="String" jdbcType="VARCHAR" />
</resultMap>
<resultMap id="coursestatusResultMap" type="CourseStatus">
<id property="id" column="status_id" javaType="Long" jdbcType="NUMERIC" />
<result property="score" column="course_score" javaType="Integer" jdbcType="NUMERIC" />
<association property="course" column="course_code" javaType="Course" resultMap="courseResultMap" />
</resultMap>
<select id="selectStudentDept" resultMap="studentDeptResultMap">
select
s.stu_id,
s.stu_code,
s.stu_name,
s.stu_age,
s.stu_grade,
s.stu_daynight,
d.dept_id,
d.dept_code,
d.dept_name
from student s, dept d
where s.dept_code = d.dept_code
</select>
<select id="selectStudentCourseStatus" resultMap="studentCourseStatusResultMap">
select s.stu_id,
s.stu_code,
s.stu_name,
s.stu_age,
s.stu_grade,
s.stu_daynight,
d.dept_id,
d.dept_code,
d.dept_name,
c.course_id,
c.course_code,
c.course_name,
c.course_instructor,
t.status_id,
t.COURSE_SCORE
from student s, dept d, course_status t, course c
where s.stu_code = t.stu_code
and s.dept_code = d.dept_code
and t.course_code = c.course_code
</select>
<select id="selectCourse" resultMap="courseResultMap">
select COURSE_ID,
COURSE_CODE,
COURSE_NAME,
COURSE_INSTRUCTOR
from COURSE
order by COURSE_ID
</select>
<sql id="selectStudent">
select * from student
</sql>
<select id="selectStudentByName" parameterType="String"
resultMap="studentResultMap">
<include refid="selectStudent" />
where stu_name like CONCAT('%',#{keyword},'%')
</select>
<select id="selectStudentByGradeOrDay" parameterType="Student"
resultMap="studentResultMap">
<include refid="selectStudent" />
<where>
<if test="grade != null">
stu_grade = #{grade}
</if>
<if test="daynight != null">
and stu_daynight = #{daynight}
</if>
</where>
</select>
<select id="selectStudentByGradeOrDayMap" parameterType="Map"
resultMap="studentResultMap">
<include refid="selectStudent" />
<where>
<if test="grade != null">
stu_grade = #{grade}
</if>
<if test="day != null">
or stu_daynight = #{daynight}
</if>
</where>
</select>
<select id="selectStudentGrade" resultType="integer">
select count(*) stu_cnt from STUDENT group by STU_GRADE
</select>
<insert id="insertCourse" parameterType="Course">
insert into course
(course_code,course_name,course_instructor)
values(#{code},#{name},#{instructor})
</insert>
<insert id="insertStudent" parameterType="Student">
insert into student
(stu_code,stu_name,stu_age,stu_grade,stu_daynight,dept_code)
values(
#{code},
#{name},
#{age},
#{grade},
#{daynight},#{dept.code} )
</insert>
<update id="updateStudent" parameterType="Student">
update student set
stu_name = #{name},
stu_age = #{age},
stu_grade = #{grade},
stu_daynight = #{daynight},
dept_code = #{dept.code}
where stu_id = #{id}
</update>
<insert id="insertCourseStatus" parameterType="CourseStatus">
insert into COURSE_STATUS (STU_CODE,COURSE_CODE,COURSE_SCORE)
values (#{student.code},#{course.code},#{score})
</insert>
<delete id="deleteStudent" parameterType="Integer">
delete from student where stu_id = #{value}
</delete>
</mapper>
package com.myboot.datajpa.entity;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import com.fasterxml.jackson.annotation.JsonIgnore;
@Entity
@Table(name = "student")
public class StudentEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "student_id")
private Long id;
@Column(unique = true, nullable = false)
private Integer code;
private String name;
private Integer age;
private String grade;
private String daynight;
// 학생 -> 학과 참조 (단방향)
@OneToOne(fetch = FetchType.LAZY) // 1:1 관계
// EAGER : 참조 관계에 있는 DEPT 까지 함께 Fetch됨 (1:1 관계일때는 무관하지만, 많을 경우 모든 참조 관계 Fetct)
// LAZY : 직접 사용하는 DEPT fetch
@JoinColumn(name = "dept_id") // 외래키 연결
private DeptEntity dept; // DEPT의 PK 작성 (해당 테이블 참조한다는 목적)
//@JsonIgnore
@OneToMany(mappedBy = "student")
private List<CourseStatusEntity> courseStatus = new ArrayList<>();;
public StudentEntity() {
}
public StudentEntity(Integer code, String name, Integer age, String grade, String daynight) {
this.code = code;
this.name = name;
this.age = age;
this.grade = grade;
this.daynight = daynight;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getGrade() {
return grade;
}
public void setGrade(String grade) {
this.grade = grade;
}
public String getDaynight() {
return daynight;
}
public void setDaynight(String daynight) {
this.daynight = daynight;
}
// 해당 메소드를 호출할 때 Student가 가진 Dept 참조 테이블 Fetch
// 성능 이슈로 FetchType은 Eager이 아닌 Lazy 사용 권장
public DeptEntity getDept() {
return dept;
}
public void setDept(DeptEntity dept) {
this.dept = dept;
}
public List<CourseStatusEntity> getCourseStatus() {
return courseStatus;
}
public void setCourseStatus(List<CourseStatusEntity> courseStatus) {
this.courseStatus = courseStatus;
}
@Override
public String toString() {
return "StudentEntity [id=" + id + ", code=" + code + ", name=" + name + ", age=" + age + ", grade=" + grade
+ ", daynight=" + daynight + ", dept=" + dept + "]";
}
}
package com.myboot.datajpa.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "dept")
public class DeptEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "dept_id") // 변수는 id이지만 컬럼명은 dept_id이기 때문에 연결 목적
private Long id;
@Column(unique = true, nullable = false)
private Integer code;
private String name;
public DeptEntity() {
}
public DeptEntity(Integer code, String name) {
this.code = code;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "DeptVO [id=" + id + ", code=" + code + ", name=" + name + "]";
}
}
package com.myboot.datajpa.entity;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import com.fasterxml.jackson.annotation.JsonIgnore;
@Entity
@Table(name = "student")
public class StudentEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "student_id")
private Long id;
@Column(unique = true, nullable = false)
private Integer code;
private String name;
private Integer age;
private String grade;
private String daynight;
// 학생 -> 학과 참조 (단방향)
@OneToOne(fetch = FetchType.LAZY) // 1:1 관계
// EAGER : 참조 관계에 있는 DEPT 까지 함께 Fetch됨 (1:1 관계일때는 무관하지만, 많을 경우 모든 참조 관계 Fetct)
// LAZY : 직접 사용하는 DEPT fetch
@JoinColumn(name = "dept_id") // 외래키 연결
private DeptEntity dept; // DEPT의 PK 작성 (해당 테이블 참조한다는 목적)
//@JsonIgnore
// 학생 -> 학생 이력을 보기 위해 해당 구문 필요 (없을 경우 학생 이력에서만 학생만 볼 수 있음)
// one : 학생 many : 학생 이력
@OneToMany(mappedBy = "student") // CourseStatusEntity의 @ManyToOne의 student 변수를 매핑
private List<CourseStatusEntity> courseStatus = new ArrayList<>();;
public StudentEntity() {
}
public StudentEntity(Integer code, String name, Integer age, String grade, String daynight) {
this.code = code;
this.name = name;
this.age = age;
this.grade = grade;
this.daynight = daynight;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getGrade() {
return grade;
}
public void setGrade(String grade) {
this.grade = grade;
}
public String getDaynight() {
return daynight;
}
public void setDaynight(String daynight) {
this.daynight = daynight;
}
// 해당 메소드를 호출할 때 Student가 가진 Dept 참조 테이블 Fetch
// 성능 이슈로 FetchType은 Eager이 아닌 Lazy 사용 권장
public DeptEntity getDept() {
return dept;
}
public void setDept(DeptEntity dept) {
this.dept = dept;
}
public List<CourseStatusEntity> getCourseStatus() {
return courseStatus;
}
public void setCourseStatus(List<CourseStatusEntity> courseStatus) {
this.courseStatus = courseStatus;
}
@Override
public String toString() {
return "StudentEntity [id=" + id + ", code=" + code + ", name=" + name + ", age=" + age + ", grade=" + grade
+ ", daynight=" + daynight + ", dept=" + dept + "]";
}
}
package com.myboot.datajpa.entity;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import com.fasterxml.jackson.annotation.JsonIgnore;
@Entity
@Table(name = "course_status")
public class CourseStatusEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "status_id")
private Long id;
//@JsonIgnore
// 학생 이력 -> 학생을 봤을 때 학생에게 이력이 여러개 이기 때문에 ManyToOne
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "student_id")
private StudentEntity student;
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "course_id")
private CourseEntity course;
private Integer score;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public StudentEntity getStudent() {
return student;
}
public void setStudent(StudentEntity student) {
this.student = student;
}
public CourseEntity getCourse() {
return course;
}
public void setCourse(CourseEntity course) {
this.course = course;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
@Override
public String toString() {
return "CourseStatusEntity [id=" + id + ", score=" + score + "]";
}
}
FetchType의 Default가 Eager이기 때문에, 모두 Lazy로 설정 필요
StudentServiceImpl.java
package com.myboot.datajpa.service;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.modelmapper.ModelMapper;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.myboot.datajpa.entity.CourseStatusEntity;
import com.myboot.datajpa.entity.StudentEntity;
import com.myboot.datajpa.exception.BusinessException;
import com.myboot.datajpa.repository.CourseStatusRepository;
import com.myboot.datajpa.repository.DeptRepository;
import com.myboot.datajpa.repository.StudentRepository;
import com.myboot.datajpa.vo.CourseStatusVO;
import com.myboot.datajpa.vo.DeptVO;
import com.myboot.datajpa.vo.StudentVO;
@Service("studentService")
@Transactional
public class SutdentServiceImpl implements StudentService {
private StudentRepository studentRepository;
private DeptRepository deptRepository;
private CourseStatusRepository statusRepository;
private ModelMapper modelMapper;
// modelMapper : Entity -> VO 변환 역할
public SutdentServiceImpl(StudentRepository studentRepository, DeptRepository deptRepository,
CourseStatusRepository statusRepository, ModelMapper modelMapper) {
this.studentRepository = studentRepository;
this.deptRepository = deptRepository;
this.statusRepository = statusRepository;
this.modelMapper = modelMapper;
}
@Override
public StudentVO getStudent(Integer code) throws Exception {
// 매핑을 할 때 변수명과 컬럼명이 일치하지 않을 수 있을 경우를 고려
modelMapper.getConfiguration().setAmbiguityIgnored(true);
Optional<StudentEntity> optional = studentRepository.findByCode(code); // 학번을 가져와 사용
// 학번이 없을 경우 에러 처리
if(!optional.isPresent())
throw new BusinessException(code + " Student가 존재하지 않습니다.", HttpStatus.NOT_FOUND);
StudentEntity student = optional.get();
StudentVO studentVO = modelMapper.map(student, StudentVO.class); //Entity -> VO 변환
// Fetch Type이 Lazy로 설정되어 있을 경우 getDept() 메서드를 호출할 때 데이터 가져옴
DeptVO deptVO = modelMapper.map(student.getDept(), DeptVO.class); // VO -> Entity 변환
studentVO.setDept(deptVO);
List<CourseStatusEntity> statusList = student.getCourseStatus();
System.out.println(statusList);
// Stream을 사용하여 Entity -> VO 변환
List<CourseStatusVO> statusVoList = statusList.stream()
.map(entity -> new CourseStatusVO(entity))
.collect(Collectors.toList());
studentVO.setCourseStatus(statusVoList); // VO 반환
return studentVO;
}
@Override
public List<CourseStatusVO> getCourseStatus(Integer code) throws Exception {
Optional<StudentEntity> optional = studentRepository.findByCode(code);
if(!optional.isPresent())
throw new BusinessException(code + " Student가 존재하지 않습니다.", HttpStatus.NOT_FOUND);
StudentEntity student = optional.get();
List<CourseStatusEntity> statusList = statusRepository.findByStudent(student);
List<CourseStatusVO> statusVoList = statusList.stream()
.map(status -> new CourseStatusVO(status, status.getStudent()))
.collect(Collectors.toList());
return statusVoList;
}
@Override
public StudentEntity getStudentEntity(Integer code) throws Exception {
Optional<StudentEntity> optional = studentRepository.findByCode(code);
if(!optional.isPresent())
throw new BusinessException(code + " Student가 존재하지 않습니다.", HttpStatus.NOT_FOUND);
StudentEntity student = optional.get();
return student;
}
}