TIL - 25주차 코드

 

1. 230515

1) JPA

  • AJAX로 모든 프론트단을 설계하고 도배질하려면, JPA로 구현하면 된다. 쿼리가 길어진다면, 무조건 View 테이블로 만들어서 Join도 최대한 줄이고 동적쿼리도 줄일것

  • 하지만, JOIN을 많이 하려면, Mybatis를 이용해야 한다.

  • nativeQuery로 설정을 해주면, Mybatis나 MariaDB로 변환되는 것인데 JPA를 쓰는 것은 nativeQuery를 안 된다.




2) 구글 로그인 아이콘 변경하는 방법 :

  • 현재, 기존 Google 로그인 버튼의 코드에서 아이콘을 추가하면, 에러가 발생한다.

  • 우리는 사용자가 만든 custom 버튼으로 설정 해야 한다.

  • 즉, 구글 로그인할 때, auth(코드 기반)나 token(토큰 기반)인지 판단해야한다.

  • 구글 로그인에서 ‘popup-type’이나 ‘prompt’ 설정이 필요하다.(옵션)

    • ‘popup-type’은 토큰으로 쓸지 코드 기반으로 쓸지 설정해준다.



3) 구글 로그인 아이콘 변경하는 방법 실습 :

a. 구글 로그인 실습 코드

  • popup-type 중요!!


  • 구글 로그인에서 ‘popup-type’이나 ‘prompt’ 설정이 필요하다.(옵션)
    • ‘popup-type’은 토큰으로 쓸지 코드 기반으로 쓸지 설정해준다.
function customLoginHandler(response){
  // console.log("hihihi");
    fetch(`https://www.googleapis.com/oauth2/v3/userinfo?access_token=${response.access_token}`)
    .then(res=>res.json())
    .then(credential=>{
      console.log(credential);

      // **전역 객체(global object)에 인증 정보를 담기**
      // userDetails.username = userData.name;
      // userDetails.email = userData.email;
      // userDetails.roles = ["ADMIn","MEMBER"];

      router.push("/signupWithGoogle");
    })
    .catch(e=>{
      console.log("error");
    });
}


    <div class="wd-100">
      <GoogleLogin :callback="customLoginHandler" class="wd-100" popup-type="TOKEN">
        <span class="deco icon-logo-google btn btn-outline">구글로 로그인</span>
      </GoogleLogin>
    </div>


  • 구글 로그인 전체 실습 코드
<script setup>
import { reactive } from 'vue';
import { useRouter, useRoute } from 'vue-router';
// 전역변수가 필요한데 어떻게 하지? =>모듈만들어서 붙인다 
// import userDetails from '../stores/UserDeatails.js';
import { useUserDetailsStore } from '../stores/useUserDetailsStore';
import { decodeCredential } from 'vue3-google-login'


let userDetails = useUserDetailsStore();
let router = useRouter();
let route = useRoute();

let user = reactive({
  username:"",
  password:"",
  roles:"",

})

// async, await로 따로 fetch 하지 않고 한 번에 fetch 하자!
// 예전에는 상태를 유지하는 stateful 방식이었다.
// 하지만, 요즘에는 상태를 유지하지 않고 stateless 방식이다.
function customLoginHandler(response){
  // console.log("hihihi");
    fetch(`https://www.googleapis.com/oauth2/v3/userinfo?access_token=${response.access_token}`)
    .then(res=>res.json())
    .then(credential=>{
      console.log(credential);

      // **전역 객체(global object)에 인증 정보를 담기**
      // userDetails.username = userData.name;
      // userDetails.email = userData.email;
      // userDetails.roles = ["ADMIn","MEMBER"];

      router.push("/signupWithGoogle");
    })
    .catch(e=>{
      console.log("error");
    });
}

function googleLoginHandler(response){
  let userData = decodeCredential(response.credential);

  console.log(userData);
  userDetails.username = userData.name;
  userDetails.email = userData.email;
  userDetails.roles=["ADMIN","MEMBER"];


  let returnURL = route.query.returnURL;
  
  if(returnURL)
    router.push(returnURL);
  else
    router.push("/index");

  
}

async function loginHandler(){
  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)
  //console.log(json.result.email);
  //전역 객체(global object)에 인증 정보를 담기
  userDetails.username = json.result.userName;
  userDetails.email = json.result.email;
  userDetails.roles=json.roles;


  let returnURL = route.query.returnURL;
  
  if(returnURL)
    router.push(returnURL);
  else
    router.push("/index");
}

</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="loginHandler" />
            </div>
            <div class="font-14">또는</div>
            <div class="wd-100">
              <!-- <a href="" class="deco icon-logo-google btn btn-outline"
                >구글로 로그인</a
              > -->
              <GoogleLogin :callback="customLoginHandler" class="wd-100" popup-type="TOKEN">
                <span class="deco icon-logo-google btn btn-outline">구글로 로그인</span>
              </GoogleLogin>
            </div>
            <div>
              <GoogleLogin :callback="googleLoginHandler" />
            </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");
    main{

    }
</style>



4. 백단에서 구글 로그인

a. 라이브리 추가

  • 타임리프 라이브러리 추가

  • OAuth2 Client 라이브러리 추가




b. 기존의 DefaultService(DB 썼던 것) 대신에 구글 사용자 정보로 추가해주기

  • SecurityConfig로부터 사용자 DB용 서비스를 구글 로그인용 서비스로 변경해주기
package kr.co.rland.rlandapiboot3.auth;

import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;


@Service
public class GoogleUserDetailService extends DefaultOAuth2UserService{
    
    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        
        // 유저 정보를 바로 반환하지 말고 잠시 담았다가 반환해주기!
        OAuth2User user = super.loadUser(userRequest)
        
        System.out.println(user.getAttribute());

        return user;
    }
}




c. application.properties에 사용자 정보 간단히 추가


logging.level.org.hibernat.SQL=DEBUG
spring.jpa.properties.hibernate.format_sql=true

spring.security.oauth2.client.registration.google.client-id=
spring.security.oauth2.client.registration.google.client-secret=




d. 타임리프에서 템플릿 설정 추가(기본 템플릿 구현)

  • index.html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
   <h1>로그인 페이지</h1>
   <div>
        <a href="">구글 로그인</a>
   </div>
</body>
</html>


  • user/login.html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
   <h1>Login Page</h1>
</body>
</html>


  • member/index.html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
   <h1>로그인 페이지</h1>
   <div>
        <a href="">구글 로그인</a>
   </div>
</body>
</html>


  • HomeController.java

@Controller
@RequestMapping("/")
public class HomeController {
    
    @GetMapping("index")
    public String index(){
        return "index";
    }
    
}


  • MemberController.java

@Controller
@RequestMapping("member") 
public class MemberController {

    // @Autowired
    // private MemberService service;

    @GetMapping("index")
    public String index(){
        return "member/index";
    }

}


  • UserController.java

@Controller
@RequestMapping("user")
public class UserController {
    
    @GetMapping("login")
    public String login(){

        return "user/login";
    }   
}




e. SpringSecurity Config에서 Oauth2 추가 설정

a) console.cloud.google.com에서 SpringBoot용 Authorized Redirect URIs 설정 추가
http://localhost:8080/login/oauth2/code/google


b) .oauth2Login();를 추가로 설정!
package kr.co.rland.rlandapiboot3.auth;

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.core.userdetails.UserDetailsService;
import org.springframework.security.web.SecurityFilterChain;

@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())
        .oauth2Login();

        return http.build();
    }

    // 위에 설정까지만 하면 회원이 있는지 설정해줘야 한다!
    // @Bean
    // public UserDetailsService userDetailsService(){

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




e. 타임리프 템플릿 추가 설정!

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
   <h1>로그인 페이지</h1>
   <div>
        <a href="/oauth2/authorization/google">구글 로그인</a>
        <!-- 처음에는 access 에러가 발생 -->
    </div>
</body>
</html>

5. 로그아웃 하기

a. Config 추가 설정

.and()
.logout()
    .logoutUrl("/user/logout")
    .logoutSuccessUrl("/index");
a) 실습 코드


  • SecurityConfig.java
package kr.co.rland.rlandapiboot3.auth;

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.web.SecurityFilterChain;

@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())
        .oauth2Login()
        .and()
        .logout()
            .logoutUrl("/user/logout")
            .logoutSuccessUrl("/index");

        return http.build();
    }

    // 위에 설정까지만 하면 회원이 있는지 설정해줘야 한다!
    // @Bean
    // public UserDetailsService userDetailsService(){

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


  • index.html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
   <h1>로그인 페이지</h1>
   <div>
       <!-- 처음에는 access 에러가 발생 -->
        <a href="/oauth2/authorization/google">구글 로그인</a>
        <a href="/user/logout">로그아웃</a>
    </div>
</body>
</html>


  • user.login.html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
   <h1>Login Page</h1>

   <div>
    <!-- 처음에는 access 에러가 발생 -->
     <a href="/oauth2/authorization/google">구글 로그인</a>
     <a href="/user/logout">로그아웃</a>
 </div>
</body>
</html>



6) 사용자가 만든 폼으로 구글 로그인 설정 1


.formLogin()    // 사용자가 만든 폼 형태로 로그인하기!
    .loginPage("/user/login")    // GET (사용자에게 문서를 준다.)
    .loginProcessingUrl("/user/login") // 사용자가 POST 요청!!
    .defaultSuccessUrl("/index")    // 로그인 성공시 이동하는 URL!!
.and()

a. 폼 관련 실습 코드 :

package kr.co.rland.rlandapiboot3.auth;

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.web.SecurityFilterChain;

@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())
        .formLogin()    // 사용자가 만든 폼 형태로 로그인하기!
            .loginPage("/user/login")    // GET (사용자에게 문서를 준다.)
            .loginProcessingUrl("/user/login") // 사용자가 POST 요청!!
            .defaultSuccessUrl("/index")    // 로그인 성공시 이동하는 URL!!
        .and()
        .oauth2Login()
        .and()
        .logout()
            .logoutUrl("/user/logout")
            .logoutSuccessUrl("/index");

        return http.build();
    }

    // 위에 설정까지만 하면 회원이 있는지 설정해줘야 한다!
    // @Bean
    // public UserDetailsService userDetailsService(){

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





7) 사용자가 만든 폼으로 구글 로그인 설정 2

  • 구글 로그인에는 아까 이전에 auth(코드 기반)나 token(토큰 기반)기반 처럼 openId 형식, Autherized 형식이 있다.

  • userInfoEndpoint가 사용자 정보를 전달해주면, userService가 사용자 정보를 받는다!

.oauth2Login()
    .userInfoEndpoint() // userInfoEndpoint가 사용자 정보를 전달해주면,
    .userService(googleUserDetailService); // userService가 사용자 정보를 받는다!

a. 실습 코드

package kr.co.rland.rlandapiboot3.auth;

import org.springframework.beans.factory.annotation.Autowired;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class RlandSecurityConfig {
    
    @Autowired
    private GoogleUserDetailService googleUserDetailService;

    // 인증 '필터' 추가하기!!(수문장)
    // 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())
        .formLogin()    // 사용자가 만든 폼 형태로 로그인하기!
            .loginPage("/user/login")    // GET (사용자에게 문서를 준다.)
            .loginProcessingUrl("/user/login") // 사용자가 POST 요청!!
            .defaultSuccessUrl("/index")    // 로그인 성공시 이동하는 URL!!
        .and()
        .logout()
            .logoutUrl("/user/logout")
            .logoutSuccessUrl("/index")
        .and()
        .oauth2Login()
            .userInfoEndpoint() // userInfoEndpoint가 사용자 정보를 전달해주면,
            .userService(googleUserDetailService); // userService가 사용자 정보를 받는다!
        
        

        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    // 위에 설정까지만 하면 회원이 있는지 설정해줘야 한다!
    // @Bean
    // public UserDetailsService userDetailsService(){

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





3. JDK 8, 리눅스 시작 : 230517

1) JAVA 8

  • lambda, functional interface, stream API, optional class 등등

  • 이것은 ‘콜렉션의 Data 분석 기능’을 위해서 존재하는 기능들이다.

  • Arrays : 정적인 변수이다.

5) 전체 코드 :

  • Program.java
package main.java.ex1.sort;

import java.util.Arrays;

public class Program{
    public static void main(String[] args){

        // ['객체 정렬']

        // ********* 1. 숫자형 배열 정렬 ***************
        // compare는 '객체 정렬'이라서 int 타입이 아니라 Integer 타입으로 사용할 것!
        Integer[] ages = {10,30,3,34,53,23,40};
        
        // Arrays.sort(ages);   // 오름차순 정렬

        // 역정렬은 Compare 연산을 이용해야해서 원래 어렵다. 이를 쉽게 해주는 것이 람다 표현식이다. 
        
        Arrays.sort(ages,(a,b)->a-b);   // 기본(a-b) : 오름차순 정렬
        // b-a : 내림차순 정렬(음수가 결과니까 자리를 바꿔서 내림차순이 된다.)

        System.out.println(Arrays.toString(ages));

        // ********* 2. 문자열 배열 정렬 ***************
        
        String[] stringArray = { "Barbara", "James", "Mary", "John", "Patricia", "Robert", "Michael", "Linda" };
        
        // Arrays.sort(stringArray,(a,b)->a-b);
        
        // 문자열은 비교를 할 수 없어서 문자열을 비교하기 위해서 compareTo를 이용하자!*****
        Arrays.sort(stringArray, (o1, o2) -> o1.compareTo((o2)));   // 오름차순
        Arrays.sort(stringArray, (o1, o2) -> o2.compareTo((o1)));   // 내림차순
        

        // ********* 3. Method Reference ***************

        // 이를 더 간단히 해주기 위해서 compareTo 메서드만 사용하자!
        // 하지만, 어디서 선언되었는지 몰라서 이것은 'String::'로 타입을 선언해주자!
        // 이러한 것을 'Method Reference'라고 부른다. 하지만, 이것은 오름차순인 기본형태 정렬만 가능하다! 
        // Arrays.sort(stringArray, (o1, o2) -> o2.compareTo((o1)));   // 내림차순
        Arrays.sort(stringArray, String::compareTo);   // 기본 : 오름차순

        System.out.println(Arrays.toString(ages));

        // ***** 이러한 모든 것은 Stream API를 위해서 등장했다.
        
        // ********* 4. 인터페이스 개념 ***************
        // ** 인터페이스 개념 : 1. 접점이 인터페이스이다. ex) 인터넷 : 네트워크와 네트워크 이어주기,
        // 2. Java에서 부품(모듈을)을 갈아끼는 형태이다. 기능을 바꿔낀다. 즉, 다양한 성질을 갖게 된다. 이것을 '다형성'이라고 부른다.
        // 3. Java에서 코드 상에서 인터페이스는 ~~이다.

        // ******** 5. '람다 표현식'의 등장 배경 *************
        // ** 위의 개념을 추가로 데이터 분석에서 많이 사용된다.(파이썬 같은 곳) 
        // 즉, 람다 표현식은 수식(데이터 분석에서)에서 많이 사용된다. 람다 표현식은 함수 1개만 적용된 것에서만 사용된다. 
        // 그래서, 메서드가 여러개 인경우 쓸 수가 없던 것이었다.**** 
        

        // interface Comparable<T>는 저기서 구현할 수 없어서 여기서 익명 클래스로 구현된다!!
        // new Comparable<Integer>() {

        //     @Override
        //     public int compareTo(Integer o) {

        //         return 0;
        //     }
            
        // };

        // **** 원래 형태!!
        // Comparable<Integer> comp1 = new Comparable<T>() {

        //     @Override
        //     public int compareTo(Integer o) {

        //         return 0;
        //     }
            
        // };

        // 내가 푼 것 : 'Integer o' 인자가 필요하다!
        Comparable<Integer> comp0 = () -> {
            return 0;
        };


        // 매개변수와 몸통만 있으면된다.!(함수명만 지우면 된다.)
        Comparable<Integer> comp1 = (Integer o) -> {
            return 0;
        };

        // 훨씬더 간단한 형태!
        // 매개변수와 몸통만 있으면된다.!
        Comparable<Integer> comp2 = o -> 0;


        // **************** 6. Object ****************

    }
}



  • Comparable.java
package main.java.ex1.sort;

@FunctionalInterface
public interface Comparable<T> {
    int compareTo(T o);
    // 하지만 함수를 하나 더 추가하면, 람다 표현식을 사용할 수 없다!!
    // int test();

    // *** [Funtional Interface] 개념 ***
    // 자바에서는 함수를 전달하는 기능이 이전에는 없었다.
    // (무조건 자바에서는 클래스나 인터페이스가 필요하다!)
    // @FunctionalInterface 선언하면 된다. 람다식은 여기서만 사용된다!
    
    // [함수형 인터페이스 추가 내용]
    // 인터페이스는 함수형 인터페이스입니다.
    // default method, static method 를 넣어도 문제 없습니다.
    // 어차피 함수형 인터페이스 형식에 맞지 않는다면 @FunctionalInterface 이 다음 에러를 띄워줍니다.


}


6) 리눅스

a. OS 개념, 소프트웨어 개념

  • OS는 실행환경이고 우리는 OS 위에 올려져 있는 소프트웨어를 사용한다.

  • 이러한 소프트웨어인 톰캣이나 mariaDB는 리눅스에서 개발된다.



b. MacOS, Windows, Linux 관계

  • Mac은 서버용 OS가 아니다.

  • MacOS는 기반이 UNIX이다. 그래픽만 이용한 것이 MacOs이다.

  • UNIX는 보통 console만 사용된다.

  • UNIX는 에뮬레이터가 필요 없이 톰캣, Oracle, Java JDK 등을 사용할 수 있다.

  • Windows에서는 서버를 돌릴 수 없다. 리소스가 계속 증가해서 재부팅이 필요하다. 하지만, 서버는 안정적인 것이 필요해서 Windows를 사용할 수 없다.

    • 리눅스는 안정적이고 여러 사람들에 의해 만들어진 것이며 무료이다.


c. Linux 설치하기

  • 보통, 요즘에는 Debian 계열의 Ubuntu 사용!

  • virtual box 이용하기!!




4. Stream API, 리눅스 설치 : 230518

1) Stream API 등장 배경 : JDK 8 미만

  • 기본적으로 객체 정렬을 다음과 같이 진행된다.

  • Comparable로부터 implements를 해서 구현해줘야 한다.




a. 실습 코드

  • Program.java
package main.java.ex1.sort;

import java.util.Arrays;

public class Program{
    public static void main(String[] args){

        Exam[] list = {
            new Exam(1,2,3),
            new Exam(2,3,4),
            new Exam(3,2,1)
        };

        Arrays.sort(list);
        System.out.println(Arrays.toString(list));
	}
}
        


  • Exam.java

package main.java.ex1.sort;


public class Exam implements Comparable<Exam> {
    private int kor;
    private int eng;
    private int math;
    
    public Exam() {
    }
 
    public Exam(int kor, int eng, int math) {
       super();
       this.kor = kor;
       this.eng = eng;
       this.math = math;
    }
 
    public int getKor() {
       return kor;
    }
 
    public void setKor(int kor) {
       this.kor = kor;
    }
 
    public int getEng() {
       return eng;
    }
 
    public void setEng(int eng) {
       this.eng = eng;
    }
 
    public int getMath() {
       return math;
    }
 
    public void setMath(int math) {
       this.math = math;
    }
    
    public int total() {
       return kor+eng+math;
    }
 
    @Override
    public String toString() {
       return "Exam [kor=" + kor + ", eng=" + eng + ", math=" + math + "]";
    }
    
    @Override
    public int compareTo(Exam exam){
      return this.total() - exam.total();
    }
 }



2) Stream API 개념

  • 하지만, JDK 8이상의 환경에서는 객체의 정렬을 1)처럼 사용하지 말고 Stream API를 사용한다.

  • 엘리먼트(배열이나 콜렉션)을 스트림에다가 functional style(함수의 모습으로)로 결합한다.

  • Stream 객체를 사용하여 filtering, Mapping, Sorting 과정을 거쳐서 정렬된다.

  • 굉장히 객체의 분석이나 정렬이 쉬워진다.




a. 기본 형식

  • 아래 Stream API 코드는 함수 모습처럼 보이지만 실제는 함수가 아니라 객체를 꽂아 넣는 것이라서 ‘functional-style 프로그래밍’이라고 부른다.

  • int 형식을 Stream에서 사용하기 위해서 IntStream를 사용한다.

  • 이제는, a. 코드에 다른 함수를 꽂아 넣자!

package main.java.ex1.sort;

import java.util.Arrays;
import java.util.stream.IntStream;

public class Program{
    public static void main(String[] args){

        int[] ages = {10,32,34,21,23,25,46};
        int[] result = IntStream
                            .of(ages)
                            .toArray();

        System.out.println(Arrays.toString(result));
    }
}



b. 응용 형식 1 : 조건문 추가

package main.java.ex1.sort;

import java.util.Arrays;
import java.util.stream.IntStream;

public class Program{
    public static void main(String[] args){

        int[] ages = {10,32,34,21,23,25,46};
        int[] result = IntStream
                            .of(ages)
                            .filter(age->age>25)
                            .toArray();
                            
        System.out.println(Arrays.toString(result)); // 32, 34, 46 순서로 출력
    }
}



c. 응용 형식 2 : 정렬 추가

  • 굉장히 객체의 분석이나 정렬이 쉬워진다.
package main.java.ex1.sort;

import java.util.Arrays;
import java.util.stream.IntStream;

public class Program{
    public static void main(String[] args){

        int[] ages = {10,32,34,21,23,25,46};
        int[] result = IntStream
                            .of(ages)
                            .filter(age->age>25)
                            .sorted()
                            .toArray();

        System.out.println(Arrays.toString(result));
    }
}



3) Stream API 객체 살펴보기

a. Mapping

  • 일반적으로, 기존의 데이터 형식을 다른 모습으로 변환한다.
package main.java.ex1.sort;

import java.util.Arrays;
import java.util.stream.IntStream;

public class Program{
        int[] ages = {10,32,34,21,23,25,46};
        int[] result = IntStream
                            .of(ages)
                            .filter(age->age>25)
                            .sorted()
                            .map(age->age/10*10)	
                            .toArray();

    	   // 30, 40, 40 출력(나이가 30대, 40대로 변환)
        System.out.println(Arrays.toString(result));
    }
}




3) Stream 객체 심화 내용

  • No Storage : 흐르는 강처럼 컨베이어 벨트와 같다. 데이터를 저장하지 않는다.
    • Stream 객체 : 데이터의 원본을 꺼내기만 한다. 원본을 훼손하지 않는다.


  • Laziness Seeking : 일련의 과정들이 나중에 다 최적화된다.


  • Possibly unbounded : 저장소가 아니라서 크기를 유한할 때는 필요로하지 않는다.


  • Consumable : 원본을 한번 꺼내서 사용하면 소비되고 사라진다.


  • 즉, Stream 객체는 커다란 연산기라고 부른다.



4) Stream 객체 생성 방법

  • Collection stream() : 원래, 콜렉션에서 스트림을 사용할 수 있다.


  • Arrays.stream : 배열에서도 스트림을 사용할 수 있다.


  • BufferedReader.lines() : 원격의 네트워크로 온 데이터도 스트림으로 읽을 수 있다.



5) Stream 객체 종류

  • BaseStream 부모로부터 상속받아서 사용 가능하다.


  • 스칼라 값을 가진 Stream 객체 종류 : IntStream, LongStream, DoubleStream
    • 스칼라 값이라서 평균도 구할 수 있다.


  • 참조 값을 가진 Stream 객체 종류 : Stream
    • 참조 값이라서 연산은 불가능하다. 참조 값을 가진 Stream 객체는 일반 객체를 받을 수 있다.



6) Stream API 활용 : 파일 읽기

  • Stream API로 외부 파일을 읽으면 이전의 FileInputStream을 이용하는 것보다 매우 편해진다.
package main.java.ex1.sort;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

public class Program{
    public static void main(String[] args) throws IOException{

        // ***** 3. Stream API 활용 2 : 파일 읽기(편하다.) ***** 
        
        List<Exam> list = Files
        .lines(Path.of("res/data.csv"))
        .skip(1)    // 첫 줄 넘기기
        .map(line->{
            String[] tokens = line.split(",");
            
            if(tokens.length != 3)
                throw new RuntimeException("인자의 개수를 잘못되었습니다.");
            
            int kor = Integer.parseInt(tokens[0]);
            int eng = Integer.parseInt(tokens[1]);
            int math = Integer.parseInt(tokens[2]);

            return new Exam(kor, eng, math);
        })
        .toList();  // 기존의 코드에서 map을 쓰면, 에러가 안 발생한다.
        // JDK 8에서는 toList 대신에 collect를 사용한다.
        // JDK 17에서 toList 사용한다.

        System.out.println(list);



7) 리눅스 설치하는 과정

  • 아래 과정은 Linux를 가상 환경에서 다운 받을 시, 나오는 설치 과정들이다.


  • Mirror address : 원격의 Ubuntu 패키지 주소를 각 지역마다 나누어 패키지를 다운 받는 곳에서 다운 받는 속도를 빠르게 하기 위해 Mirroring하는 주소이다.


  • Mount Point : 파일 저장 파티션


  • ext4 파일 타입 : 리눅스의 파일 타입 설정


  • Profile Setting : 리눅스는 관리자를 우리가 만들 수 있다.


  • OpenSSH : 원격에서 관리할수 있는 최적화된 운영체제이다. 원격에서 접속할 수 있도록 하는 도구이다.
    • Secure Shell이라고 부른다. 안정된 원격 shell이다.



8) 리눅스 이해하기

a. Shell :

  • Shell의 기본 의미 : 기본적으로는 ‘사용자 인터페이스’를 의미한다.


  • OS : 깔아놓은 ‘어플리케이션’을 모아서 ‘관리’하는 곳


  • Shell 개념 : 운영체제가 실행하는 인터페이스, ‘운영체제를 감싸고 있는 것’이다. 원래 사전적으로 shell 뜻이 통한다.
    • 정리** : ‘운영체제가 제공하는 인터페이스’이다.
    • 사용자가 만질 수 있는 것은 Shell만 존재한다.


  • 이제는 Shell을 통해 원격의 서버를 운영해보자!
    • 가상환경에서 Linux를 켜고 기본 사용자의 컴퓨터 환경(원격 환경)에서 터미널로 접속하는 과정


b. Bash Shell :

  • 원래는 Bourne Shell(본쉘)이다.

  • 본쉘을 범용적으로 사용하기 위해 다시 만들어진 Bourne Again Shell인 ‘bash’를 이용한다.



c. ‘$ sign’,’#(샵) sign’

  • $ sign : 일반적인 유저 계정

  • # sign : 관리자 계정



d. sudo 명령어

  • sudo : 일반 사용자가 일시적으로 관리자로 권한으로 사용할 수 있다.

  • sudo su - : -만 쓰면 관리자 계정으로 변환된다.
    • 루팅 : 루트의 권한을 획득했다.(권리자 권한)
  • pwd : 현재 워크 디렉토리(present work directory)


e. 원격(내 컴퓨터 터미널)에서 가상 컴퓨터(Virtual Box)로 접속하기!**

  • 포트포워딩으로 설정 후 접속하기!!

  • 접속 명령어** : ssh + " " + id + @ + ip주소




5. 예외처리, 리눅스 명령어 : 230519

1) 예외처리

  • 라이브러리로 예외처리하기
    • throw new 함수명() : 이것으로 사용자가 만든 예외로 예외처리하기!


a. 실습코드

  • Program.java
package main.java.ex3.except;

public class Program {  
    
    // 여기서, throw는 자바의 런타임 환경에 예외 처리를 던지는 것이다.
    public static void main(String[] args){ 
        Calculator calc = new Calculator();
        int result;
        try {
            // 예외처리를 실행하는애가 책임을 져서 여기서 모든 것을 끝낸다. 
            result = calc.add(1111, 2);
            System.out.println(result);
        } catch (천을넘는예외 e) {
            System.out.println("죄송합니다. 입력에 오류가 있습니다. : 사용방법 합이 1000 이하");
            // e.printStackTrace();
        } catch (음수가되는예외 e) {
            // e.printStackTrace();
            System.out.println("죄송합니다. 입력에 오류가 있습니다. : 사용방법 합이 양수여야 합니다. ");
        } finally{
            System.out.println("마무리작업");
        }
        

        System.out.println("작업종료");
    }
}



  • Calculator.java
package main.java.ex3.except;

public class Calculator {
		
    // 여기서, throw는 사용자가 만든 예외 처리 함수에 예외 처리를 던지는 것이다.
	public static int add(int x, int y) throws 천을넘는예외, 음수가되는예외 {
		int result = x+y;
		
        if(result > 1000)
            throw new 천을넘는예외();
        
        if(result < 0)
            throw new 음수가되는예외();
        
		return result;
	}
	
	public static int sub(int x, int y) {
		int result = x-y;
		
		return result;
	}
	
	public static int multi(int x, int y) {
		int result = x*y;
		
		return result;
	}
	
	public static int div(int x, int y) {
		int result = x/y;
		
		return result;
	}
}


  • 천을넘는예외.java
package main.java.ex3.except;

// Exception을 상속 받아야 한다.
public class 천을넘는예외 extends Exception {

}


  • 음수가되는예외.java
package main.java.ex3.except;

public class 음수가되는예외 extends Exception{

}




2) JDK 8 이후의 예외처리

  • ‘try with resources’
    • 리소스를 가지고 있는 try문


  • 지역화되어서 다시 try-catch문을 이용하는 것을 막고 try문의 인자에 리소스를 담아서 1번만 선언해도 된다.



a. 실습코드


  • Program2.java

package main.java.ex3.except;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;

public class Program2 {  
    
    public static void main(String[] args){ 

        // try with resources

        try(
            FileOutputStream fos = new FileOutputStream("res/data.txt");
            PrintStream out = new PrintStream(fos);
        ){
            out.println("Hello");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        
    }
}


  • data.txt

Hello




3) 리눅스 실행 및 명령어

a. 로컬에서 실행하기

  • UTM에서 가상 환경에서 리눅스에 접속하고 맥북에서 iterm에서 ssh heon@아이피주소를 입력해서 리눅스를 원격으로 접속하기



b. 리눅스에서 패키지 및 파일을 제거하는 방법

  • apt-get remove : 기본 패키지 파일 제거, 설정 파일은 남겨둔다.

  • apt-get purge : 설정파일도 같이 제거



c. 리눅스 파일 시스템의 구조

  • 리눅스에서는 파일이랑 폴더가 같은 취급한다.

  • 마운트 : 루트(“/”)에서 퍼져나가서 USB 파일을 리눅스의 브랜치에 붙이는 경우

  • 레지스트리 : 어플리케이션이 가지고 있는 실행 설정



d. 리눅스 명령어

  • cd / : root 경로로 이동


  • cd : 현재 사용자 경로로 이동



e. 리눅스 디렉토리 역할 정리

  • 디렉토리는 역할마다 정해진 디렉토리 구조를 무조건 지켜야하는 것은 적합하지는 않다.**


  • 리눅스의 파일 경로를 bin로 이동해서 ls를 입력해보면 연두색 파일명의 파일과 민트색 파일명의 파일이 있다.


  • 연두색 파일명의 파일은 실행중인 파일들을 의미하고 민트색 파일의 링크 파일이며 바로가기를 의미한다.


  • dev : 디스크의 폴더를 의미한다. 장치파일들이 저장되어 있는 디렉토리입니다.


  • tty : 터미널을 의미한다.


  • home : 현재 활동 중인 유저들의 id를 확인할수 있다.


  • media : 현재는, 외장하드나 USB에 연결된 것이 없다. 외장하드 등과 연결되면 목록에서 목록 확인 가능!
    • mnt : mnt 폴더명과 같은 역할을 한다. 예전에는 mnt 폴더라고 불리었다. 파일 시스템을 임시로 연결하는 디렉토리이다..


  • opt : ‘사용자 어플리케이션’를 의미한다. 추가 패키지가 설치되는 디렉토리
    • usr과 같은 역할을 지닌 폴더 구조이다.


  • tmp : 실행중인 임시 데이터 저장 폴더.


  • var : 환경 변수들의 모임.


f. 리눅스 디렉토리를 웹 개발에서 사용 방법 예시

  • 톰캣의 사용/비사용 로그 파일을 var 폴더에 둔다.


  • config 파일은 etc 폴더에 저장하면 된다.



4) 리눅스 : 파일 및 폴더 생성하고 지우기

  • 바이너리 파일을 저장할 수 있는 폴더 만들기!


  • ‘touch 파일명’ : touch Hello.java 파일 만들기


  • ‘rmdir’ : 디렉토리 지울 때, 사용하지만, 내부 폴더에 파일이 전부 지워져야 한다.


  • ‘rm’ : 파일이나 디렉토리 지울 때, 사용한다.


  • ‘rm -r 파일명’ : 강제로 지우기!


a. vi 편집기 사용하기(예전 버전 리눅스용 편집기)

  • ‘vi HEllo.java’ : 자바 파일로 이동하기


  • ‘i’ : 편집기에 텍스트 입력하기


  • ‘dd’ : 편집기에 insert로 들어가기 전에 dd 명령어를 입력하면 텍스트를 1줄씩 지우기


  • ‘dw’ : 편집기에 insert로 들어가기 전에 dd 명령어를 입력하면 텍스트의 글자 1개씩 지우기


b. nano 편집기 사용하기(최근 버전 리눅스용 편집기)

  • 많이는 안 쓰이고 수정 시, 사용!!


  • ‘nano Hello.java’ : 나노 편집기 사용하기!


  • 나가는 방법 :
    • 1) ctrl + x : 나가기
    • 2) Y : 나가기 확정
    • 3) Enter : 나가기!


  • 외부 폴더에서 파일 내부 코드 읽기 : cat Hello.java


  • 파일 복사하기 : cp Hello.java Hello1.java
    • ‘Hello.java’ 파일을 ‘Hello1.java’라는 파일로 추가로 생성하여 복붙한다.


  • 파일명 바꾸기 : mv Hello1.java Hello.java
    • 같은 디렉토리에서 같은 디렉토리로 못가므로 파일명이 바뀐다.


  • 파일 만들기 : touch Hello.java



5) 파이프 라인 개념 :

  • 한 쪽에서 결과를 다른 쪽에도 결과를 내보냄**


  • 문자열 출력하기 : echo “Hello haha”


  • >는 다른 파일에 내용을 대체하여 영향을 준다. : echo "Oh My God~~"
    • 결과 : Oh My God


  • >>는 다른 파일에 내용을 추가하여 영향을 준다. : echo "Oh My God~~@@"
    • 결과 : Oh My God~~@@ Oh My God


  • 파일 내부의 특정 문자열 검색하기 : Hello.java || grep "MY"
    • 파일 내부의 텍스트 중 “MY”라는 부분 찾기


  • cat Hello.java | More : 전체 목록중 화면에 보여지는 부분 만 보여주고 나머지는 More로 대체해서 More 버튼을 눌러야지 전체 목록에 대한 나머지 부분을 볼 수 있다.


  • cat Hello.java | less : 전체 목록 바로 보여주고 방향키의 위와 아래로 움직일 때



6) 리눅스 권한 :

  • ‘rwX’ 개념 이용하기!