반응형

토이 프로젝트에서 로그인 후 화면에서 api 호출 시 jwt토큰을 이용하여 인증처리 하는 방식으로 개발 하고자하여 작성한다.

 

환경설정
  • Spring boot 2.7.2
  • open JDK 1.8
  • InteliJ
  • MacOS

 

Spring Security

Spring Security는 Spring과는 별개로 작동하는 보안담당 프레임워크이다.

 

크게 두 가지의 동작을 수행한다.

1.Authenticatio(인증): 특정 대상이 "누구" 인지 확인하는 절차이다.

2.Authorization(권한) : 인증된 주체가 특정한 곳에 접근 궈한을 확인하는 것이다.

 

Spring Security 인증과정

인증하는 과정은 위 그림과 같다.

 

1. Http Request가 서버로 넘어온다.

2. 가장먼저 AuthenticationFilter가 요청을 낚아챈다.

3. AuthenticationFilter에서 Reqeust의 UserName, Password를 이용하여 UserNamePasswordAuthenticationToken을 생성한다.

4. 토큰을 AuthenticationManager가 받는다.

5. AuthenticationManager는 토큰을 AuthenticationProvider에게 토큰을 넘겨준다.

6. AuthenticationProvider는 UserDetailsService로 토큰의 사용자 아이디(Username)을 전달하여 DB에 존재하는지 확인한다. 이때, UserDetailsService는 DB의 회원정보를 UserDetails라는 객체로 반환한다.

7. AuthenticationProvider는 반환받은 UserDetilas객체와 실제 사용자의 입력정보를 비교한다.

8. 비교가 완료되면 사용자 정보를 가진 Authentication 객체를 SecurityContextHolder에 담은 이후 AuthenticationSuccessHandle를 실행한다.(실패시 AuthenticationFailuereHandler를 실행한다)

 

 

Spring Security Filter

스프링 시큐리티는 필터를 기반으로 수행된다.

필터와 인터셉터의 차이는 실행되는 시점의 차이이다
필터 : dispatcher servlet으로 요청이 도착하기 전에 동작한다.
인터셉터 : dispatcher servlet을 지나고 controller에 도착하기 전에 동작한다.
  • SecurityContextPersistenceFilter : SecurityContextRepository에서 SecrityContext를 가져오거나 저장하는 역할을 한다.
  • LogoutFilter : 설정된 로그아웃 URL로 오는 요청을 감시하며, 해당 유저를 로그아웃 처리
  • (UsernamePassword)AuthenticationFilter : (아이디와 비밀번호를 사용하는 form 기반 인증) 설정된 로그인 URL로 오는 요청을 감시하며, 유저 인증 처리
    • AuthenticationManager를 통한 인증 실행
    • 인증 성공 시, 얻은 Authentication 객체를 SecurityContext에 저장 후 AuthenticationSuccessHandler 실행
    • 인증 실패 시, AuthenticationFailureHandler 실행
  • DefaultLoginPageGeneratingFilter : 인증을 위한 로그인폼 URL을 감시한다.
  • BasicAuthenticationFilter : HTTP 기본 인증 헤더를 감시하며 처리한다.
  • RequestCacheAwareFilter : 로그인 성공 후, 원래 요청 정보를 재구성하기 위해 사용된다.
  • SecurityContextHolderAwareRequestFilter : HttpServletRequestWrapper를 상속한 SecurityContextHolderAwareRequestWapper 클래스로 HttpServletRequest정보를 감싼다. SecurityContextHolderAwareRequestWrapper 클래스는 필터 체인상의 다음 필터들에게 부가정보를 제공한다.
  • AnonymousAuthenticationFilter : 이 필터가 호출되는 시점까지 사용자 정보가 인증되지 않았다면 인증토큰에 사용자가 익명 사용자로 나타난다.
  • SessionManagementFilter : 이 필터는 인증된 사용자와 관련된 모든 세션을 추적한다.
  • ExceptionTranslationFilter : 이 필터는 보호된 요청을 처리하는 중에 발생할 수 있는 예외를 위임하거나 전달하는 역할을 한다.
  • FilterSecurityInterceptor : 이 필터는 AccessDecisionManager 로 권한부여 처리를 위임함으로써 접근 제어 결정을 쉽게해준다
모든 필터를 달달 외울 필요는 없을 것 같고, 대충 필터들이 존재한다는 감만 익히자

 

필터체인

 

 

JWT (Json Web Token)

Cookie & Session & JWT

Cookie, Session, JWT는 모두 비연결성인 네트워크 서버 특징을 연결성으로 사용하기 위한 방법이다.

  • Cookie, Session은 서버의 어떠한 저장소에 해당 값과 매칭되는 value를 가지고 있어야한다. 그래서 서버 자원이 많이 사용되는 단점이 있다.
  • JWT는 Cookie & Session의 자원문제를 해결하기 위한 방법이다. JWT는 토큰 자체에 유저정보를 담아서 암호화한 토큰이라고 생각하면 된다. 암호화된 내용은 디코딩 과정을 통해서 해석이 가능하다.

 

본 프로젝트 JWT를 이용한 인증절차

1. 사용자가 로그인을 한다.

2. 서버에서는 계정 정보를 읽어 사용자를 확인 후, 사용자의 고유값을 부여한 후 기타 정보와 함께 payload에 집어넣는다.

3. JWT 토큰의 유효기간을 설정한다.

4. 암호화할 Secret Key를 이용해 Access Token을 발급한다.

5. 사용자는 Access Token을 받아 저장 후, 인증이 필요한 요청마다 토큰을 헤더에 실어보낸다.

6. 서버에서는 해당 토큰의 Verify Signature를 Secret key로 복호화 후, 조작여부, 유효기간을 확인한다.

7. 검증이 완료되었을 경우 Payload를 디코딩 하여 사용자의 ID에 맞는 데이터를 가져온다.

JWT는 보통 Access Token의 유효기간이 짧다. 이유는 보안문제이다 그래서 Refresh Token을 따로 발급해주는데, Access Token이 만료되면 새로운 JWT토큰을 발급할 수 있는 토큰이다.

 

 

Spring Security + JWT

Graddle

    implementation 'org.springframework.boot:spring-boot-starter-security'
    testImplementation 'org.springframework.security:spring-security-test'

 

Config

1. 가장 먼저 Security와 Filter관련 설정을 해주어야한다.

@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final JwtTokenProvider jwtTokenProvider;

    // authenticationManager를 Bean 등록합니다.
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable();
        //http.httpBasic().disable(); // 일반적인 루트가 아닌 다른 방식으로 요청시 거절, header에 id, pw가 아닌 token(jwt)을 달고 간다. 그래서 basic이 아닌 bearer를 사용한다.
        http.httpBasic().disable()
                .authorizeRequests()// 요청에 대한 사용권한 체크
                .antMatchers("/test").authenticated()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/user/**").hasRole("USER")
                .antMatchers("/**").permitAll()
                .and()
                .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider),
                        UsernamePasswordAuthenticationFilter.class); // JwtAuthenticationFilter를 UsernamePasswordAuthenticationFilter 전에 넣는다
        // + 토큰에 저장된 유저정보를 활용하여야 하기 때문에 CustomUserDetailService 클래스를 생성합니다.
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);


    }
}

 

1. 먼저 AuthenticationManager를 Bean으로 등록해준다.

2. Config 설정

  • antMatchers() : 해당 URL로 요청 시 설정을 해준다.
  • authenticated() : antMatchers에 속해있는 URL로 요청이 오면 인증이 필요하다고 설정한다.
  • hasRole() : antMatchers에 속해있는 URL로 요청이 들어오면 권한을 확인한다.

3. addFilterBefore() : 필터를 등록한다. 스프링 시큐리티 필터링에 등록해주어야 하기 때문에, 여기에 등록해주어야한다. 파라미터는 2가지가 들어간다. 왼쪽은 커스텀한 필터링이 들어간다. 오른쪽에 등록한 필터전에 커스텀필터링이 수행된다.

4. http.sesstionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) : 세션을 사용하지 않는다고 설정한다.

 

Custom FIlter

//해당 클래스는 JwtTokenProvider가 검증을 끝낸 Jwt로부터 유저 정보를 조회해와서 UserPasswordAuthenticationFilter 로 전달합니다.
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends GenericFilterBean {

    private final JwtTokenProvider jwtTokenProvider;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 헤더에서 JWT 를 받아옵니다.
        String token = jwtTokenProvider.resolveToken((HttpServletRequest) request);
        // 유효한 토큰인지 확인합니다.
        if (token != null && jwtTokenProvider.validateToken(token)) {
            // 토큰이 유효하면 토큰으로부터 유저 정보를 받아옵니다.
            Authentication authentication = jwtTokenProvider.getAuthentication(token);
            // SecurityContext 에 Authentication 객체를 저장합니다.
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        chain.doFilter(request, response);
    }
}

 

Custom Provider

// 토큰을 생성하고 검증하는 클래스입니다.
// 해당 컴포넌트는 필터클래스에서 사전 검증을 거칩니다.
@RequiredArgsConstructor
@Component
public class JwtTokenProvider {
    private String secretKey = "myprojectsecret";

    // 토큰 유효시간 30분
    private long tokenValidTime = 30 * 60 * 1000L;

    private final UserDetailsService userDetailsService;

    // 객체 초기화, secretKey를 Base64로 인코딩한다.
    @PostConstruct
    protected void init() {
        secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
    }

    // JWT 토큰 생성
    public String createToken(String userPk, List<String> roles) {
        Claims claims = Jwts.claims().setSubject(userPk); // JWT payload 에 저장되는 정보단위, 보통 여기서 user를 식별하는 값을 넣는다.
        claims.put("roles", roles); // 정보는 key / value 쌍으로 저장된다.
        Date now = new Date();
        return Jwts.builder()
                .setClaims(claims) // 정보 저장
                .setIssuedAt(now) // 토큰 발행 시간 정보
                .setExpiration(new Date(now.getTime() + tokenValidTime)) // set Expire Time
                .signWith(SignatureAlgorithm.HS256, secretKey)  // 사용할 암호화 알고리즘과
                // signature 에 들어갈 secret값 세팅
                .compact();
    }

    // JWT 토큰에서 인증 정보 조회
    public Authentication getAuthentication(String token) {
        UserDetails userDetails = userDetailsService.loadUserByUsername(this.getUserPk(token));
        return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
    }

    // 토큰에서 회원 정보 추출
    public String getUserPk(String token) {
        return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
    }

    // Request의 Header에서 token 값을 가져옵니다. "Authorization" : "TOKEN값'
    public String resolveToken(HttpServletRequest request) {
        return request.getHeader("Authorization");
    }

    // 토큰의 유효성 + 만료일자 확인
    public boolean validateToken(String jwtToken) {
        try {
            Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwtToken);
            return !claims.getBody().getExpiration().before(new Date());
        } catch (Exception e) {
            return false;
        }
    }
}

 

User, UserDetailService 작성

@Builder
@Data
@Entity
@Table(name = "T_USER")
@NoArgsConstructor
@AllArgsConstructor
public class User implements UserDetails {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "USER_SEQUENCE_ID")
    private Long userSequenceId;

    @Column(name = "USER_EMAIL", nullable = false, length = 100, unique = true)
    private String userEmail;

    @Column(name = "USER_BIRTH", length = 6)
    private String userBirth;

    @Column(name = "USER_NICKNAME", length = 15)
    private String userNickname;

    @Column(name = "ADMIN", length = 10)
    @Enumerated(EnumType.STRING)
    private enums.Admin admin;

    @ElementCollection(fetch = FetchType.EAGER)
    @Builder.Default
    private List<String> roles = new ArrayList<>();


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.roles.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(   Collectors.toList());
    }

    @Override
    public String getPassword() {
        return null;
    }

    @Override
    public String getUsername() {
        return userEmail;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

 

@RequiredArgsConstructor
@Service
public class CustomUserDetailService implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        return userRepository.findByUserEmail(username)
                .orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다."));


    }
}

 

Test Controller 

@RestController
public class TestController {
    @PostMapping("/test")
    public String test(){
        return "<h1>테스트 통과</h1>";
    }
}

 

회원가입, 로그인 Contoller 작성

@Slf4j
@RestController
@RequiredArgsConstructor
public class UserController {
    private final JwtTokenProvider jwtTokenProvider;
    private final UserRepository userRepository;

    final String BIRTH = "001200";
    final String EMAIL = "aabbcc@gmail.com";
    final String NICKNAME = "침착맨";
    final Long SEQUENCEID = Long.valueOf(1);
    final enums.Admin ADMIN = enums.Admin.Admin;

    User user = User.builder()
            .userEmail(EMAIL)
            .userBirth(BIRTH)
            .userNickname(NICKNAME)
            .admin(ADMIN)
            .userSequenceId(SEQUENCEID)
            .roles(Collections.singletonList("ROLE_USER")) // 최초 가입시 USER 로 설정
            .build();


    @PostMapping("/join")
    public String join(){
        log.info("로그인 시도됨");

        userRepository.save(user);


        return user.toString();

    }

    // 로그인
    @PostMapping("/login")
    public String login(@RequestBody Map<String, String> user) {
        log.info("user email = {}", user.get("email"));
        User member = userRepository.findByUserEmail(user.get("email"))
                .orElseThrow(() -> new IllegalArgumentException("가입되지 않은 E-MAIL 입니다."));

        return jwtTokenProvider.createToken(member.getUsername(), member.getRoles());
    }
}

 

 

Test

1. Postman 실행

2. 회원가입 호출

 

4. 테스트 호출

 

 

 

 

 

출처

https://velog.io/@jkijki12/Spirng-Security-Jwt-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0

 

Spirng Security + Jwt 로그인 적용하기

프로젝트를 진행하면서 Spring Security + Jwt를 이용한 로그인을 구현하게 되었다. 목차 Spring Security JWT Spring SEcurity + JWT Spring Security > 가장먼저 스프링 시큐리티에 대해서 알아보자. Sprin

velog.io

 

 

감사합니다

반응형

'BackEnd > Java' 카테고리의 다른 글

[Thymeleaf] layout-dialect 사용하기  (0) 2022.07.22
SpringBoot MySQL & JPA 연동  (0) 2022.03.31
[IntelliJ] Lombok 설치 및 Lombok Annotation 정리  (0) 2022.03.16
JAVA 버전이 변경 안될때 체크사항  (0) 2022.03.16
IntelliJ 설치  (0) 2022.03.15
반응형
환경
  • Spring boot 2.7.2
  • open JDK 1.8
  • InteliJ
  • MacOS

 

dependency 추가
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation group: 'nz.net.ultraq.thymeleaf', name: 'thymeleaf-layout-dialect', version: '2.4.1'

spring-boot-starter-thymeleaf : thymleaf 뷰 템플릿 엔진을 사용하기 위한 기본적인 라이브러리

thymeleaf=layout-dialect : thymeleaf를 이용해서 layout을 만들기 위해 추가적으로 사용되는 라이브러리

 

Bean등록
@Configuration
public class LayoutConfig {
    @Bean
    public LayoutDialect layoutDialect(){
        return new LayoutDialect();
    }
}

 

사용법

1. 고정적인 head, header, footer 영역은 th:replace="fragments/head :: headFragment" 를 사용해서 정의하자.

:: 앞은 경로를, :: 뒤는 구분자를 의미한다.

 

2. html 태그에 xmlns:th="http://www.thymeleaf.ofg" 네임스페이스를 작성해주어야 IDE가 타임리프 문법에 error를 뿌리지 않는다. 또한, layout의 틀을 만들때는 꼭 xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" 네임스페이스를 적어주어야 한다! (layout의 틀임을 알려주는 격)

 

Layout 구조
  • layout/layout.html : layout의 기본 틀이되는 html
  • layout/fragments/head.html : css관리
  • layout/fragments/script.html : script 관리
  • layout/fragments/top.html : body의 머리
  • layout/fragments/footer.html : body의 꼬리
  • layout/fragments/sidebar.html : 사이드메뉴
  • content : body의 몸통

 

Layout.html
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">

<!-- 공통 헤드-->
<th:block th:replace="/layout/fragments/head :: headFragment"></th:block>

<body id="page-top">
<div id="wrapper">
    <!-- 사이드바-->
    <th:block th:replace="/layout/fragments/sidebar :: sidebarFragment"></th:block>

    <div id="content-wrapper" class="d-flex flex-column">
        <div id="content">
            <!-- 상단바-->
            <th:block th:replace="/layout/fragments/top :: topbarFragment"></th:block>

            <!-- 본문-->
            <th:block layout:fragment="content"></th:block>
        </div>

        <!-- 공통 하단-->
        <th:block th:replace="/layout/fragments/footer :: footerFragment"></th:block>
    </div>
</div>

<!-- 공통 스크립트-->
<th:block th:replace="/layout/fragments/script :: scriptFragment"></th:block>
</body>
</html>

 

 

footer.html
<!DOCTYPE html>
<html lagn="ko" xmlns:th="http://www.thymeleaf.org">

<!-- Footer -->
<th:block th:fragment="footerFragment">
    <footer class="sticky-footer bg-white">

        .... 하단(footer) 코드 작성

    </footer>
</th:block>

</html>

 

head.html
<!DOCTYPE html>
<html lagn="ko" xmlns:th="http://www.thymeleaf.org">

<!-- Head -->
<th:block th:fragment="headFragment">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">

        .... head 태그 코드 작성

        <link href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i" rel="stylesheet" />
        <link th:href="@{/bootstrap/vendor/fontawesome-free/css/all.min.css}" rel="stylesheet" type="text/css" />
        <link th:href="@{/bootstrap/css/sb-admin-2.min.css}" rel="stylesheet" />

    </head>
</th:block>
</html>

 

script.html
<!DOCTYPE html>
<html lagn="ko" xmlns:th="http://www.thymeleaf.org">

<!-- 공통으로 쓰이는 script 파일을넣는다.-->
<th:block th:fragment="scriptFragment">


    <!-- Bootstrap core JavaScript-->
    <script th:src="@{/bootstrap/vendor/jquery/jquery.min.js}"></script>
    <script th:src="@{/bootstrap/vendor/bootstrap/js/bootstrap.bundle.min.js}"></script>

    .... 스크립트 코드 작성

    <!-- Page level custom scripts -->
    <script th:src="@{/bootstrap/js/demo/chart-area-demo.js}"></script>
    <script th:src="@{/bootstrap/js/demo/chart-pie-demo.js}"></script>

</th:block>
</html>

 

sidebar.html
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">

<th:block th:fragment="sidebarFragment">

    <!-- Sidebar -->
    <ul class="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion" id="accordionSidebar">

        .... 사이드바 코드 작성

    </ul>

</th:block>

</html>

 

top.html
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.w3.org/1999/xhtml">

<th:block th:fragment="topbarFragment">

    <!-- Topbar -->
    <nav class="navbar navbar-expand navbar-light bg-white topbar mb-4 static-top shadow">

        .... 상단 코드 작성

    </nav>

</th:block>
</html>

 

 

 

 

결과

 

반응형

'BackEnd > Java' 카테고리의 다른 글

[Spring] Spring Security + jwt 사용  (0) 2022.07.23
SpringBoot MySQL & JPA 연동  (0) 2022.03.31
[IntelliJ] Lombok 설치 및 Lombok Annotation 정리  (0) 2022.03.16
JAVA 버전이 변경 안될때 체크사항  (0) 2022.03.16
IntelliJ 설치  (0) 2022.03.15
반응형
JPA(Java Persistence API) 란?

자바 진영의 ORM 기술표준으로, 인터페이스의 모음이다.  ※ 출처참조

 

 

환경
IntelliJ IDEA Ultimate 2021. 3. 2
Gradle 7.4.1
MySQL 5.7

 

 

 

 

Build.gradle 의존성 추가
dependencies {
      implementation 'mysql:mysql-connector-java'
      implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
 }

 

 

application.properties에 DB 정보 입력
# MySQL 설정
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# DB Source URL
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/Schema?useSSL=false&useUnicode=true&serverTimezone=Asia/Seoul

#DB Username
spring.datasource.username=<username>

#DB Password
spring.datasource.password=<password>

#true 설정시 JPA 쿼리문 확인 가능
spring.jpa.show-sql=true

#DDL(Create, Alter, Drop) 정의시 DB의 고유 기능을 사용할 수 있다.
spring.jpa.hibernate.ddl-auto=update

# JPA의 구현체인 Hibernate가 동작하면서 발생한 SQL의 가독성을 높여준다.
spring.jpa.properties.hibernate.format_sql=true

spring.jpa.hibernate.ddl-auto=[]

  • create : 기존 테이블을 삭제하고 새로 생성 [ Drop + Create ]
  • create-drop : Create 속성에 추가로 어플리케이션을 종료할 때 생성한 DDL을 제거 [ Drop + Create + Drop]
  • update : DB 테이블과 엔티티 매핑 정보를 비교해서 변경 사항만 수정 [ 테이블이 없을 경우 Create ]
  • validate : DB 테이블과 엔티티 매핑정보를 비교해서 차이가 있으면 경고를 남기고 어플리케이션을 실행하지 않음
  • none : 자동 생성 기능을 사용하지 않음

 

 

JPA Entity 생성?
@ToString
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Table (name ="tbl_memo") // 클래스명과 테이블명이 다를때 매핑시켜주기위해 사용
@Entity
public class Memo {
    @Id
    @GeveratedValue(strategy = GenerationType.IDENTITY) //MySQL의 Auto_INCREMENT를 사용
    private Long id;

    @Column(length =200, nullable =false)
    private String memoText;
}

 

사용 어노테이션 정리

 

  • @Entity : DB의 테이블을 뜻함 [ Spring Data JPA 에서는 반드시 @Entity 어노테이션을 추가해야함]
  • @Table : DB 테이블의 이름을 명시 [ 테이블 명과 클래스 명이 동일한 경우 생략가능 ]
  • @Getter : Lombok의 Getter를 이용해 Getter 메소드를 생성하고 @Builder를 이용해서 객체를 생성할 수 있게처리한다.
  • @Builder를 이용하기 위해 @AllArgsContructor@NoArgsContructor를 같이 처리해야 컴파일 에러가 발생하지 않음
  • @Id : Privary Key를 뜻함
  • @HeneratedValue : Primary Key의 키 생성 전략(Strategy)을 설정하고자 할 때 사용
  1. GenerationType.IDENTITY : MySQL의 AUTO_INCREMENT 방식을 이용
  2. GenerationType.AUTO(default: JPA구현체(Hibernate)가 생성 방식을 결정
  3. GenerationType.SEQUENCE: DB의 SEQUENCE를 이용해서 키를 생성 . @SequenceGenerator와 같이 사용
  4. GenerationType.TABLE: 키 생성 전용 테이블을 생성해서 키 생성. @TableGenerator와 함께 사용
  • @Column : DB Column을 명시
  • @Transient : @Column과 반대로 테이블에 컬럼으로 생성되지 않은 필드의 경우에 사용한다.

 

 

JPA Repository 생성

Spring Data JPA는 JPA의 구현체인 Hibernate를 이용하기 위한 여러 API를 제공합니다.

그 중에서 가장 많이 사용되는 것이 JPA Repository라는 인터페이스입니다.

 

public interface MemoReporisotry extends JpaRepository<Memo, Long> {

}

작성된 MemoRepository는 JpaRepository를 상속하는 것만으로 모든작업이 끝납니다.

  • MemoRepository를 이용해서 작성된 테이블에 SQL문 없이 CRUD 작업을 할 수 있게 됩니다.
  • JpaRepository의 제네릭 타입으로는 <Entity, PK의 타입>을 지정해주면 Spring Data JPA는 자동으로 스프링의 빈(bean)으로 등록됩니다.
  • Spring이 내부적으로 인터페이스 타입에 맞는 객체를 생성해서 bean으로 등록

 

테스트를 통한 CURD 기능 확인

1.1 Create 테스트

 

Insert를 할 때 Repository 객체의 save()메소드를 사용합니다.

 

@SpringBootTest
pulbic class MemoRepositoryTest{
    @Autowired
    MemoRepository memoRepository;

    @Test
    public void InsertDummies() {
        IntStream.rangeClosed(1, 10).forEach( i -> {
            Memo memo = Memo.builder()
                                          .memoText("Sample...." + i)
                                          .build();
            //Create!
            memoRepository.save(memo);
        });
    }
}

 

1.2 Read 테스트

 

Select를 할 때 Repository 객체의 findById() 메소드를 사용합니다.

findById()를 통해 기본키를 넣어주면, 해당하는 객체를 Optional 타입으로 반환해줍니다.

@SpringBootTest
pulbic class MemoRepositoryTest{
    @Autowired
    MemoRepository memoRepository;

    @Test
    public void SelectDummies() {
        Long id = 10L;

        Optionam<Memo> result = memoRepository.findById(id);

        System.out.println("------------------------------------------";
          if(result.isPresned()){
               Memo memo = result.get();
               System.out.println(memo);
        }
    }
}

 

1.3 Update 테스트

 

수정 작업과 등록작업은 동일하게 save()를 이용해서 처리합니다.

내부적으로 해당 엔티티의 @Id값이 일치하는지를 확인해서 Insert 혹은  Update작업을 처리합니다.

 

@SpringBootTest
pulbic class MemoRepositoryTest{
    @Autowired
    MemoRepository memoRepository;

 @Test
    public void UpdateDummies() {
        Memo memo = Memo.builder()
                                      .id(10L)
                                      .memoText("Update Text")
                                      .build();
        memoRepository.save(memo);
    }
}

 

 

1.4 Delete 테스트

 

삭제 작업도 수정 작업과 동일한 개념으로 삭제하려는 엔티티 객체가 있는지 먼저 확인하고, 삭제합니다

@SpringBootTest
pulbic class MemoRepositoryTest{
    @Autowired
    MemoRepository memoRepository;

    @Test
    public void DeleteDummies() {
        Long id = 10L;
        
        memoRepository.deleteById(id);
    }
}

 

 

출처

https://dev-coco.tistory.com/85

 

[Spring Boot] MySQL & JPA 연동 및 테스트 (Gradle 프로젝트)

SpringBoot에서 MySQL 그리고 Spring Data JPA를 연동하는 방법에 대해 알아보도록 하겠습니다. 1. 프로젝트에 의존성 추가하기 build.gradle에 의존성을 아래와 같이 추가해줍니다. dependencies { implementatio..

dev-coco.tistory.com

 

 

감사합니다.

 

반응형
반응형
Lombok이란?

Lombok은 자바 Domain(DTO, VO) 에서 반복적으로 작성되는 getter/setter나 toString, 생성사 코드 등의 소스들을, 어노테이션(Annotaion)을 사용하여 생략할 수 있도록 컴파일 시점에 자동으로 생성해주는 라이브러리이다.

 

환경
IntelliJ IDEA Ultimate 2021. 3. 2
Gradle 7.4.1

※IntelliJ 2020.03 이후 버전에서는 기본Plugin으로 Lombok이 설치되어 있습니다.

 

설치방법

파일 -> 설정 -> 플러그인 -> Lombok 검색 후 설치

 

Dependency 설정하기

Lombok Plugin을 사용하기 위해서는 dependency에 추가되어야만 사용이 가능하다.

 

Gradle 7.4.1

//build.gradle
...
dependencies {
     //lombok plugin
     implementation 'org.projectlombok:lombok'
     annotationProcessor 'org.projectlombok:lombok'

     //default
     implementation 'org.springframework.boot:spring-boot-starter-web'
     testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

 

Annotation Processing 설정하기

어노테이션 프로세싱 활성화를 해주어야 어노테이션 기반인 lombok을 사용할 수 있습니다.

파일 -> 설정 -> 빌드,실행,배포 -> 컴파일러 -> 어노테이션프로세서 에서 어노테이션 처리 활성화를 체크해준다

 

 

Lombok의 Annotation

@Getter/@Setter

  • 어노테이션을 붙인 클래스 멤버 변수들 혹은 특정 변수의 getXXX(), setXXX() 메소드를 생성한다.
  • 기본적으로 public method로 선언된다.
  • @Setter(AccessLevel.PRIVATE)처럼 AccessLevel을 통하여 접근권한을 설정할 수 있다. (PRIVATE, PROTECTED 등)
  • @Setter 같은 경우는 무결성이 보장되어야 하는 클래스나 변수에는 사용을 지양해야 한다.

 

@ToString

  • 어노테이션을 붙인 클래스의 toString() 메소드를 생성한다.
  • @ToString(callSuper = true)를 사용하면 상속받은 클래스의 정보까지 출력된다. (default값은 false)
  • @ToString을 붙인 클래스에 순환 참조를 하는 객체 타입 필드가 있다면, 무한 루프가 발생한다.
  • ex1) 클래스 A에 어노테이션을 붙였는데, A의 멤버 변수에 클래스 B타입이 있고 B의 멤버 변수에 클래스 A타입이 있을 경우 무한루프 발생
  • solution) @Tostring(exclude = "A")와 같이 명시적으로 해당 필드를 제외시켜줘야 한다.

 

@NonNull

  • 어노테이션을 붙인 변수는 반드시 값이 있어야 한다.
  • Setter에 null 값이 들어오게 되면 NulPointerException이 발생하게된다.
  • @NonNull은 불필요하게 branch coverage를 증가시키므로 사용이 권장되지는 않는다.

@NoArgsConstructor

  • 어노테이션이 붙은 클래스의 기본 생성자를 생성한다.
  • AcessLevel 을 통하여 접근권한을 설정할 수 있다.
  • 기본값은 Public 이지만, Protected 를 사용하여 안전성을 보장해주는 것을 권장한다.

@RequiredArgsConstructor

  • 어노테이션이 붙은 클래스의 final 혹은 @NonNull인 필드만을 포함한 생성자를 생성한다.
  • AccessLevel을 통하여 접근권한을 설정할 수 있다.
  • 필드에 잘못된 값이 들어가도 에러를 뱉이 않을수도 있기에 사용에 주의가 필요하다.

@AllArgsConstructor

  • 어노테이션이 붙은 클래스의 모든 필드를 포함한 생성자를 생선한다.
  • AccessLevel을 통하여 접근권한을 설정할 수 있다.
  • 필드에 잘못된 값이 들어가도 에러를 뱉지 않을수도 있기에 사용에 주의가 필요하다.

@EqualsAndHashCode

  • 어노테이션이 붙은 클래스에 equals()hashCode() 메소드를 생성한다.
  • exclude를 통하여 특정 필드를 제외시킬 수 있다.
  • equals() - 두 객체의 내용이 같은지 비교하는 메소드이다.
  • hashCode() - 해당 객체의 해시값을 반환하는 메소드이다.

@Data

  • @Getter, @Setter, @EqualsAndHashCode, @RequiredArgsConstructor, @ToString 을 포함한다.
  • @Data(staticConstructor = "foo") 와 같이 foo라는 static factory method를 생성 할 수도 있다.

@Value

  • 어노테이션이 붙은 클래스를 불변(Immutable) 클래스로 선언한다.
  • 모든 필드와 클래스를 기본적으로 private 및 final로 선언하고, setter 메소드를 생성하지 않는 점을 제외하곤 @Data와 비슷하다.

@Cleanup

  • close() 메소드를 자동으로 호출해주어, close()관련 코드 작성을 최소화 할 수 있다.

 

@Synchronized

  • synchronized 키워드를 사용할 때 데드락이 발생하는 경우를 방지하기 위해 어노테이션이 붙은 메소드가 실행되기 전에 잠금 $lock이라는 개인 잠금 필드를 생성한다
  • lock 오브젝트를 자동으로  생성하므로, Synchronized를 손쉽게 사용할 수 있게 해준다.

@Builder

  • 빌더 패턴을 사용할 수 있도록 빌더API를 제공한다
  • private 이긴 하지만, @AllArgsConstructor를 기본적으로 적용한다.
  • 클래스보다는 사용자 정의 생성자 혹은 static 객체 생성 메소드에 사용하는 것을 권장한다.
  • @Singular : Collection 타입에 서넝ㄴ하게 되면 파라미터를 하나씩 추가할 수 있다.

@Log, @Slf4j

  • 로그를 남기기 위한 어노테이션이다.
  • 어노테이션을 클래스에 선언하면, log관련 static 메소드를 선언하지 않아도 된다.
정리
보통 @Getter, @Setter, @ToString, @Builder, @Log 정도만 많이 사용한다고 합니다.
@Data 보다는 코드가 길어지더라도 필요한 어노테이션만 선언하는 것을 권장합니다

 

 

출처
http://gre-eny.tistory.com/303
반응형

'BackEnd > Java' 카테고리의 다른 글

[Spring] Spring Security + jwt 사용  (0) 2022.07.23
[Thymeleaf] layout-dialect 사용하기  (0) 2022.07.22
SpringBoot MySQL & JPA 연동  (0) 2022.03.31
JAVA 버전이 변경 안될때 체크사항  (0) 2022.03.16
IntelliJ 설치  (0) 2022.03.15
반응형

JAVA설치 후 커맨드 창을 열어 해당 명령어를 입력하면, 자바 버전이 나오는데 본인이 설치한 버전과 다를 경우

해결 방법에 대해 안내합니다.

//명령어 입력
java -version

 

환경변수 설정
  1. 키보드 [Win+R] 단축키를 이용해 실행창을 연다. 
  2. [sysdm.cpl] 을 입력하고 [고급 탭]으로 이동한다.
  3. 환경변수 버튼을 클릭한다.
  4. 시스템 변수에 JAVA_HOME 의 경로가 정상적으로 잡혀 있는지 확인한다.
  5. 경로가 정상적이지 않다면 경로를 재설정 해준다.
  6. 시스템변수 -> Path의 경로를 추가한다 [ %JAVA_HOME%\bin ] ※bin까지 꼭 입력해야한다.
  7. 커맨드창을 열어 java -version으로 자바버전이 바뀌었는지 확인한다.
  8. 7번까지 진행하여도 자바 버전이 바뀌지 않았으면, 시스템변수 -> Path에 자바 경로가 다른것이 있는지 체크한다. 윈도우에서 java통합 설치를 진행하면 자동으로  C:\Program Files (x86)\Common Files\Oracle\Java|javapath 가 잡혀서 삭제 해주었다

감사합니다.

 

 

반응형

'BackEnd > Java' 카테고리의 다른 글

[Spring] Spring Security + jwt 사용  (0) 2022.07.23
[Thymeleaf] layout-dialect 사용하기  (0) 2022.07.22
SpringBoot MySQL & JPA 연동  (0) 2022.03.31
[IntelliJ] Lombok 설치 및 Lombok Annotation 정리  (0) 2022.03.16
IntelliJ 설치  (0) 2022.03.15
반응형
환경
  • 개발 Window11
  • Git
  • Github
  • Jenkins
  • RabbitMQ 3.9.12
  • Erlang 24.2
  • MySQL
설치

1. https://www.jetbrains.com/ko-kr/idea/download/#section=windows 링크로 이동하여 다운로드 합니다.

  1. Ultimate (라이센스 구독) 과 Community버전이 있으며 자신에게 맞는 버전을 이요하면 된다. (Ultimate버전은 30일 무료이다).

 

IntelliJ 설치화면

 

 

2. 설치경로 지정 후 [Next] 클릭

 

3. 설치 옵션 체크 후 [Next] 클릭

 

  1. Create Deskto Shortcut - 바로가기 생성
  2. Update Context Menu - 프로젝트로 폴더 열기 메뉴 추가
  3. Create Associations - 사용할 언어 확장자 선택
  4. Update PATH Variable - 윈도우 환경변수 자동 추가

[Next]로 넘기면 IntelliJ 설치가 완료 됩니다.

 

감사합니다.

 

 

 

 

반응형

+ Recent posts