반응형

사용이유

datasource 커넥션을 로직이나 상황별로 나눠야 하는 상황이 올 수 도 있고, 어플리케이션단에서 샤딩을 구현해야 할 수 도 있고, read 커넥션 및 write 커넥션의 datasource 를 나눠야 하는 경우가 있다.

애초에 다른 datasoure 파일을 만들어도 되지만, 동적으로 쿼리 요청시마다 datasource 를 변경해야 할 때 AbstractRoutingDataSource 를 사용하면 된다.

 

AbstractRoutingDataSource란?

spring-jdbc 모듈에 포함되어 있는 클래스로, 여러 DataSource를 등록하고 특정 상황에 맞게 원하는 DataSource를 사용할 수 있도록 추상화한 클래스이다.

 

구현방식

AbstractRoutingDataSource는 이름 기반으로 다중 DataSource 정보를 등록하는 방법이다.

AbstractRoutingDataSource의

public void setTargetDataSources(Map<object, object=""> targetDataSource)를 호출하여

String:DataSource을 키:값으로하는 Datasource를 Map에 저장할 수 있다.

 

AbstractRoutingDataSourcedetermineCurrentLooupKey()를 오버라이드해서 상황에 맞게 Key를 반환하도록 구현해야 한다.

어떤 데이터 소스를 쓸지에 대한 결정은 determineCurrentLooupKey 메소드 내부에서 쿼리 호출시마다 동적으로 결정된다.

return 되는 String 결과값에 해당되는 datasource 를 실행하게 된다.

public class MyRoutingDataSource extends AbstractRoutingDataSource {
	@Override
	protected Object determineCurrentLookupKey() {
        boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
        if (isReadOnly) {
            return "slave";
        } else {
            return "master";
        }	
	}
}

참고로 boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly(); 를 사용하려면

transactionManager 에 LazyConnectionDataSourceProxy 를 설정해 주어야 한다.

    @Bean("RoutingDataSource")
    public DataSource routingDataSource() {
        HikariDataSource masterDataSource = createDatasource(~~);
        HikariDataSource slaveDataSource = createDatasource(~~);

        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put("master", masterDataSource);
        dataSourceMap.put("slave", slaveDataSource);

        DynamicRoutingDataSource routingDataSource = new MyRoutingDataSource();
        routingDataSource.setTargetDataSources(dataSourceMap);
        routingDataSource.setDefaultTargetDataSource(masterDataSource);
        return routingDataSource;
    }
    
    @Bean("routingLazyDataSource")
    public DataSource routingLazyDataSource(@Qualifier("RoutingDataSource") DataSource dataSource) {
        return new LazyConnectionDataSourceProxy(dataSource);
    }
    
    @Bean(name = "transactionManager")
    public PlatformTransactionManager transactionManager(@Qualifier("routingLazyDataSource") DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
    
    public HikariDataSource createDatasource() {
    	
        // 데이터 connection 정보
        ~~
        return new HikariDataSource(config);
    }

 

LazyConnectionDataSourceProxy 는 데이터소스를 한번 더 감싸는 역할을 해주어, LazyConnectionDataSourceProxy 로 여러 데이터 소스를 묶어 PlatformTransactionManager 에 datasource 를 세팅해야, tranaction 이 정확히 동작한다.

다음과 같이 PlatformTransactionManager 에 LazyConnectionDataSourceProxy 로 datasource 여러개를 감싼 datasource 를 set 해줘야 

TransactionSynchronizationManager.isCurrentTransactionReadOnly() 를 할 경우 정확히 readOnly 를 사용하는 쿼리를 캐치해서 분리 할 수 있다.

@Transactional(readOnly = true)
public List<Test> findTestList() {
	return testMapper.selectTestList();
}

다음과 같이 readOnly 가 true 인 경우는 isCurrentTransactionReadOnly 가 true 이므로 determineCurrentLookupKey 메소드에서 "slave" datasource 를 반환하게 된다.

만약에 위에서 만든 여러 datasource 를 LazyConnectionDataSourceProxy 로 감싸지 않으면 readOnly 가 true 인지를 알 수 가 없다.

 

 

위처럼도 사용가능하고, determineCurrentLookupKey 를 어떻게 구현하느냐에 따라 db 정보를 다르게 사용할 수 있다. 

 

반응형
반응형

CompletableFuture 하나만 있으면 비동기 처리 및 여러 병렬처리를 원하는 대로 할 수 가 있다.

 

여기서 코드 구현하려는 내용은

list 데이터가 있고, 각 list 의 item 이 http 커넥션을 통해 데이터를 가져오는 등 순차적으로 수행하기에는 시간소요가 오래걸랄때 쓰레드를 이용해 시간을 줄여보고자 한다.

 

List<Item> list = itemRepository.findAll();


ExecutorService executorService = Executors.newFixedThreadPool(10);
log.info(list.size() + "개");

List<CompletableFuture<Boolean>> resultList =  list.stream().map(
    item -> CompletableFuture.supplyAsync(() -> {
    	try {
    		return this.getDataByConnection(item.getParam());
    	} catch (IOException e) {
    		e.printStackTrace();
    	}
    	return false;
    }, executorService)
).collect(Collectors.toList());

resultList.stream().map(CompletableFuture::join).collect(Collectors.toList()); 
// join 을 해야 CompletableFuture 가 stream api 내부에서 수행됨. 



private boolean getDataByConnection(String param) throws IOException {
        // 해당 내용은 okhttp 라이브러리로 다른 http 커넥션 사용해도 됨.
        Request request = new Request.Builder()
                .url("https://~~~~~~~")
                .method("GET", null)
                .build();
        Response response = client.newCall(request).execute();
        String jsonString = response.body().string();
        
        ~~~~~~ 성공 및 실패에 대한 응답
        
        return true;

    }

1. ExecutorService 로 쓰레드 생성

ExecutorService 로 생성된 쓰레드 개수만큼만 병렬쓰레드로 실행 할 수 있다.

list 의 개수에 따라 더 늘려줄 수 도 있고, list 개수가 너무 많으면 성능 부하가 생길 수 있으므로 적정수의 쓰레드를 생성하자.

 

2. list 를 stream api 를 사용하여 stream 내부에서 CompletableFuture 를 실행하자.

stream api 내부에서 map api 로  supplyAsync 든 runAsync 를 하자.

둘다 CompletableFuture 의 비동기 메소드 이지만, supplyAsync 는 응답값을 받고, runAsync 는 응답을 받지 않는 메소드이다.

 

3. 만든 executorService 쓰레드를 CompletableFuture 파라미터에 넣어주자. 

쓰레드 개수를 10개ㄹ로 해주면 아래와 같이 10개까지만 쓰레드가 생성되서 실행이 된다.

 

쓰레드풀을 넣지 않아도 된다. 

넣지 않으면 적정수의 쓰레드가 생성.

7개의 쓰레드까지만 생성이 되었다.

 

4. 마지막으로 join 을 실행시키자

CompletableFuture 를 받는 변수를 생성 하고, join 을 해줘야 해당 쓰레드풀이 실행이 되고 결과값이 반환이 된다.

CompletableFuture 의 <> 안에 들어가 있는 객체를 통해 새로운 list 로도 만들수 있다.

 

EX) 아래와 같이 list 의 item 마다 병렬처리를 통해 새로운 list 를 구성할 수 있다.

List<CompletableFuture<Item>> resultList =  list.stream().map(
    item -> CompletableFuture.supplyAsync(() -> {
    	try {
    		return this.getDataByConnection(item.getParam());
    	} catch (IOException e) {
    		e.printStackTrace();
    	}
    	return false;
    }, executorService)
).collect(Collectors.toList());

List<Item> finalList = resultList.stream().map(CompletableFuture::join).collect(Collectors.toList());

 

 

 

반응형
반응형

query_string 쿼리는 여러 조건을 조합하기에는 용이한 문법이지만 옵션이 한정되어 있다.

여러 쿼리를 조합하기 위해서는 상위에 bool 쿼리를 사용하고 그 안에 다른 쿼리들을 넣는 식으로 사용 가능하다.

 

bool 쿼리는 4개의 인자를 가지고 있고, 그 인자 안에 다른 쿼리들을 배열로 넣는 방식으로 동작한다.

 

 

  • must : 쿼리가 참인 도큐먼트들을 검색
  • must_not : 쿼리가 거짓인 도큐먼트들을 검색
  • should : 검색 결과 중 이 쿼리에 해당하는 도큐먼트의 점수를 높인다
  • filter : 쿼리가 참인 도큐먼트를 검색하지만 스코어를 계산하지 않는다. must 보다 검색 속도가 빠르고 캐싱이 가능.

 

GET <인덱스명>/_search
{
  "query": {
    "bool": {
      "must": [
        { <쿼리> }, …
      ],
      "must_not": [
        { <쿼리> }, …
      ],
      "should": [
        { <쿼리> }, …
      ],
      "filter": [
        { <쿼리> }, …
      ]
    }
  }
}

 

and 조건으로 검색하려면

must 배열안에 여러개의 조건을 추가해야 한다.

 

예를 들어 match 해당 기간동안 logDate의 로그에 어떤 text 를 가진 데이터 조회를 할 경우

GET 인덱스/_search
{
  "query": { 
    "bool": { 
      "must": [
        {
          "match": {
              "text": "텍스트"
          }
        },
        { 
          "range": { 
             "logDate": {
                  "gte": "2021-06-29 11:00:00.000",
                  "lte": "2021-06-29 12:00:00.000",
                  "format": "yyyy-MM-dd HH:mm:ss.SSS",
                  "time_zone": "+09:00"
             }
          }
        }
      ]
    }
  }
}

 

 

or 조건으로 검색하려면 must : [{match :"A B"}] 와 같이 검색하자.

 

 

 

참고문헌

> https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html [bool 쿼리 공식문서]

> https://esbook.kimjmin.net/05-search/5.2-bool [블로그 Elastic 가이드북]

반응형

'DB > Elasticsearch' 카테고리의 다른 글

[ELK] logstash filter grok 사용  (0) 2022.12.18
반응형

vue 에서 vuex 를 사용하여 store 를 만들면,

getters 의 경우 this.$store.getters.[getter이름] 로 사용가능

actions 의 경우 this.$store.dispatch(‘[action이름]’, data) 과 같이 사용할 수 있다.

또는 직접 state 에 바로 접근해서 this.$store.state.book.message 로도 사용가능은 하다.

 

helper 없이 기본적으로 사용

<template>
  <div id="app">
    <div>
      <label>{{getMsg}}</label>

      <br/>

      <button @click="onChangedMsg">Click</button>
    </div>
  </div>
</template>

<script>
export default {
  name: 'app',
  computed: {
    getMsg () {
      return this.$store.getters.getMsg
    }
  },
  methods: {
    onChangedMsg () {
      this.$store.dispatch('callMutation', { newMsg: 'World !!' })
    }
  }
}
</script>

 

store/index.js

store 를 하나만으로는 사용하기 어려우므로 기능별 또는 페이지별로 분리해야한다.

기능/페이지별로 store를 분리하고, 하나의 store에는 state, mutations, actions, getters를 포함해서 관리하는게 편하다.

index 파일에서 여려개의 모듈을 import 하는 역할이다.

import Vue from 'vue'
import Vuex from 'vuex'

import BookStore from './module/book'

Vue.use(Vuex)

export default new Vuex.Store({
    modules: {
        book: BookStore
    }
})

store/module/book.js

// state
const state = {
    message: 'Hello'
}

// mutations
const mutations = {
     changeMessage (state, newMsg) {
      state.message = newMsg
    }
}

// actions
const actions = {
    callMutation ({ state, commit }, { newMsg }) {
      commit('changeMessage', newMsg)
    }
}

// getters
const getters = {
    getMsg (state) {
      return `${state.message} => Length : ${state.message.length}`
    }
}

export default {
  state,
  mutations,
  actions,
  getters
}

위와 같이 한 페이지별 기능별로 js 파일을 따로 모듈로 관리하여 둔다면 관리하기 편하다.

 

Vuex Binding Helper

위에 설명했던 예시처럼 사용할 수도 있지만 vuex 에는 binging helper 라는 util 이 존재하여 좀 더 간편하게 사용이 가능하다.

 

Helper는 state, mutations, actions, getters 별로 각각 mapState, mapActions, mapMutations, mapGetters가 존재하고 아래처럼 바인딩할 수 있다.

 

import { mapState, mapActions, mapMutations, mapGetters } from 'vuex'

export default {
  name: 'Sample',
  computed: {
    ...mapState('book', {
        message: state => state.message     // -> this.message
    }),
    ...mapGetters('book', [
       'getMsg'       // -> this.getMsg
    ])
  },
  methods: {
    ...mapMutations('book', [
        'mutation이름'     // -> this.mutation이름() 으로 mutation 접근
    ]),
    ...mapActions('book', [
        'action이름'      // -> this.action이름() 으로 action call가능
    ])
  }
}

 

Vuex createNamespacedHelpers

mapHelper를 통해 개발이 가능하지만 좀 더 편리하고 효율적으로 하기 위해서는 createNamespacedHelpers를 사용하는 것이 좋다. 어떻게 보면 결과적으로나 사용되는 Util은 동일하다.

 

import { createNamespacedHelpers } from 'vuex'

const 
    bookHelper = createNamespacedHelpers('book'),
    bookListHelper = createNamespacedHelpers('bookList')

export default {
  name: 'Sample',
  computed: {
    ...bookHelper.mapState({
        message: state => state.message     // -> this.message
    }),
    ...bookHelper.mapGetters([
       'getMsg'       // -> this.getMsg
    ]),
    ...bookListHelper.mapState({
        messageList: state => state.messageList     // -> this.messageList
    }),
    ...bookListHelper.mapGetters([
       'getMsgList'       // -> this.getMsgList
    ])
  },
  methods: {
    ...bookHelper.mapMutations([
        'changeMessage'     // -> this.changeMessage()
    ]),
    ...bookHelper.mapActions([
        'callMutation'      // -> this.callMutation()
    ]),
    ...bookListHelper.mapMutations([
        'changeMessageList'     // -> this.changeMessageList()
    ]),
    ...bookListHelper.mapActions([
        'callMutationList'      // -> this.callMutationList()
    ]),
  }
}

 

반응형
반응형

spring boot 에서 mybatis 를 연동하는 법을 해보자

pom.xml

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>

일단 pom.xml 에 mybatis 라이브러리부터 추가하자. dbms 에 맞는 driver 는 추가됐다고 가정하고 진행한다.

 

사용버젼 표

아래 표에 맞게 version 명시하면 된다.

MyBatis-Spring-Boot-Starter MyBatis-Spring Spring Boot Java 
2.2 2.0 (need 2.0.6+ for enable all features) 2.5 or higher 8 or higher
2.1 2.0 (need 2.0.6+ for enable all features) 2.1 - 2.4 8 or higher
2.0 (EOL) 2.0 2.0 or 2.1 8 or higher
1.3 1.3 1.5 6 or higher
1.2 (EOL) 1.3 1.4 6 or higher
1.1 (EOL) 1.3 1.3 6 or higher
1.0 (EOL) 1.2 1.3 6 or higher

spring boot 2.5 이상부터 mybatis-spring-boot-starter 2.2 버젼이 사용가능하다.

 

config 클래스

@Configuration
@MapperScan(value="com.juntcom.api.mapper.*", sqlSessionFactoryRef="dbMybatisSqlSessionFactory")
@Slf4j
public class DbMybatisConfig {
    @Autowired
    private Environment env;
 
    private static final String prefix = "spring.db.datasource.hikari.";
        
    @Bean(name = "dbMybatisSource", destroyMethod = "close")
    @Primary
    public HikariDataSource dbMybatisSource() {
    	HikariConfig config = new HikariConfig();
    	config.setUsername(env.getProperty(prefix+"username")); 
    	config.setPassword(env.getProperty(prefix+"password")); 
//    	config.setDriverClassName(env.getProperty(prefix+"driverClassName"));
    	config.setJdbcUrl( env.getProperty(prefix+"jdbc-url") );
    	config.setMaxLifetime( Long.parseLong(env.getProperty(prefix+"max-lifetime")) );
    	config.setConnectionTimeout(Long.parseLong( env.getProperty(prefix+"connection-timeout")));
    	config.setValidationTimeout(Long.parseLong( env.getProperty(prefix+"validation-timeout")));
    	
    	
    	config.addDataSourceProperty( "cachePrepStmts" ,  env.getProperty(prefix+"data-source-properties.cachePrepStmts"));
        config.addDataSourceProperty( "prepStmtCacheSize" , env.getProperty(prefix+"data-source-properties.prepStmtCacheSize"));
        config.addDataSourceProperty( "prepStmtCacheSqlLimit" , env.getProperty(prefix+"data-source-properties.prepStmtCacheSqlLimit") );	
        config.addDataSourceProperty( "useServerPrepStmts" , env.getProperty(prefix+"data-source-properties.useServerPrepStmts") );
        
    	
    	HikariDataSource dataSource = new HikariDataSource( config );
        
        return dataSource;
    }
    
    @Bean(name = "dbMybatisSqlSessionFactory")
    @Primary
    public SqlSessionFactory dbMybatisSqlSessionFactory(@Qualifier("dbMybatisSource") DataSource dbMybatisSource) throws Exception {

        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dbMybatisSource);
//        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:sql/read/*.xml"));
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:sql/**/*.xml"));
        return sqlSessionFactoryBean.getObject();
    } 

    @Bean(name = "dbMybatisSqlSessionTemplate")
    @Primary
    public SqlSessionTemplate dbMybatisSqlSessionTemplate(SqlSessionFactory dbMybatisSqlSessionTemplate) throws Exception {
        return new SqlSessionTemplate(dbMybatisSqlSessionTemplate);
    }


    
   
    @Bean(name="dbMybatisTransactionManager")
    @Primary
    public PlatformTransactionManager dbMybatisTransactionManager(@Qualifier("dbMybatisSource") DataSource dbMybatisSource) {
  
    	DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
    	transactionManager.setDataSource(dbMybatisSource);
        return transactionManager;
    }
}

@MapperScan(value="com.juntcom.api.mapper.*", sqlSessionFactoryRef="dbMybatisSqlSessionFactory")

@MapperScan -> mapper 클래스를 놓을 클래스 경로가 맞아야 한다. 

mapper 라는 패키지 하위에 mapper interface 를 위치 시켜야 mapper 를 스캔한다.

 

private static final String prefix = "spring.db.datasource.hikari."

라고 선언부가 application.yml 파일에서 가져오기 위한 prefix 이다. 따로 설정하지 않고, class 파일 내부에서 작성해도 되나, 설정값을 한눈에 보기 위해 application.yml 에 기입하는게 낫다.

 

("classpath:sql/**/*.xml"));

resources 하위에 sql 폴더 생성 후 그 밑에 xml 파일을 만들자.

경로는 sql/*.xml 로 sql 폴더 바로 밑에 xml 파일 두고 싶은경우는 다음과 같이 하자. 

나 같은 경우는 read 폴더 및 writer 폴더를 나누는게 좋아서 /**/ 를 추가했다.

 

참고

datasource 를 하나만 둘 거면 yml 에 설정값을 추가함으로써 db connection 을 할 수 있지만,

java 파일에서 한 이유는

여러개의 db 를 사용할 경우 multi datasource 를 사용하기 위함이다.

yml 에 db1, db2 라고 추가해 java class 파일을 별도로 

class1 {

  private static final String prefix "spring.db1.datasource.hikari."

  ...

}

class2 {

  private static final String prefix "spring.db2.datasource.hikari."

  ...

}

와 같이 class 를 별도로 두면 된다.

application.yml

spring:
  db: #db1 이든 db2 이든 상관없다. java class 의 prefix 의 값과 맞춰주면 된다.
    datasource:
      hikari:
        jdbc-url: jdbc:mysql://localhost:3306?&useSSL=false&characterEncoding=UTF-8&serverTimezone=UTC
        username: root
        password: 비밀번호
        driverClassName: com.mysql.cj.jdbc.Driver
        maximum-pool-size: 10
        #minimum-idle: 100
        max-lifetime: 1800000 #1800000
        connection-timeout: 30000 #30000
        validation-timeout: 5000
        #connection-test-query: SELECT 1
        data-source-properties:
          cachePrepStmts: true
          prepStmtCacheSize: 250
          prepStmtCacheSqlLimit: 2048
          useServerPrepStmts: true

위까지만 하면 datasource 가 뜨기까지의 설정은 되었으나, mapper 클래스 및 resource 하위에 쿼리가 비어있기 때문에 샘플 하나는 만들어야 어플리케이션이 뜬다.

 

 

mapper 클래스

다음과 같이 위치시켜주자. 패키지명을 달리하려면 config 클래스에서 MapperScan 의 값을 변경하자.

@Mapper
public interface ReadMapper {
    List<Map<String, Object>> selectCode() throws Exception;
}

. xml 파일

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.juntcom.api.mapper.read.ReadMapper">

    <select id="selectCode" resultType="map">
		SELECT
		    *
		FROM
            TEST
    </select>


</mapper>

 

 

 

참고문헌

> https://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/ [mybatis spring boot starter 공식문서]

반응형
반응형

vue3 를 사용하게 되면 vuex4 를 사용하여 store 를 관리 할 수 있다.

 

vuex 4 설치

npm install --save vuex@4.0.1   

 

store 작성

 vuex 의 store를 module 형식으로 구성

|-- store

|    |-- index.js

|    |-- mutation-types.js

|    |-- modules

|         |-- person.js

   

  • mutation-types.js : 뮤테이션 타입 정의를 담당 
  • modules : vuex 에서 각 모듈들을 담아두는 폴더 
  • index.js : vuex 의 store 를 정의

다음과 같은 구조로 해야 소스관리가 편하다.

mutation-types.js 작성

export const PERSON = {
    SET_NAME: 'SET_NAME', // 이름을 변경하는 타입 정의
};

person.js 모듈 작성

방법1

import { PERSON } from '../mutation-types';

const state = {
    name: '',
    age: 0,
}

const getters = {
    personInfo: (state) => {
        return `이름 : ${state.name}, 나이 : ${state.age}`;
    }
}

const actions = {
    changeName({ commit }, value){
        commit(PERSON.SET_NAME, value);
    },
}

const mutations = {
    [PERSON.SET_NAME](state, value) {
        state.name = value;
    }
}

export default {
    namespaced: true,
    state,
    getters,
    actions,
    mutations
}

방법2

import { PERSON } from '../mutation-types';


export const person = {
  state: () => ({
    name: '',
    age: 0,
  }),
  mutations: {
    [PERSON.SET_NAME](state, value) {
      state.name = value;
    }
  },
  getters: {
    personInfo(state) {
      return `이름 : ${state.name}, 나이 : ${state.age}`;
    }
  },
  actions: {
    changeName({ commit }, value){
        commit(PERSON.SET_NAME, value);
    },
  }
};

store 의 모듈들은 state, mutations, getters, actions 라는 속성으로 이루어져야 store 를 관리 하기 수월하다.

 

참고 : namespace 

store 를 모듈화 할떄는 어떤 스토어에서 어떤 모듈을 사용했는지 헷갈리므려 namespaced 를 꼭 사용해서

person/personInfo 등등으로 이름으로 호출 가능

 

namespace 를 사용시 

state는 기존대로 state.moduleName.stateName으로 호출

getters computed(() => store.getters["moduleName/getterName"])으로 호출

mutation store.commit("moduleName/mutationName", params)으로 호출

action store.dispatch("moduleName/actionName", params)으로 호출

 

index.js (스토어 선언 작성)

스토어를 선언하는 방법은 이전과 달리 createStore 를 통해 생성한다

import { createStore } from 'vuex';
import person from './modules/person';


export default createStore({
    modules: {person},
})

여러개 모듈로 할 경우 아래와 같이 mudules 에 추가하면 된다.

import { createStore } from "vuex";
import { Counter } from "@/store/modules/Counter";
import { moduleA } from "@/store/modules/moduleA";

export default createStore({
  modules: { Counter, moduleA }
});

 

store 연결하기 (main.js 작성)

 위의 작성된 코드를 main.js 에서 연결을 해야 한다.

스토어의 연결을 위해 위에서 작성한 스토어 선언 파일을 불러와서 .use() 를통해 사용하도록 해주면 vuex 설정이 완료된다.

import { createApp } from "vue";
import App from "./App.vue";
import store from "./store";

createApp(App)
  .use(store)
  .mount("#app");

 

useStore

 vuex 4 에서 새로나온 useStore 는 스토어를 사용하는 새로운 방법

const store = useStore(); // 스토어 호출
 
const name = computed(() => store.state.person.name); // state 정보 가져오기
const personInfo = computed(() => store.getters['person/personInfo']); // getters 가져오기
const changeName = e => store.dispatch('person/changeName', e.target.value); // 액션함수 

 

useStore를 사용한 예제 코드 작성

<template>
    <div id="app">
        <h2>{{name}}</h2>
        <p>{{personInfo}}</p>
        <input type="text" :value="name" @input="changeName" placeholder="이름을 작성해 주세요."/>
    </div>
</template>

<script>
    import {computed} from 'vue';
    import {useStore} from 'vuex';

    function usePerson() {
        const store = useStore();

        const name = computed(() => store.state.person.name);
        const personInfo = computed(() => store.getters['person/personInfo']);
        const changeName = e => store.dispatch('person/changeName', e.target.value);

        return {
            name,
            personInfo,
            changeName
        }
    }

    export default {
        name: 'App',
        setup() {
            return {
                ...usePerson()
            }
        },
    }
</script>

 

참고문헌

> https://kyounghwan01.github.io/blog/Vue/vue3/composition-api-vuex/#vuex-%E1%84%89%E1%85%A6%E1%84%90%E1%85%B5%E1%86%BC-%E1%84%86%E1%85%B5%E1%86%BE-store-module-1%E1%84%80%E1%85%A2%E1%84%85%E1%85%A9-%E1%84%89%E1%85%B5%E1%86%AF%E1%84%92%E1%85%A2%E1%86%BC [vue3에서 vuex 사용법 한글설명 자세함]

> https://next.vuex.vuejs.org/guide/migrating-to-4-0-from-3-x.html [공식문서]

반응형
반응형

실행 모드

vue cli 에는 기본적으로 3개 모드가 있다. vue cli 뿐 아니라 다른곳에서도 마찬가지.

1) development, 2) production, 3) test

그 외에 사용자가 정의한 모드를 추가 할 수 있다.

 

사용자 정의 모드

 // 최상단 경로 /package.json

"scripts": {
  .. 생략
   // local 로컬 모드 추가
  "local": "vue-cli-service serve --mode local",
  "mymode": "vue-cli-service serve --mode mymode", // 임의로 생성
   .. 생략
},
> npm run local
> npm run mymode

로 정의된 모드를 실행할 수 있다.

 

환경변수

실행 모드에 따라 변수 설정을 달리 할 수 있다.

기본적으로 2개의 환경 변수 NODE_ENV, BASE_URL 이 있다.

그 외에 환경변수 파일을 만들어 사용자가 정의한 변수를 추가 할 수 있다.

환경변수들은 process.env 객체에 정의되어 있고, 어디서나

console.log(process.env);

로 변수 값을 확인할 수 있다.

 

NODE_ENV

앱이 실행되는 모드이다.

3개의 기본 모드 "development", "production", "test" 가 있고, 사용자 정의 모드를 추가 할 수 있다.

모드 별로 기본적으로 사용하는 명령어가 있다.

  • development  ->  vue-cli-service serve
  • test -> vue-cli-service test:unit
  • production -> vue-cli-service build 와 vue-cli-service test:e2e

 

BASE_URL

vue.config.js의 publicPath 옵션에 해당하고 앱이 배포되는 기본 경로이다.

 

환경변수

환경변수 파일 생성방법

// 파일 경로: /.env.local, 파일은 반드시 루트 디렉토리에 생성해야 한다.

NODE_ENV = "local"

BASE_URL: "/"

VUE_APP_MYCONST = "아무변수 선언한거"

사용자 정의 변수 생성은 아래와 같이 생성한다.

VUE_APP_생성할변수명 = 값

 

환경변수 파일 룰

.env                # 모든 경우에 로드
.env.local          # 모든 경우에 로드 되지만 git 에 무시됨.
.env.[mode]         # 특정명의 모드에만 로드됨
.env.[mode].local   # 특정명의 모드에만 로드되지만 git 에 무시됨.

 

포트 변경

// /vue.config.js
module.exports = {
    devServer: {
        // 사용자 정의 환경 변수에서 VUE_APP_PORT가 있으면 사용하고
        // 없으면 3000 포트로 개발서버를 실행합니다.
        port: process.env.VUE_APP_PORT || 3000
    }
}

현재 실행하려는 모드가 local

npm run local 로 실행하고 .env.local 파일에 VUE_APP_PORT 변수가 있다면 해당 포트로 실행되고, 

정의된게 없다면 3000 포트로 실행하게 된다.

 

.env.local  

기본적으로 vue cli 로 프로젝트 생성시 .gitignore 파일에 .env.local 파일은 제외되어 있다.

해당 파일을 공유하려면 .gitignore 파일을 수정해야 한다.

// 파일 위치: /.gitignore

# local env files
.env.local
.env.*.local

 

참고문헌

https://cli.vuejs.org/guide/mode-and-env.html#modes

반응형
반응형

라즈베리 파이를 미니 서버로 사용하기 위해 새로 구매를 했다.

세팅조차 처음이라 포스팅을 한다.

 

목차

1. Micro SD 카드에 라즈비안 이미지 라이팅(writing)

2. SSH 활성화

3. 라즈베리파이 동작

4. SSH 클라이언트 사용하여 라즈베리파이에 접속

 

 

1. Micro SD 카드에 라즈비안 이미지 라이팅(writing)

키트를 샀을때 sd 카드가 왔을거다. sd 카드를 pc 에 연결시켜 라즈비안 os 이미지를 sd카드에 업로드 해야한다.

 

Raspberry Pi OS를 Micro SD 카드에 인스톨하기 위해 사용할 프로그램을 다운로드하여 설치해야한다.

윈도우, 맥, 리눅스 용을 다운로드 받을 수 있다.

https://www.raspberrypi.org/software/ 

 

라즈베리파이를 위한 운영체제를 선택하면 다운로드하여 Micro SD카드에 기록까지 해주는 프로그램이다. 

CHOOSE OS 를 선택 한 후 Raspberry Pi OS(32-bit) 를 선택한다. 

 

그 후 CHOOSE SD CARD 를 클릭하고 Micro SD카드를 선택한다.

다 되면 WRITE 를 선택한다.

설치가 조금 걸린다.

 

 

2. SSH 활성화

2016년 이후로 라즈비안은 보안상의 이유로 디폴트로 SSH 가 비활성화 되어 있다고 한다.

PC 에서 활성화 시키는 방법은 연결 후 os 업로드가 완료 되었다면, 

boot 드라이브를 들어가 ssh 파일을 만들면 끝이다.

ssh 파일 내용은 없어도 된다.

그냥 ssh 라는 이름의 파일을 만들자.

 

ssh 파일이 생성되면 라즈베리파이로 ssh 접속이 활성화가 된다.

 

3. 라즈베리 파이 사용하기

라즈베리 파이 OS 를 업로드 한 SD 카드를 끼운다.

c타입의 전선을 power 포트에 끼운다.

랜선을 Network 포트에 끼운다.

 

다음과 같이 진행하고, 라즈베리파이에 연결된 ip 주소를 확인한다.

나같은 경우는 iptime 에 연결해서 iptime 에 할당된 ip 주소에 raspberry 가 떠서 확인했다.

ip주소를 확인 할 수 있는 ip 대역을 이용해라.

 

4. 라즈베리파이 접속하기

ip 가 192.168.0.1 기본게이트 내에서 192.168.0.30 으로 확인되었다.(22 port)

putty 나 각자 ssh 접속 툴을 사용해서 접속하자.

기본계정은 pi 

비밀번호는 raspberry 이다.

 

 

반응형

+ Recent posts