한국어

네트워킹

온누리070 플레이스토어 다운로드
    acrobits softphone
     온누리 070 카카오 프러스 친구추가온누리 070 카카오 프러스 친구추가친추
     카카오톡 채팅 상담 카카오톡 채팅 상담카톡
    
     라인상담
     라인으로 공유

     페북공유

   ◎위챗 : speedseoul


  
     PAYPAL
     
     PRICE
     

pixel.gif

    before pay call 0088 from app


https://youngest-programming.tistory.com/31?category=855796



먼저 개념부터 간단히 살펴보면 SQLite는 MySQL나 PostgreSQL와 같은 데이터베이스 관리 시스템이지만, 서버가 아니라 응용 프로그램에 넣어 사용하는 비교적 가벼운 데이터베이스이다

SQLiteOpenHelper는 데이터베이스 생성 및 버전 관리를 관리하는 도우미 클래스입니다.


먼저 들어가기 앞서, 데이터베이스는 다음과 같은 순서로 생성되고 사용된다는 것을 꼭 기억해야한다.

///데이터베이스 4단계
//1. 데이터베이스만들기(오픈하기)
//2. 테이블 만들기
//3. 레코드(데이터) 추가하기
//4. 데이터 조회화기(조회를 이용해서 데이터를 꺼내와 리스트뷰에 보여준다는 등 활용도 할 수 있을거다.

 

 

안드로이드 개발할때 가볍고 쉽게 쓰기위한 데이터베이스가 있는데 SQLiteDatabase와 이것을 발전시킨 SQLiteOpenHelper 이다. 

 

일반적인 SQLiteDatabase는 만약 기존에 앱을 사용하던 유저들이 있는데 데이터베이스의 테이블 구조가 변경되는 경우 이미 사용하고 있는 상태에서 기존의 테이블을 삭제해서 다시만드는것은 매우 위험하고 좋지않다. (새로 유입되는 유저들은 그냥 처음이니깐 테이블을 새로 생성해서 사용하면 끝이지만...)

 

그래서 위와 같은 경우 사용할 수 있는게 헬퍼클래스 즉, SQLiteOpenHelper 이다. SQLiteOpenHelper를 사용하면 기존에 테이블이 있는지 없느지 판단하여 각각에 알맞게 데이터베이스 테이블을 업그레이드 시킨다는가 새로 생성할 수 있다.  이러한 로직을 밑에 그림과 같이 onCreate(), onOpen(), onUpgrade()에 구현하면 된다. 추가로 간단히 설명하자면 onOpen은 데이터베이스 오픈관련, onCreate()는 데이터베이스 테이블을 처음 생성해주는 경우, onUpgrade는 기존에 테이블이 이미 생성되있고 이것을 업그레이드(구조를 수정한다던지)하는것을 구현하면 된다. 

이 구현하는 것은 SQLiteOpenHelper 을 extends한 클래스를 만들어서 하면 된다.

자세한건 실습을 해보면 알게될 것 같다.

 

 


먼저 기본 SQLiteDatabase 코드먼저 살펴보겠다. 

import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;


//데이터베이스 4단계
//1. 데이터베이스만들기
//2. 테이블 만들기
//3. 레코드(데이터) 추가하기
//4. 데이터 조회화기 (조회를 이용해서 데이터를 꺼내와 리스트뷰에 보여준다는 등 활용도 할 수 있을거다.
//DB쿼리문 작성시 공백이 매우 중요하므로 주의해야한다.
//코드를 짤때 데이터베이스를 오픈하거나 테이블을 만드는건 앱이 처음 생성될떄 onCreate같은데다가 미리 생성을 다해주고
//데이터를 넣거나(insert) 조회하는것은(select) 사용자가 뭔가 이벤트를 발생시켰을때 처리하는식으로 하면 된다.
public class MainActivity extends AppCompatActivity {
    EditText editText;
    EditText editText2;
    EditText editText3;
    EditText editText4;
    EditText editText5;

    TextView textView;

    SQLiteDatabase database;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        editText = findViewById(R.id.editText2);
        editText2 = findViewById(R.id.editText3);
        editText3 = findViewById(R.id.editText4);
        editText4 = findViewById(R.id.editText5);
        editText5 = findViewById(R.id.editText6);


        textView = findViewById(R.id.textView);

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String databaseName = editText.getText().toString();//데이터베이스 이름 설정
                openDatabase(databaseName);
            }
        });

        Button button2 = findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String tableName = editText2.getText().toString();//테이블 이름설정
                createTable(tableName);
            }
        });

        Button button3 = findViewById(R.id.button3);
        button3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String name = editText3.getText().toString().trim();//넣을 데이터 값 설정, trim을 해줌으로 공백으로 인한 에러방지
                String ageStr = editText4.getText().toString().trim();//넣을 데이터 값 설정
                String mobile = editText5.getText().toString().trim();//넣을 데이터 값 설정

                int age = -1;
                try{
                    Integer.parseInt(ageStr);
                }catch (Exception e){}

                insertData(name, age, mobile);
            }
        });

        Button button4 = findViewById(R.id.button4);
        button4.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String tableName = editText2.getText().toString();//테이블 이름설정
                selectData(tableName);
            }
        });
    }

    public void openDatabase(String databaseName){
        println("openDatabase() 호출됨");
         database = openOrCreateDatabase(databaseName, MODE_PRIVATE,null) ; //보안때문에 요즘은 대부분 PRIVATE사용, SQLiteDatabase객체가 반환됨
        if(database !=null){
            println("데이터베이스 오픈됨");
        }
    }

    public void createTable(String tableName){
        println("createTable() 호출됨.");

        if(database!= null) {
            //_id는 SQLite에서 내부적으로 관리되는 내부 id이다.
            String sql = "create table " + tableName + "(_id integer PRIMARY KEY autoincrement, name text, age integer, mobile text)";
            database.execSQL(sql);

            println("테이블 생성됨.");
        }else {
            println("데이터베이스를 먼저 오픈하십쇼");
        }
    }

    public void insertData(String name, int age, String mobile){
        println("insertData() 호출됨.");

        if(database != null){
            String sql = "insert into customer(name, age, mobile) values(?, ?, ?)";
            Object[] params = {name, age, mobile};
            database.execSQL(sql, params);//이런식으로 두번쨰 파라미터로 이런식으로 객체를 전달하면 sql문의 ?를 이 params에 있는 데이터를 물음표를 대체해준다.
            println("데이터 추가함");

        }else {
            println("데이터베이스를 먼저 오픈하시오");
        }
    }

    public  void selectData(String tableName){
        println("selectData() 호출됨.");
        if(database != null){
            String sql = "select name, age, mobile from "+tableName;
            Cursor cursor = database.rawQuery(sql, null); //파라미터는 없으니깐 null 값 넣어주면된다.
            println("조회된 데이터개수 :" + cursor.getCount());
            //for문으로해도되고 while 문으로 해도됨.
            for( int i = 0; i< cursor.getCount(); i++){
                cursor.moveToNext();//이걸 해줘야 다음 레코드로 넘어가게된다.
                String name = cursor.getString(0); //첫번쨰 칼럼을 뽑아줌
                int age = cursor.getInt(1);
                String mobile = cursor.getString(2);
                println("#" + i + " -> " +  name + ", " + age + ", "+ mobile );
            }
            cursor.close(); //cursor라는것도 실제 데이터베이스 저장소를 접근하는 것이기 때문에 자원이 한정되있다. 그러므로 웬만하면 마지막에 close를 꼭 해줘야한다.
        }
    }

    public void println(String data){
        textView.append(data + "\n");
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:orientation="vertical"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="1.데이터베이스 오픈" />

        <EditText
            android:id="@+id/editText2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:ems="10"
            android:inputType="textPersonName"
            android:text="customer.db" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="2. 테이블 만들기" />

        <EditText
            android:id="@+id/editText3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:ems="10"
            android:inputType="textPersonName"
            android:text="customer" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/button3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="3. 데이터 추가하기" />

        <EditText
        android:id="@+id/editText4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:inputType="textPersonName"
        android:text="걸스데이" />
        <EditText
            android:id="@+id/editText5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:inputType="numberDecimal"
            android:text="20" />
        <EditText
            android:id="@+id/editText6"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:inputType="textPersonName"
            android:text="010-1321-4421" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/button4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="4. 데이터 조회하기" />


    </LinearLayout>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/holo_orange_light">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:id="@+id/textView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textSize="24sp" />
        </LinearLayout>

    </ScrollView>

</LinearLayout>

 

 


다음은 SQLiteOpenHelper 를 사용한 예제이다.

package com.example.myhelper_sqliteopenhelper;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;


///데이터베이스 4단계
//1. 데이터베이스만들기
//2. 테이블 만들기
//3. 레코드(데이터) 추가하기
//4. 데이터 조회화기(조회를 이용해서 데이터를 꺼내와 리스트뷰에 보여준다는 등 활용도 할 수 있을거다.
//DB쿼리문 작성시 공백이 매우 중요하므로 주의해야한다.
//코드를 짤때 데이터베이스를 오픈하거나 테이블을 만드는건 앱이 처음 생성될떄 onCreate같은데다가 미리 생성을 다해주고
//데이터를 넣거나(insert) 조회하는것은(select) 사용자가 뭔가 이벤트를 발생시켰을때 처리하는식으로 하면 된다.
public class MainActivity extends AppCompatActivity {
    EditText editText;
    EditText editText2;
    EditText editText3;
    EditText editText4;
    EditText editText5;

    TextView textView;

    SQLiteDatabase database;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        editText = findViewById(R.id.editText2);
        editText2 = findViewById(R.id.editText3);
        editText3 = findViewById(R.id.editText4);
        editText4 = findViewById(R.id.editText5);
        editText5 = findViewById(R.id.editText6);


        textView = findViewById(R.id.textView);

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String databaseName = editText.getText().toString();//데이터베이스 이름 설정
                openDatabase(databaseName);
            }
        });

        Button button2 = findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String tableName = editText2.getText().toString();//테이블 이름설정
                createTable(tableName);
            }
        });

        Button button3 = findViewById(R.id.button3);
        button3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String name = editText3.getText().toString().trim();//넣을 데이터 값 설정, trim을 해줌으로 공백으로 인한 에러방지
                String ageStr = editText4.getText().toString().trim();//넣을 데이터 값 설정
                String mobile = editText5.getText().toString().trim();//넣을 데이터 값 설정

                int age = -1;
                try {
                    Integer.parseInt(ageStr);
                } catch (Exception e) {
                }

                insertData(name, age, mobile);
            }
        });

        Button button4 = findViewById(R.id.button4);
        button4.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String tableName = editText2.getText().toString();//테이블 이름설정
                selectData(tableName);
            }
        });
    }

    public void openDatabase(String databaseName) {
        println("openDatabase() 호출됨");
     /*   database = openOrCreateDatabase(databaseName, MODE_PRIVATE, null); //보안때문에 요즘은 대부분 PRIVATE사용, SQLiteDatabase객체가 반환됨
        if (database != null) {
            println("데이터베이스 오픈됨");
        }*/

        DatabaseHelper helper = new DatabaseHelper(this , databaseName, null, 3); //헬퍼를 생성함
       // DatabaseHelper helper = new DatabaseHelper(this , databaseName, null, 4); //위에거 실행후 이거 실행했을 경우 (이미 해당 디비가있으므로 헬퍼의 update가 호출될것이다.)
        database = helper.getWritableDatabase();  //데이터베이스에 쓸수 있는 권한을 리턴해줌(갖게됨)

    }


    public void createTable(String tableName) {
        println("createTable() 호출됨.");

        if (database != null) {
            String sql = "create table " + tableName + "(_id integer PRIMARY KEY autoincrement, name text, age integer, mobile text)";
            database.execSQL(sql);

            println("테이블 생성됨.");
        } else {
            println("데이터베이스를 먼저 오픈하십쇼");
        }
    }

    public void insertData(String name, int age, String mobile) {
        println("insertData() 호출됨.");

        if (database != null) {
            String sql = "insert into customer(name, age, mobile) values(?, ?, ?)";
            Object[] params = {name, age, mobile};
            database.execSQL(sql, params);//이런식으로 두번쨰 파라미터로 이런식으로 객체를 전달하면 sql문의 ?를 이 params에 있는 데이터를 물음표를 대체해준다.
            println("데이터 추가함");

        } else {
            println("데이터베이스를 먼저 오픈하시오");
        }
    }

    public void selectData(String tableName) {
        println("selectData() 호출됨.");
        if (database != null) {
            String sql = "select name, age, mobile from " + tableName;
            Cursor cursor = database.rawQuery(sql, null); //파라미터는 없으니깐 null 값 넣어주면된다.
            println("조회된 데이터개수 :" + cursor.getCount());
            //for문으로해도되고 while 문으로 해도됨.
            for (int i = 0; i < cursor.getCount(); i++) {
                cursor.moveToNext();//이걸 해줘야 다음 레코드로 넘어가게된다.
                String name = cursor.getString(0); //첫번쨰 칼럼을 뽑아줌
                int age = cursor.getInt(1);
                String mobile = cursor.getString(2);
                println("#" + i + " -> " + name + ", " + age + ", " + mobile);
            }
            cursor.close(); //cursor라는것도 실제 데이터베이스 저장소를 접근하는 것이기 때문에 자원이 한정되있다. 그러므로 웬만하면 마지막에 close를 꼭 해줘야한다.
        }
    }

    public void println(String data) {
        textView.append(data + "\n");
    }

    class DatabaseHelper extends SQLiteOpenHelper {
        public DatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
            super(context, name, factory, version);
        }

        @Override
        public void onCreate(SQLiteDatabase db) { //데이터베이스를 처음 생성해주는 경우(기존에 사용자가 데이터베이스를 사용하지 않았던 경우)
            println("createCreate() 호출됨.");
            String tableName = "customer";
            //이 함수에서 데이터베이스는 매개변수인 db를 써야한다.
            //테이블 생성할떄 if not exists라는 조건문을 넣어줄 수 있다. (존재하지않을때 테이블 생성)
            //_id는 SQLite에서 내부적으로 관리되는 내부 id이다.
            String sql = "create table if not exists " + tableName + "(_id integer PRIMARY KEY autoincrement, name text, age integer, mobile text)";
            db.execSQL(sql);

            println("테이블 생성됨.");

        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { //기존 사용자가 디비를 사용하고있어서 그걸 업데이트(수정)해주는경우
            println("onUpgrade() 호출됨: " + oldVersion + ", " + newVersion);

            //여기 예제에서는 그냥 삭제했지만 보통의 Alter로 수정을 한다거나 할 수도 있다.
            if (newVersion > 1) {
                String tableName = "customer";
                db.execSQL("drop table if exists " + tableName);
                println("테이블 삭제함");

                String sql = "create table if not exists " + tableName + "(_id integer PRIMARY KEY autoincrement, name text, age integer, mobile text)";
                db.execSQL(sql);

                println("테이블 생성됨.");
            }
        }
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:orientation="vertical"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="1.데이터베이스 오픈" />

        <EditText
            android:id="@+id/editText2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:ems="10"
            android:inputType="textPersonName"
            android:text="customer.db" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="2. 테이블 만들기" />

        <EditText
            android:id="@+id/editText3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:ems="10"
            android:inputType="textPersonName"
            android:text="customer" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/button3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="3. 데이터 추가하기" />

        <EditText
        android:id="@+id/editText4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:inputType="textPersonName"
        android:text="걸스데이" />
        <EditText
            android:id="@+id/editText5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:inputType="numberDecimal"
            android:text="20" />
        <EditText
            android:id="@+id/editText6"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:inputType="textPersonName"
            android:text="010-1321-4421" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/button4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="4. 데이터 조회하기" />


    </LinearLayout>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/holo_orange_light">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:id="@+id/textView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textSize="24sp" />
        </LinearLayout>

    </ScrollView>

</LinearLayout>

[실행결과]


위의 예제코드를 읽거나 직접 복사해서 실행해본다면 이해할 수 있을거 같다.

그리고 다시한번 

///데이터베이스 4단계
//1. 데이터베이스만들기(오픈하기)
//2. 테이블 만들기
//3. 레코드(데이터) 추가하기
//4. 데이터 조회화기(조회를 이용해서 데이터를 꺼내와 리스트뷰에 보여준다는 등 활용도 할 수 있을거다.

꼭 기억하도록 하자.

 

 

 

 


추가로 안드로이드 개발자문서를 보면 다음과같이 한 테이블과 관련된 아이템을 static으로 선언해서 코드를 작성할 수도 있다. (위에 코드와 다른코드입니다. DB구조가 다를 수 도 있습니다.)

import android.provider.BaseColumns;

public class CommentContract {
    private CommentContract(){}
    public static final String SQL_CREATE_COMMENT_TABLE = String.format("create table if not exists %s (%s integer PRIMARY KEY , %s text, %s integer, %s text, %s float, %s text, %s integer )",
            CommentEntry.TABLE_NAME,
            CommentEntry.COLUMN_NAME_ID,
            CommentEntry.COLUMN_NAME_WRITER,
            CommentEntry.COLUMN_NAME_MOVIEID,
            CommentEntry.COLUMN_NAME_TIMES,
            CommentEntry.COLUMN_NAME_RATING,
            CommentEntry.COLUMN_NAME_CONTENTS,
            CommentEntry.COLUMN_NAME_RECOMMEND);
/*(id integer PRIMARY KEY , writer text, movieId integer, times text, rating float, contents text, recommend intefer */
    public static class CommentEntry implements BaseColumns{
        public static final String TABLE_NAME = "comment";
        public static final String COLUMN_NAME_ID = "id";
        public static final String COLUMN_NAME_WRITER = "writer";
        public static final String COLUMN_NAME_MOVIEID = "movieId";
        public static final String COLUMN_NAME_TIMES = "times";
        public static final String COLUMN_NAME_RATING = "rating";
        public static final String COLUMN_NAME_CONTENTS = "contents";
        public static final String COLUMN_NAME_RECOMMEND = "recommend";
    }
}

 


참고 : https://www.edwith.org/boostcourse-android/lecture/17121/

      : https://developer.android.com/reference/android/database/sqlite/SQLiteOpenHelper

 

공감과 추천은 큰 힘이됩니다.!

 

 

 

 

조회 수 :
19
등록일 :
2019.10.14
22:55:49 (*.214.125.21)
엮인글 :
http://webs.co.kr/index.php?document_srl=3320674&act=trackback&key=02b
게시글 주소 :
http://webs.co.kr/index.php?document_srl=3320674
List of Articles