TIL - 23주차 코드

 

1. JPA 기본 개념 : 230501

1) JPA과 ORM 기본 개념

  • ORM : DB와 서비스를 연결 시, 불필요한 작업을 줄여준다.

  • persistence layer로 어떤 것을 사용하십니까? : ORM을 이용한다.


a. 정리 :

  • Mybatis : DAO의 함수와 함수에서 구현된 쿼리문을 매핑해놓는 것이다. Mapper Tool만 작성하면, 알아서 매핑해주더라.


  • JPA : JPA는 Object와 Table을 매핑한다. 그러면 우리는 SQL문을 작성하지 않아도 된다. JPA가 DAO도 구현해준다. 또한, JPA가 Repository의 인터페이스도 구현해준다. 마지막으로, JPA가 쿼리문도 만들어준다.
    • 그래서 정말 편리하다. 하지만 불편한점도 매우 많다.



  • 따라서, JPA는 Object와 Relation(DB의 테이블)를 연결해주면, 모든 것을 알아서 다 연결해준다!

  • 하지만, JPA는 복잡한 SQL문을 사용할 수 없어서 추가적인 별도의 프레임워크가 필요하다. 예를 들면, QueryDSL 등등




2) JPA 개념

  • 기본적으로 통계에서는 테이블의 칼럼이 연결되는 Mapping이 필요하고 집계도 필요한다. 또한, 집계를 리듀싱(Reducing)이라고 부른다.

  • Java에서는 빅데이터를 이용하여 통계를 이용할 때는, Hadoop이라는 라이브러리가 추가적으로 필요했다.


a. JPA가 필요한 이유 :

  • JPA는 Java Persistence 기술과 관련과 통일감 있게 해주기 위해서 JPA가 필요하다!

  • 즉, 이전의 JDBC가 등장한 이유와 같다.

  • Hibernate를 사용하기 위해서 실제 구동하는 코드가 다 다르기 때문에 JPA를 사용한다.(JDBC를 사용하는 이유와 같다.)

  • 그래서, Spring Boot 프로젝트에서 JPA를 사용하기 위해서 Spring Add Starters에서 JPA 라이브러리를 추가하자!




3) JPA 정의 방법 :

  • @Entity라는 어노테이션을 이용하기만 하면 DB의 테이블과 연동된다.
  • jakarta.persistence라는 라이브러리 추가해주기!


  • Member.java

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Member {
    private int id;
    private String userName;
    private String password;
    private String email;
}



a. JPA 장점 :

  • 수정할 때, 매우 편리하다. 칼럼이 추가될 때, 편리하다!(원래 DB 칼럼 추가, Entity 추가, 서비스단 추가, 인자 추가 등 모든 과정들이 필요했다.)

  • JPA에서 기본 기능인 Insert, Delete, Getter, Setter 등등은 이미 구현되어 있다.

  • 아래 코드처럼 extends만 해주면 된다.( + Object명과 Integer 타입만 사용하기!)

  • 빈 객체에 담을 필요도 없다.(@Repository를 사용할 필요가 없다.)




a) 실습 코드 :


  • MemberRepository.java
public interface MemberRepository extends JpaRepository<Member, Integer>{

}


b) 실제 JPA Repository 구현 부분** :
  • 기본적인 SQL문에 관련된 함수들이 모든 것이 다 구현되어있다.


  • JpaRepository<T, ID>
/**
 * JPA specific extension of {@link org.springframework.data.repository.Repository}.
 *
 * @author Oliver Gierke
 * @author Christoph Strobl
 * @author Mark Paluch
 * @author Sander Krabbenborg
 * @author Jesse Wouters
 * @author Greg Turnquist
 * @author Jens Schauder
 */
@NoRepositoryBean
public interface JpaRepository<T, ID> extends ListCrudRepository<T, ID>, ListPagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {

	/**
	 * Flushes all pending changes to the database.
	 */
	void flush();

	/**
	 * Saves an entity and flushes changes instantly.
	 *
	 * @param entity entity to be saved. Must not be {@literal null}.
	 * @return the saved entity
	 */
	<S extends T> S saveAndFlush(S entity);

	/**
	 * Saves all entities and flushes changes instantly.
	 *
	 * @param entities entities to be saved. Must not be {@literal null}.
	 * @return the saved entities
	 * @since 2.5
	 */
	<S extends T> List<S> saveAllAndFlush(Iterable<S> entities);

	/**
	 * Deletes the given entities in a batch which means it will create a single query. This kind of operation leaves JPAs
	 * first level cache and the database out of sync. Consider flushing the {@link EntityManager} before calling this
	 * method.
	 *
	 * @param entities entities to be deleted. Must not be {@literal null}.
	 * @deprecated Use {@link #deleteAllInBatch(Iterable)} instead.
	 */
	@Deprecated
	default void deleteInBatch(Iterable<T> entities) {
		deleteAllInBatch(entities);
	}

	/**
	 * Deletes the given entities in a batch which means it will create a single query. This kind of operation leaves JPAs
	 * first level cache and the database out of sync. Consider flushing the {@link EntityManager} before calling this
	 * method.
	 *
	 * @param entities entities to be deleted. Must not be {@literal null}.
	 * @since 2.5
	 */
	void deleteAllInBatch(Iterable<T> entities);

	/**
	 * Deletes the entities identified by the given ids using a single query. This kind of operation leaves JPAs first
	 * level cache and the database out of sync. Consider flushing the {@link EntityManager} before calling this method.
	 *
	 * @param ids the ids of the entities to be deleted. Must not be {@literal null}.
	 * @since 2.5
	 */
	void deleteAllByIdInBatch(Iterable<ID> ids);

	/**
	 * Deletes all entities in a batch call.
	 */
	void deleteAllInBatch();

	/**
	 * Returns a reference to the entity with the given identifier. Depending on how the JPA persistence provider is
	 * implemented this is very likely to always return an instance and throw an
	 * {@link jakarta.persistence.EntityNotFoundException} on first access. Some of them will reject invalid identifiers
	 * immediately.
	 *
	 * @param id must not be {@literal null}.
	 * @return a reference to the entity with the given identifier.
	 * @see EntityManager#getReference(Class, Object) for details on when an exception is thrown.
	 * @deprecated use {@link JpaRepository#getReferenceById(ID)} instead.
	 */
	@Deprecated
	T getOne(ID id);

	/**
	 * Returns a reference to the entity with the given identifier. Depending on how the JPA persistence provider is
	 * implemented this is very likely to always return an instance and throw an
	 * {@link jakarta.persistence.EntityNotFoundException} on first access. Some of them will reject invalid identifiers
	 * immediately.
	 *
	 * @param id must not be {@literal null}.
	 * @return a reference to the entity with the given identifier.
	 * @see EntityManager#getReference(Class, Object) for details on when an exception is thrown.
	 * @deprecated use {@link JpaRepository#getReferenceById(ID)} instead.
	 * @since 2.5
	 */
	@Deprecated
	T getById(ID id);

	/**
	 * Returns a reference to the entity with the given identifier. Depending on how the JPA persistence provider is
	 * implemented this is very likely to always return an instance and throw an
	 * {@link jakarta.persistence.EntityNotFoundException} on first access. Some of them will reject invalid identifiers
	 * immediately.
	 *
	 * @param id must not be {@literal null}.
	 * @return a reference to the entity with the given identifier.
	 * @see EntityManager#getReference(Class, Object) for details on when an exception is thrown.
	 * @since 2.7
	 */
	T getReferenceById(ID id);

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example)
	 */
	@Override
	<S extends T> List<S> findAll(Example<S> example);

	@Override
	<S extends T> List<S> findAll(Example<S> example, Sort sort);
}



b. Mybatis 장점 :

  • Mybatis는 동적 쿼리가 필요했을 때, 매우 유용했다.

  • WHERE 절에 의한 Null 값 처리가 가능했다.




4) JPA 사용방법 :

  • Optional<Member> member = repository.findById(1);로 JPA에서 만들어주는 Repository 계층 이용하기



a. 실습 코드 :


  • DefaultMemberService.java

@Service
public class DefaultMemberService implements MemberService {

    @Autowired
    private MemberRepository repository;


    @Override
    public boolean isValid(String userName, String password) {
        Optional<Member> member = repository.findById(1);

        if(!member.isEmpty())
            System.out.println(member.toString());
    
        return false;
    }
    
}




5) JPQL

  • 내가 원하는 쿼리문을 만들 수 없다.

  • JPA가 제공해주는 새로운 명령어이다.

    • @Query("from member where username = :username")


a. JPA 주의 사항** :

  • 1) 소대문자 불일치로 인한 에러이다. 매개변수의 값이라서 똑같이 맞춰줘야 한다.

  • 2) Entity의 속성 이름과 인자의 맞춰주기!




b. 실습 코드 :

  • JPA를 사용하면, 순수 SQL이 아니라 다른 SQL인 JQL(JPQL)을 사용해야 한다.


  • MemberRepository.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import kr.co.rland.rlandapiboot3.entity.Member;

public interface MemberRepository extends JpaRepository<Member, Integer>{

    // JPQL : 초기 Mybatis와 같은 형식!이다. 복잡한 쿼리에서 사용한다. 지저분해진다.
    @Query("from member where username = :username")
    Member findByUserName(String string);
}




5-1) JPQL 문제점

  • ‘.NET’이라는 프레임워크에서는 ‘LINQ’를 이용하는데, LINQ에서 Q가 ‘Query’이다. .NET의 LINQ가 Stream API로 구현되어있다. Java에서 Stream API는 원래, 콜렉션의 정렬, 필터링 등등에 사용된다.

  • 그래서 QueryDSL를 엮어서 사용한다! 이것은 복잡해져 버린다.

  • 그래서 도저히 안되서 Java에서는 Native 속성을 집어 넣어버렸다. 원래는, 특정 DBMS에 종속되지 않도록 객체지향스럽게 개발이 되어야 한다.

  • 정리** : 특정 DBMS에 종속되는데는 Mybatis 사용하고 JPA를 사용하면, 순수 SQL이 아니라 다른 SQL인 JQL(JPQL)을 사용해야 한다.




6) JPA 추적하는 방법**

  • application.properties에서 Hibernate SQL문 추적하는 설정
logging.level.org.hibernat.SQL=DEBUG
spring.jpa.properties.hibernate.format_sql=true



7) Vue.js 로그인 방법

a. 실습 코드 :

  • Index.vue
<template>
    <header>
        <h1>index header</h1>
        <div>
            <ul>
                <li><router-link to="/index">home</router-link></li>
                <li><router-link to="/login">login</router-link></li>
                <li><router-link to="/admin/index">admin</router-link></li>
            </ul>
        </div>
    </header>
    <main>
        welcome page
    </main>
    <footer>index footer</footer>
</template>


  • 페이지 라우터 정리 :
    • main.js

import { createApp } from 'vue'
import { createRouter, createWebHashHistory } from 'vue-router'


import App from './App.vue'

// 이렇게 Router를 한번에 모아두는 것은 불편할 수도 있다.
// 페이지가 많아지면 Layout 이름이 다른것과 겹치지 않게  길어지기 때문이다.
const routes = [
    { path: '/index', component: Index},

    { path: '/', component: Layout, children:[
        { path: 'login', component: Login },
    ]},

    { path: '/admin', component: AdminLayout, children:[
        { path: 'menus', component: MenuLayout, childrem:[
            { path: 'list', component: MenuList}
            { path: 'detail', component: MenuDetail}
        ] },
    ]},
    
  ]
  
  // ** 글로벌 Vue 방식
//   const router = VueRouter.createRouter({
//     // 4. Provide the history implementation to use. We are using the hash history for simplicity here.
//     history: VueRouter.createWebHashHistory(),
//     routes, // short for `routes: routes`
//   })

    // ES6 방식 
  const router = createRouter({
    // 4. Provide the history implementation to use. We are using the hash history for simplicity here.
    history: createWebHashHistory(),
    routes, // short for `routes: routes`
  })

  // Vue.createApp이 글로벌 라이브러리이다.
createApp(App).mount('#app') // -> ES6 라이브러리이다.



2. Vue 인증과 권한 1 : 230503

1) Spring Security 설정

  • Vue.js에서 인증 기능을 구현하기 위해 먼저 SpringBoot에서 Spring Security를 먼저 구현하자!


2) Spring Security 기본 설정

  • 항상 필요한 부분!


  • RlandSecurityConfig.java
    • Spring Security 기본 설정 방법
    • build() 중요!!

@Configuration
public class RlandSecurityConfig {
    
    // 인증 '필터' 추가하기!!(수문장)
    // 1. 내가 원하는 URL만 보안 설정하자! 
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{

        // 2. 역할자에 따라서 권한을 부여해라!
        // 1) .csrf().disable() : cors 설정도 풀어줘야 한다    .
        http
        .csrf().disable()
        .authorizeHttpRequests(authorize->authorize
                .requestMatchers("/admin/**").hasAnyRole("ADMIN")
                .requestMatchers("/member/**").hasAnyRole("ADMIN","MEMBER")
                .anyRequest().permitAll());
        
        return http.build();
    }

    // 위에 설정까지만 하면 회원이 있는지 설정해줘야 한다!
    // 이렇게 여기서 설정해주거나 RlandUserDetailService에서 @Service로 설정해준다.
    // @Bean
    // public UserDetailsService userDetailsService(){

    //     return new RlandUserDetailService();
    // }
}



  • RlandUserDetails.java
    • Spring Security를 이용하기 위한 데이터 그릇 역할해주는 부분(Entity 느낌)

// Entity인 데이터를 담는 그릇 느낌이다. **

@AllArgsConstructor
@NoArgsConstructor
@ToString
@Builder
public class RlandUserDetails implements UserDetails {
    private int id;
    private String username;
    private String password;
    private String email;
    private List<GrantedAuthority> authorities;
    
    public void setId(int id) {
        this.id = id;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

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

    public void setAuthorities(List<GrantedAuthority> authorities) {
        this.authorities = authorities;
    }

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

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

    public int getId() {
        return id;
    }

    public String getEmail() {
        return email;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

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

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

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

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


}



  • RlandUserDetailService.java
    • Spring Security를 사용자가 만든 DB 정보로 이용하기 위한 과정
    • 스프링 시큐리티용 그릇을 인스턴스로 생성해서 기존 사용자가 만든 entity에 데이터 set 시키기
    • builder 패턴 이용해보기!

@Service
public class RlandUserDetailService implements UserDetailsService {

    @Autowired
    private MemberRepository repository;
    
    @Override
    public UserDetails loadUserByUsername(String username) {


        Member member = repository.findByUserName(username);
        List<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));

        // 스프링 시큐리티용 그릇을 인스턴스로 생성해서 기존 사용자가 만든 entity에 데이터 set 시키기 
        RlandUserDetails user = new RlandUserDetails();
        
        // 빌더 이용해보기!
        user.builder()
        .id(member.getId())
        .username(member.getUserName())
        .password(member.getPwd())
        .email(member.getEmail())
        .authorities(authorities)
        .build();

        return user;
    }
    
}



3) Security 설정을 사용하는 부분

a. 사용자가 구현한 서비스에서 Spring Security 설정을 이용하기


  • MemberService.java

public interface MemberService {

    boolean isValid(String userName, String password);
    
}



  • DefaultMemberService.java

@Service
public class DefaultMemberService implements MemberService{

    @Autowired
    private MemberRepository repository;

    @Override
    public boolean isValid(String userName, String password) {
        // Optional<Member> member= repository.findById(1);

        // 암호화시 인코더 필요! 암호화를 안하면, 평문으로 비밀번호가 DB에 insert
        // new BcryptPasswordEncoder().encode();

       Member member= repository.findByUserName(userName);
    //    System.out.println(member.toString());
        
        if(member == null)
           return false;
        else if(!BCrypt.checkpw(password, member.getPwd()))
            return false;
        
        
        return true;
    }
    
}     



  • MemberController.java

@RestController
@RequestMapping("members") 
public class MemberController {

    @Autowired
    private MemberService service;

    @GetMapping("isvalid")
    public ResponseEntity<Map<String, Object>> isValid(String userName, String password){

        Map<String, Object> dto = new HashMap<>();

        dto.put("result",false);

        // ** DB 이용시 사용하기! **
        if(service.isValid(userName, password))
            dto.put("result", true);

        return new ResponseEntity<Map<String, Object>>(dto, HttpStatus.OK);
    }

}




3. Vue 인증과 권한 2 : 230504

  • Vue.js에서 Spring Security 적용하기 위한 부분

1) 기존의 Spring Security 부분 수정하기


a. 실습코드

  • MemberController.java
    • 수정 사항 : @postMapping으로 변경
    • 메서드의 인자명 주의!
    • 메서드명 변경하기

@RestController
@RequestMapping("members") 
public class MemberController {

    @Autowired
    private MemberService service;

    @PostMapping("login")
    public ResponseEntity<Map<String, Object>> login(
        String username, 
        String password){

        System.out.println(username);
        Map<String, Object> dto = new HashMap<>();

        dto.put("result",null);

        // ** DB 이용시 사용하기! **
        if(service.isValid(username, password)){
            Member member = service.getUsername(username);
            dto.put("result", member);
        }

        return new ResponseEntity<Map<String, Object>>(dto, HttpStatus.OK);
    }
}



  • MemberService.java

public interface MemberService {

    boolean isValid(String userName, String password);

    Member getUsername(String username);
    
}



  • DefaultMemberService.java

@Service
public class DefaultMemberService implements MemberService{

    @Autowired
    private MemberRepository repository;

    @Override
    public boolean isValid(String userName, String password) {
        // Optional<Member> member= repository.findById(1);

        // 암호화시 인코더 필요! 암호화를 안하면, 평문으로 비밀번호가 DB에 insert
        // new BcryptPasswordEncoder().encode();

       Member member= repository.findByUserName(userName);
    //    System.out.println(member.toString());
        
        if(member == null)
           return false;
        else if(!BCrypt.checkpw(password, member.getPwd()))
            return false;
        
        
        return true;
    }

    @Override
    public Member getUsername(String username) {
        return repository.findByUserName(username);
    }
    
}

       



2) Vue.js에서 전역변수 사용하는 과정

a. 전역변수 테스트

  • .js 파일에 모듈을 하나 만들면 다른 페이지에서도 import해서 전역변수처럼 사용할 수 있는지 테스트 해보자



a) 실습코드 :
  • UserDetails.js
    • 여기서 모듈 생성
// export default {}는 객체를 반환한다.
export default {
    username:"",
    password:"",
    email:"",
    role:""
}




  • Login.vue
    • 앞서 생성된 모듈을 import하여 전역변수에 인자값 담기
<script setup>
import {reactive} from 'vue';

// 전역 변수를 선언! store에서 가져온다.
// 하나의 모듈에다가 만들어서 공유하면 전역변수가 된다. 
// 모두다 같은 객체를 공유하는지가 궁금하다! 그래서, 여기서 담아서 다른페이지에서 읽어보자!!
import userDetails from '../store/UserDetails.js';


let user = reactive({
  username:"newlec",
  password:"111",
  role:""
});

async function logHandler(){
  console.log(user.username);
  let response = await fetch("http://localhost:8080/members/login",{
    method:"POST",
    headers:{
      "Accept":"application/json",
      "Content-type":"application/x-www-form-urlencoded"
    },
    body:`username=${user.username}&password=${user.password}`
  });
  let json = await response.json();
  console.log(json);

  // 로그인하고 나서 전역 객체(global object)에 로그인한 인증 정보를 담기
  // 자바의 entity 값과 같게 반환해줘야 한다.
  userDetails.username = json.result.userName;
  userDetails.password = json.result.pwd;
  userDetails.email = json.result.email;
}
</script>

<template>
    <main>
      <div class="sign-in">
        <div class="sign-in-logo">
          <img src="/image/logo-black.svg" alt="Rland" />
        </div>
        <form class="sign-in-form">
          <div class="sign-in-form-input">
            <div>
              <input
                type="text"
                class="input-bottom-line"
                placeholder="아이디"
                required
                v-model="user.username"
              />
            </div>
            <div>
              <input
                type="password"
                class="input-bottom-line"
                placeholder="비밀번호"
                required
                v-model="user.password"
              />
            </div>
          </div>

          <div class="sign-in-form-button">
            <div class="wd-100">
              <input type="submit" value="로그인" class="btn btn-default" @click.prevent="logHandler"/>
            </div>
            <div class="font-14">또는</div>
            <div class="wd-100">
              <a href="" class="deco icon-logo-google btn btn-outline"
                >구글로 로그인</a
              >
            </div>
          </div>
        </form>
        <div class="sign-in-find-user font-14">
          <a href="/sign-up.html">회원가입</a> |
          <a href="">비밀번호 찾기</a>
        </div>
      </div>
    </main>

</template>

<style scoped>
    @import url("/css/sign-in.css")
</style>



  • Index.vue
    • Login.vue에서 담긴 인자 값을 다시 꺼내서 읽혀지는지 확인하기
    • 이렇게 하는 이유는 전역변수로 공유되는지 테스트하는 과정이다.
<script setup>
  import userDetails from '../store/UserDetails';
</script>

<template>
    <!-- ----------------------헤더---------------------------- -->


    <!-- 메인 -->
    <main>
      <!-- 상단 환영인사&이미지 -->
      <section class="greeting">
        <div class="greeting-inner">
          <!-- 1 -->
          <div>
            <h1>알랜드에 오신 것을 환영합니다.<span></span></h1>
            <h2>부안에 오면  들리는 카페</h2>
            <div>
              <a href="/menu/list.html" class="btn btn-default">주문하기  <span></span></a>
            </div>
          </div>
          <!-- 2 -->
          <div>
            <img src="/image/main-top.png" alt="Rland" />
          </div>
          <!-- 3 -->
          <div>
            <div>Rland Coffee</div>
            <div>
              <img src="/image/logo-badge.svg" alt="Rland" />
            </div>
          </div>
        </div>
      </section>

      <!-- rland를 추천합니다 -->
      <section class="recommend-rland">
        <h1>알랜드만의 특별함</h1>
        <section class="recommend-rland-fruit">
          <div>
            <img src="/image/main-fruit.png" alt="과일청" />
          </div>
          <h1>직접 만든 <strong>과일청</strong>을 맛보세요.</h1>
          <p>
            신선한 과일과 알랜드만의 레시피로 과일향의 풍미를 충분히 느낄 
            있는 수제청을 드셔보세요.
          </p>
        </section>
        <section class="recommend-rland-cookie">
          <div>
            <img src="/image/main-cookie.png" alt="수제 쿠키" />
          </div>
          <h1>우리가 구운 <strong>수제 쿠키</strong>를 만나보세요.</h1>
          <p>
            신선한 버터 그리고 견과류를 이용하여 바삭함을 더해 매일마다 직접
            구운 맛난 쿠키를 만나보세요.
          </p>
        </section>
        <section class="recommend-rland-roasting">
          <div>
            <img src="/image/main-roasting.png" alt="로스팅" />
          </div>
          <h1>다양한 <strong>로스팅</strong>으로 다채로운 맛을 느껴보세요.</h1>
          <p>
            신선한 과일과 알랜드만의 레시피로 과일향의 풍미를 충분히 느낄 
            있는 수제청을 드셔보세요.
          </p>
        </section>
      </section>

      <!-- 명소 찾기 -->
      <section class="tourist-spot">
        <h1>알랜드 주변의 명소를 찾아보세요.</h1>
      </section>
      <div class="recommend-coffe-spot">
        알랜드 주변의 30곳이 넘는 힐링 장소에서 맛나는 커피와 경치로 힐링을
        해보세요.
      </div>

      <!-- --------------------------------------------------------------------footer -->


    </main>
</template>



b. Vue.js에서 CORS 문제 해결 방법

  • 하지만 위의 과정을 하면, Vue.js에서 AJAX를 통해 DB 데이터를 가져오는 과정이 Spring Boot의 서버인 톰캣 서버를 이용해야하므로 CORS 설정을 해줘야 한다.


  • CORSConfig.java
package kr.co.rland.rlandapiboot3.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CORSConfig {
    
    @Bean
    WebMvcConfigurer webMvcConfigurer(){
        WebMvcConfigurer configurer = new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**").allowedOrigins("*");
            }
        };
        return configurer;
    } 
}




3) Vue.js에서 전역변수를 이용하여 로그인하는 과정

  • 문제점 발생** : 로그인하고나서 새로고침이 되어도 상태값이 유지되어야 한다. 하지만, 이런 상황에서는 로그인 상태값을 유지하지 못한다.


a. 순수 전역변수를 이용하여 상태값 유지하는 실습 과정 1 :

  • v-show는 DOM이 있지만 보이지만 않는다. v-if는 DOM이 아예 없어서 보이지 않는다.**

  • 현재 상황에서는 페이지 리로드되어도 상태값 유지 불가능하다.



a) 정리


b) 실습 코드:
  • 단순히 import userDetails from '../store/UserDetails.js'; 해당 import로 store에 저장된 모듈을 전역 변수로 사용할 수 있다.


  • Header.vue

<script setup>
  import userDetails from '../../store/UserDetails';
</script>
<template>
    <header class="header-container">
      <h1 class="d-none">알랜드</h1>
      <div>
        <router-link to="/index" title="index페이지로 이동합니다.">
          <img src="/image/logo.svg" />
        </router-link>
        <a class="icon icon-menu">메뉴</a>
      </div>
      <nav class="nav-wrap">
        <h1 class="d-none">네비게이션 목록</h1>
        <ul>
          <li>
            <a
              href="/index"
              class="icon icon-home"
              title="index페이지로 이동합니다."
              ></a
            >
          </li>
          <li>      <!-- v-show는 DOM이 있지만 보이지만 않는다. v-if는 DOM이 아예없어서 보이지 않는다. -->
            <router-link v-if="userDetails.username == ''" to="/login" class="icon icon-sign-on" title="로그인">로그인</router-link>
            <router-link v-if="userDetails.username != ''" to="/logout" class="icon icon-sign-out" title="로그아웃">로그아웃</router-link>
          </li>
        </ul>
      </nav>
    </header>
</template>



b. 기존의 로그인 설정 코드 :

  • Login.vue
<script setup>
import {reactive} from 'vue';

// 전역 변수를 선언! store에서 가져온다.
// 하나의 모듈에다가 만들어서 공유하면 전역변수가 된다. 
// 모두다 같은 객체를 공유하는지가 궁금하다! 그래서, 여기서 담아서 다른페이지에서 읽어보자!!
import userDetails from '../store/UserDetails.js';


let user = reactive({
  username:"newlec",
  password:"111",
  role:""
});

async function logHandler(){
  console.log(user.username);
  let response = await fetch("http://localhost:8080/members/login",{
    method:"POST",
    headers:{
      "Accept":"application/json",
      "Content-type":"application/x-www-form-urlencoded"
    },
    body:`username=${user.username}&password=${user.password}`
  });
  let json = await response.json();
  console.log(json);

  // 로그인하고 나서 전역 객체(global object)에 로그인한 인증 정보를 담기
  // 자바의 entity 값과 같게 반환해줘야 한다.
  userDetails.username = json.result.userName;
  userDetails.password = json.result.pwd;
  userDetails.email = json.result.email;
}
</script>

<template>
    <main>
      <div class="sign-in">
        <div class="sign-in-logo">
          <img src="/image/logo-black.svg" alt="Rland" />
        </div>
        <form class="sign-in-form">
          <div class="sign-in-form-input">
            <div>
              <input
                type="text"
                class="input-bottom-line"
                placeholder="아이디"
                required
                v-model="user.username"
              />
            </div>
            <div>
              <input
                type="password"
                class="input-bottom-line"
                placeholder="비밀번호"
                required
                v-model="user.password"
              />
            </div>
          </div>

          <div class="sign-in-form-button">
            <div class="wd-100">
              <input type="submit" value="로그인" class="btn btn-default" @click.prevent="logHandler"/>
            </div>
            <div class="font-14">또는</div>
            <div class="wd-100">
              <a href="" class="deco icon-logo-google btn btn-outline"
                >구글로 로그인</a
              >
            </div>
          </div>
        </form>
        <div class="sign-in-find-user font-14">
          <a href="/sign-up.html">회원가입</a> |
          <a href="">비밀번호 찾기</a>
        </div>
      </div>
    </main>

</template>

<style scoped>
    @import url("/css/sign-in.css")
</style>