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) 정리
-
원하는 방향은 Login.vue에서 로그인을 하더라도 로그아웃 아이콘만 보여야 한다. 하지만, 지금은 로그인 아이콘만 보인다.
- 왜 그럴까? 아직은 Vue.js에서 상태값 유지가 되지 않기 때문이다. 그래서 새로고침되어도 상태가 유지되지 않는다.
- 이것은 전역변수가 Reactive하지 않기 때문이다.
-
기존에는 Vue2인
Options API
에서는Vuex
를 사용했었다. 이제는 Vue3에서Composition API
와Options API
에서 사용가능한Pinia
를 이용해보자! - Pinia 참고 레퍼런스 3 : Pinia 사용 방법
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>