2021年3月16日火曜日

Google App Engine(GAE) で Datastoreを Javaから使う

目次へ



Datastore は、スケーラビリティが高い NoSQL データベースです。GAEでも、MySQLのようなRDBを使うことはできますが、CloudFirestoreのDatastoreモードというデータベースの 使用が推奨されています。
Datastoreは、GAEのデフォルトのデータベースとして登場しました。その後、Cloud Datastoreという名前でGCPのプロダクトの一つとして提供され、GAE以外からでも利用できるようになりました。 さらに、Cloud FirestoreのDatastoreモードがCloud Datastoreの最新バージョンになりました。将来的には、既存のすべてのCloud Datastoreが、FirestoreのDatastoreモードに 自動的にアップグレードされる予定のようです。

DatastoreとRDBの用語は以下のように異なります。
RDB Datastore
カインド
レコード(列) エンティティ
フィールド(項目) プロパティ


DatastoreをJavaから操作する方法はいくつかあるようですが、ここでは、Datastore APIを使う方法を示します。パッケージは以下です。


import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.Filter;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;         

DatastoreService取得

まずは、DatastoreServiceを取得し、そのメソッドを使い、挿入、削除、更新などを行うことになります。

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();            

挿入

Datastoreでは、データを登録するためのCREATE TABLEなどは不要です。カインド(RDBの表名)を指定し、Entity のインスタンスを作り、必要なプロパティ(RDBのフィールド名)ごとに値を指定し、DatasoreServiceのputメソッドで挿入します。この時、longの値のキーが自動で追加されます。

Entity entity = new Entity("カインド名");
entity.setProperty("プロパティ名","bbbb");
datastore.put(entity);

削除

削除をするには、キーを指定してDatasoreServiceのdeleteメソッドを呼びます。引数は、可変個引数なので、いくつでも指定できます。また、Iterableを引数に渡すこともできます。
Google Cloud PlatformのDatastoreのエンティティを確認すると、登録されているエンティティのキーと、プロパティを見ることができますので、そのキーのIDを指定して削除したのが以下の例です。

Key key = KeyFactory.createKey("カインド名", 5071211717459968L);
datastore.delete(key);

キーについて

キーを、エンティティから取得するのは、以下のようになります。エンティティを取得するのは、下の項目を見てください。

datastore.delete(key);
long keyID = key.getID();

キーを指定してEntityを取得

キーを指定してエンティティを取得するのは以下の通りです。getメソッドは、EntityNotFoundExceptionをスローするため、try catchが必要になります。

Enriry entity = null;
Key key = KeyFactory.createKey("カインド名",5632499082330112L);	//Kind,ID
try {
	entity = datastore.get(key);
} catch (EntityNotFoundException e) {
	e.printStackTrace();
}
entity.getProperty("プロパティ名");  //プロパティの値を取得
entity.getKey().getID();             //キーのIDを取得

更新

挿入と同様、put(Entityのインスタンス)を実行した時、Entityのインスタンスが既に存在するキーをもっていれば新規挿入ではなく更新となります。

Enriry entity = null;
Key key = KeyFactory.createKey("カインド名",5632499082330112L);	//Kind,ID
try {
	entity = datastore.get(key);
} catch (EntityNotFoundException e) {
	e.printStackTrace();
}
entity.setProperty("プロパティ名","abc");
datastore.put(entity);

すべてを選択

あるカインドからすべてのエンティティを取得するには、Queryを使います。
下の2行目について説明します。
datastore.prepare(query)でPreparedQueryが返ります。
そのクラスのasList()で問い合わせたものをリストとして返してくれます。
また、asListの引数にはFetchOptionsオブジェクトを渡しますが、これは、リミットやオフセットなどを記述するためのクラスであり、FetchOptions.Builderクラスのメソッドでそのインスタンスを作成することができます。
withDefaultsメソッドは、デフォルトのFetchOptions.Builderクラスを作成しますので、それをasListに渡し、すべてのエンティティを取得します。

Query query = new Query("カインド名");
List<Entity> results = datastore.prepare(query).asList(FetchOptions.Builder.withDefaults());
for(Entity e : results) {
	String s = e.getProperty("a");
}

Orderの指定

あるプロパティでソートしてエンティティを取得するためには、addSortメソッドを使います。

Query q = new Query("カインド名").addSort("プロパティ名", SortDirection.ASCENDING);

条件の指定

Filterクラスで条件を書くことができます。下の条件はtoiというプロパティが1と等しいものだけを取得するFilterです。
FilterOperator.EQUAL はenumでEQUAL、GREATER_THAN、GREATER_THAN_OR_EQUAL、IN、LESS_THAN、LESS_THAN_OR_EQUAL、NOT_EQUALがあります。

Filter f = new Query.FilterPredicate("toi", FilterOperator.EQUAL, 1);
Query q = new Query("カインド名").setFilter(f);
にほんブログ村 IT技術ブログ IT技術メモへ
にほんブログ村

2021年2月28日日曜日

ECLIPSEでGoogleアカウントからサインアウトする

目次へ



EclipseのプロジェクトをGCP(Google Cloud Platform)のプロジェクトにひもづけようとした(※1)ところ Googleアカウントを入力後次のようなエラーが出てしまいました。

An error occurred while retrieving projects: com.google.api.client.auth.oauth2.TokenResponseException: 400 Bad Request
{
"error" : "invalid_grant",
"error_description" : "Bad Request"
}
com.google.api.client.auth.oauth2.TokenResponseException: 400 Bad Request
{
"error" : "invalid_grant",
"error_description" : "Bad Request"
}


Googleアカウントからサインアウトし、Eclipseを再起動する必要があるようです。
ECLIPSEでサインアウトってどうするんだろうと思いましたが、Eclipseのウィンドウの右下にある、Googleのマークをクリックし、[Sign Out Of All Accounts]のボタンを押せばOKでした。


※1 EclipseのプロジェクトをGCP(Google Cloud Platform)のプロジェクトにひもづける方法は以下です。
プロジェクトをクリックして、 Eclipseのメニューの[Project]-[Properties]を開き、左のメニューの中から、[Google Cloud Platform]-[App Engine Deployment]を クリックし、AccountにGoogle Cloud Platformで使っているアカウントを入力します。
GCPに既に作成されているプロジェクトが一覧で表示されますので、その中から今回使うプロジェクトをクリックし選択します。
にほんブログ村 IT技術ブログ IT技術メモへ
にほんブログ村

ECLIPSEで Google App Engine(GAE)

目次へ



EclipseでGoogle App Engine用のプロジェクトを作成すると、配備するのもメニューからでき、非常に簡単にできます。Eclipseのプロジェクトを作成するまでの流れは以下のようになります。
  • Google Cloud SDKをインストールをインストール

    ここの手順に従い、インストールします。
    Google Cloud SDKは、GCPを自分のコンピュータから使うためのコマンドラインツールから構成されています。

  • 未インストールなら、Eclipseをインストール

    Eclipse IDE for Java EE Developers バージョン 4.7 以降をダウンロードし、インストールします。

  • ECLIPSEにCloud Tools for ECLIPSEとECLIPSE Tools for Cloud Foundaryをインストール

    Eclipseのメニューの[help]-[Eclipse MarketPlace]を開き[ Cloud Tools for Eclipse]で検索すると、両方表示されますので2つともインストールします。

    ※ Google Plugin for Eclipse が2018年01月で廃止されCloud Tools for Eclipseに移行しましたが、このインストールプロセスにより、Google Plugin for Eclipseがアンインストールされるようなので、Google Plugin for Eclipseから、Cloud Tools for Eclipseへの移行もできるようです

  • app-engine-javaのインストール

    Google Cloud Platform(GCP)のサイトに行きその中のGoogle Cloud SDK Shell(赤丸)をクリックし、次のコマンドを実行します。
    gcloud components install app-engine-java

    ※ app-engine-javaは、Google Cloud SDKの中のgcloudのコンポーネントで、Java 8 用の App Engine 拡張機能が含まれているようですが、ちょっとよくわかっていません。
  • Cloud SDKの初期化

    上と同じGoogle Cloud SDK Shellで、gcloud initを実行します。
    これで、Google Cloud SDK の認証等が完了するようです。

  • ECLIPSEでGoogle Cloud Platform(GCP)用のプロジェクトを作成

    Eclipseのメニューの[file]-[new]-[project]を開き[Google Cloud Platform]の中にあるプロジェクトを作成します。私は、Google App Engine Standard Java Projectを作成しましたが、フレキシブルとスタンダードの違いについてはここに説明があります

  • ECLIPSEのプロジェクトのプロパティでGoogle Cloud Platform(GCP)で作成したプロジェクト名を指定

    Eclipseのプロジェクトを右クリックし[Project]-[Properties]を開き、左のメニューの中から、[Google Cloud Platform]-[App Engine Deployment]を クリックし、AccountにGoogle Cloud Platformで使っているアカウントを入力します。
    GCPに既に作成されているプロジェクト名とプロジェクトIDが一覧で表示されますので、その中から今回使うGCPのプロジェクトをクリックし選択します。

  • Google Cloud Platform(GCP)にデプロイ

    まだ、何もプログラムを書いていない状態で、Eclipseのプロジェクトを右クリックし[App Engine Standardにデプロイ]をクリックするとデプロイが開始し、少し時間がかかりますが、下のようなブラウザが表示されればデプロイ成功です。


にほんブログ村 IT技術ブログ IT技術メモへ
にほんブログ村

2017年2月27日月曜日

Android AsyncTask 使い方

目次へ



Viewの描画に関する処理は、UIスレッド(メインスレッド)で行う必要があります。
別のスレッドで何かを実行し、その途中、あるいは、別のスレッドでの処理が終わってから、UIスレッドで描画を行いたい場合には、AsyncTaskが便利です。

AsyncTaskの使い方は、Swingの、SwingWorkerの使い方と似ています。

また、Androidには、UIスレッド以外のスレッドから描画ができる特別なSurfaceViewというViewもあります。

■■■■サンプル■■■■

下のサンプルは、Activityが作成されると、onCreateメソッドの中でAsyncTaskを継承したクラスのインスタンスを作成し、スタートさせています。

AsyncTaskには、次のようなメソッドがあるので、オーバーライドして使います。
  • doInBackground⑤
    UIスレッドとは、別のスレッドで実行されるのでViewの描画などはできません
  • onPostExecute⑦
    doInBackgroundが終わると、UIスレッドで実行されるので、Viewの描画などが行えます
  • onProgressUpdate⑧
    doInBackgrounからpublishProgressを呼び出す⑥と実行され、UIスレッドで実行されます


また、④を見ると、ジェネリックで次のように型が3つ指定されています。
AsyncTask<Object, String, Integer>
この3つの型を①、②、③とすると、各メソッドの引数に対応します。
  • ① doInBackgroundの引数。

    ⑤のように、引数に...を書くのは、可変個引数の意味で、ここでは、引数があってもなくてもいいという意味で使っています。
    このクラスのインスタンスを作りexecuteメソッドを呼び出すとdoInBackgroundが呼び出されるため、execute呼び出し時にdoInBackgroundに渡したいものを渡しますが、 この例では、doInBackgroundに何も渡すものがないので、execute()として呼び出しています。

  • ② onProgressUpdateの引数。

    ⑥でpublishProgressを呼び出すと、onProgressUpdateが呼び出されるため⑥の引数が②の型となります。
    また、publishProgressが呼び出されるたびに、すぐにonProgressUpdateが呼び出されるとは限らず、複数回のpublishProgress呼び出しに対して、onProgressUpdateが1回にまとめられることがあるため、複数回の引数が可変個引数として、受け取れるようになっています。
    例では String... values となっています。

  • ③ onPostExecuteの引数

    doInBackgroundが終了すると、その戻り値を引数としてonPostExecuteが呼び出されますので、doInBackgroundの戻り値もこの型となります。

import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 次の2行で別スレッドでの実行が始まる
        B b = new B();
        b.execute(); //①の型の引数を渡すが可変個引数なので無でもOK
    }
              //①     ②   ③
    class B extends AsyncTask<Object, String, Integer> { //④
                       
                   //①
        protected Void doInBackground(Object... params) { //⑤
            for(int i=0; i<10; i++) {
                publishProgress("i=" + i);  //⑥
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
         //③
            return new Integer(13);
        }

                  //③        
        protected void onPostExecute(Integer i) {  //⑦
            Toast.makeText(MainActivity.this,i+"で終わった",
               Toast.LENGTH_SHORT).show();

        }
                    //②
        protected void onProgressUpdate(String... values) {//⑧
            for(String v:values) {
                Toast.makeText(MainActivity.this, v, 
               Toast.LENGTH_SHORT).show();
            }
        }
    }
}



にほんブログ村 IT技術ブログ IT技術メモへ
にほんブログ村

2017年1月14日土曜日

Android の GridLayout概要

目次へ



GridLayoutでは、列数、行数を指定しておき、その中に部品を置くと左上から順にWidgetが配置されていきます。 swingのGridLayoutと似た使い方です。
また、行と列を指定して、Widgetを好きな位置に配置することもできます。

■■■■GridLayout例(順番に配置)

レイアウトファイル
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:columnCount="3"        列数
    android:rowCount="3"          行数
    android:layout_gravity="center_horizontal">中央揃え
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="1"
        android:onClick="click" このボタンをクリックしたときのメソッド名
        />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="2"
        android:onClick="click"
        />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="3"
        android:onClick="click"
        />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="4"
        android:onClick="click"

    以下同様に9のボタンまで作成します。

</GridLayout>


ボタンをクリックしたときのメソッド名をclickとしたので、Activityのクラスの中に
public void click(View v) {・・・}を作成します。
public class GridLayoutTest extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.third);
    }

    public void click(View v) {
        Button b = (Button)v;
        String s = b.getText()+"がクリックされました";
        Toast toast = Toast.makeText(this, s, Toast.LENGTH_SHORT);
        toast.show();
    }
}


上のような指定をした場合、次のような画面が表示されます。


■■■■GridLayout例(行と列を指定して配置)

レイアウトファイル
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:columnCount="2"
    android:rowCount="3" >
         <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="1" /> 行と列を指定していないので先頭

         <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_row="1"
            android:layout_column="0"
            android:text="2" />

         <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="3" />

         <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_row="2"
            android:layout_column="1"
            android:text="4" />
</GridLayout>


上のような指定をした場合、次のような画面が表示されます。



にほんブログ村 IT技術ブログ IT技術メモへ
にほんブログ村

Android の FrameLayout概要

目次へ



FrameLayoutでは、その中に部品を置くとその上に重なって表示されていきます。下の例は、それを使って画像の上にボタンともうひとつのが像を重ねています。

■■■■FrameLayout例

レイアウトファイル
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ImageView
        android:id="@+id/imageView1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/img1"/ />
    <ImageView
        android:id="@+id/imageView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical|center_horizontal"
        android:src="@mipmap/ic_launcher" />
  <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="ボタン"/>


</FrameLayout>


上のような指定をした場合、次のような画面が表示されます。


にほんブログ村 IT技術ブログ IT技術メモへ
にほんブログ村

2017年1月11日水曜日

Android ダイアログの使い方

目次へ



Androidでダイアログを作成する方法をまとめておきます。
ここで使うダイアログはすべてAlertDialogクラスを使い次のようなものを表示します。

  • メッセージを表示するだけのダイアログ
  • ボタンがあり、ユーザがボタンをタップし答えることのできるダイアログ
  • 複数の項目から選択をするダイアログ
  • ラジオボタンとボタンを表示するするダイアログ
  • チェックボックスとボタンを表示するするダイアログ




■■■■ダイアログを表示するためのボタンを作っておく



最初に5種類のダイアログを表示するためのボタンを作り、btn1~btn5の名前で使えるようにしておきます。
ボタンの作り方はこちら、あるいは、こちらなどで検索してください。

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

        Button btn1 = (Button)this.findViewById(R.id.button1);
        Button btn2 = (Button)this.findViewById(R.id.button2);
        Button btn3 = (Button)this.findViewById(R.id.button3);
        Button btn4 = (Button)this.findViewById(R.id.button4);
        Button btn5 = (Button)this.findViewById(R.id.button5);
    この下に、次から示すボタンのリスナを書く




■■■■メッセージを表示するだけのダイアログ



btn1をタップされた時の処理としてメッセージを表示するだけのダイアログを出すのが下のコードです。
この中の①のMainActivity.thisはActivityのインスタンスです。
OnClickListenerの中で使っているために、thisではなく、自分のクラス名.thisとしています。

if (btn1 != null) {
    btn1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);①
            builder.setTitle("タイトル");
            builder.setMessage("AlertDialogです");
            builder.show();
        }
    });
}



■■■■ボタンで答えるダイアログ



btn2をタップされた時の処理として上のようなダイアログを出すのが下のコードです。

ダイアログの中のボタンは、必要なものだけ、作成すればよいので、OKだけならbuilder.setPositiveButtonメソッドだけを呼び出せばよいことになります。

if (btn2 != null) {
    btn2.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
            builder.setTitle("タイトル");
            builder.setMessage("ボタンのあるAlertDialogです");
            //3つのボタンのうち必要なボタンだけ作成すればよい
            builder.setPositiveButton("はい", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    Toast toast = Toast.makeText(MainActivity.this, "OK", Toast.LENGTH_SHORT);
                    toast.show();
                }
            });
            builder.setNegativeButton("いいえ", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    Toast toast = Toast.makeText(MainActivity.this, "NO", Toast.LENGTH_SHORT);
                    toast.show();
                }
            });
            builder.setNeutralButton("またね", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    Toast toast=Toast.makeText(MainActivity.this,"またね",Toast.LENGTH_SHORT);
                    toast.show();
                }
            });
            builder.show();
        }
    });
}




■■■■複数の項目から選択をするダイアログ



btn3をタップされた時の処理として上のような複数の項目から選択をするダイアログを出すのが下のコードです。

このとき、選択肢の文字列はフィールドで定義しておきます。

   String[] ss = {"選択1", "選択2", "選択3"};
    int selectedItem = 0;
    boolean[] selectedItems = {false, false, false};

if (btn3 != null) {
    btn3.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
            builder.setTitle("選択してください");
            builder.setItems((CharSequence[]) ss, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int k) {
                    Toast toast=Toast.makeText(MainActivity.this,ss[k],Toast.LENGTH_SHORT);
                    toast.show();
                }
            });
            builder.show();
        }
    });
}




■■■■ラジオボタンとボタンを表示するするダイアログ



btn4をタップされた時の処理として上のようなダイアログを出すのが下のコードです。
このとき、複数の項目から選択をするダイアログの時と同じようにフィールドで選択肢の文字列を定義しておきます。また、ラジオボタンを押したときにその番号を入れておくためのselectedItemもフィールドで定義しておきます。

   String[] ss = {"選択1", "選択2", "選択3"};
    int selectedItem = 0;


if (btn4 != null) {
    btn4.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
            builder.setTitle("選択してからOKボタンを押してください");
            builder.setSingleChoiceItems((CharSequence[]) ss, 0, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    selectedItem = which;
                    Toast toast = Toast.makeText(MainActivity.this, ss[which], Toast.LENGTH_SHORT);
                    toast.show();
                }
            });
            builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    Toast toast = Toast.makeText(MainActivity.this, ss[selectedItem], Toast.LENGTH_SHORT);
                    toast.show();
                }
            });
            builder.show();
        }
    });
}



■■■■チェックボックスとボタンを表示するするダイアログ



btn5をタップされた時の処理として上のようなダイアログを出すのが下のコードです。
このとき、フィールドで選択肢の文字列と、どこにチェックが入っているかを示すbooleanの配列が必要です。
   String[] ss = {"選択1", "選択2", "選択3"};
   boolean[] selectedItems = {false, false, false};


    btn5.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
            builder.setTitle("選択してからOKボタンを押してください");
            builder.setMultiChoiceItems((CharSequence [])ss, selectedItems, 
                                 new DialogInterface.OnMultiChoiceClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int k, boolean isChecked) {
                    selectedItems[which] = isChecked;
                    Toast toast = Toast.makeText(MainActivity.this, ss[k], Toast.LENGTH_SHORT);
                    toast.show();
                }
            });
            builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    String s = "選択されているのは";
                    for(int i=0; i<3; i++) {
                        if(selectedItems[i]) {
                            s += ss[i]+" ";
                        }
                    }
                    Toast toast = Toast.makeText(MainActivity.this, s, Toast.LENGTH_SHORT);
                    toast.show();
                }
            });
            builder.show();
        }
    });
}



にほんブログ村 IT技術ブログ IT技術メモへ
にほんブログ村