- 投稿日:2019-08-30T23:14:00+09:00
ラムダ式とは
ラムダ式とは
関数型言語由来の概念。
関数型言語とは「関数を値と同じ様に扱える」言語。"値"というのは数値とか文字列とかリストとか。
値と同じ様に扱えるとは、
- 変数に代入できて
- 関数の引数に渡せて
- 関数の戻り値にできる
ということ。
例えば、引数に値をとり、それに10を足して返す関数
F(x) = x + 10のような関数。
関数型言語ではこの関数を値として取り回せる。では、上記の関数を変数に代入するにはどうすればよいか。
この「値として取り回せる関数の書き方」がラムダ式。例えば、関数型言語のHaskellでは上記の関数は
f = x -> x + 10というふうに記述できる。
「【数値xを受け取り、それに10を足した値を返す関数】を、fという変数に代入した」と思って下さい。
この書き方は言語により異なるが、どれもラムダ式である。
どれも「値として取り回せる関数の書き方」を定義したものだから。
-> とか => とか細かな書き方の違いは関係ありません。と、ここまでがラムダ式の話なのだがJavaやC#では少々事情が異なる。
これらはクラスあっての言語なので関数はクラスの中、つまりメソッドという形でしか存在できないからです。
なので、function(C#だとFunc)インターフェイスを作り、擬似的に関数として取り回せる様にしている。
例えばJavaで
Function<Integer, Integer>f = new Funciton<>(){ public Integer apply(Integer x){ return x + 10; } }の省略記法に過ぎないわけです。
単なるインスタンスなので、変数に代入できるし、引数にも戻り地にも出来る、というわけです。
以上がラムダ式の説明になります。
- 「そもそもラムダ式とはなんなのか」
- 「(関数型言語由来の概念である)ラムダ式を、オブジェクト志向言語に落とし込むには?」
がごっちゃになると一気にわけがわからなくなるので、この2つは分けて考えたほうが良いですね。
- 投稿日:2019-08-30T19:59:38+09:00
digdag の docker.run_options を少しだけ試した
digdag の docker.run_options
digdagのバージョン
0.9.37
から docker に関する オプションが使えるようになったようです。リリースノート : https://docs.digdag.io/releases/release-0.9.37.html?highlight=run_options
該当PR : https://github.com/treasure-data/digdag/pull/1025
PR の方から引用すると、以下のように設定できるみたいです。
_export: docker: image: ruby:2.5.1 build: - apt install vim docker: /usr/local/bin/nvidia-docker run_options: ["-v", "/tmp:/tmp"] build_options: ["--pull"]ここの
run_options: ["-v", "/tmp:/tmp"]
ですが、run_options: ["-v /tmp:/tmp"]
みたいにできないものか と思って試したらdocker: Error response from daemon: create /: " /" includes invalid characters for a local volume name, only "[a-zA-Z0-9][a-zA-Z0-9_.-]" are allowed. If you intended to pass a host directory, use absolute path.ってエラーが出るので出来なかったよ って話です (
build_options
でも同様ですね.)digdag の DEBUGログを見てみる
digdag run -l debug
で デバッグログを出力することができます。
run_options: ["-v", "/tmp:/tmp"]
のときに実行されるコマンド2019-08-30 19:19:07 +0900 [DEBUG] (0017@[0:default]+sample_wf+sample_docker_run_options) io.digdag.standards.command.DockerCommandExecutor: Running in docker: docker run -v /tmp:/tmp -i --rm -v ..略
run_options: ["-v /tmp:/tmp"]
のときに実行されるコマンド2019-08-30 19:20:22 +0900 [DEBUG] (0017@[0:default]+sample_wf+sample_docker_run_options) io.digdag.standards.command.DockerCommandExecutor: Running in docker: docker run -v /tmp:/tmp -i --rm -v ..略DEBUGログに表示される
docker
コマンドは全く一緒で、 どちらもコマンド的には問題なさそう。コマンドだけをコピって ローカルのターミナルで実行すると実行できます。
上記の
docker: Error response from daemon
みたいなエラーは出ません。ではなぜ出来ないか
run_options
は この部分 でcommand
と宣言されたImmutableList
にaddAll
されています。この変数
command
は最終的にProcessBuilder docker = new ProcessBuilder(command.build());
としてProcessBuilder
に渡されます。
ProcessBuilder
はプロセスの実行前にコマンドを一つにつなげる訳でもなく、command.toArray(new String[command.size()]);
とするので、run_options: ["-v /tmp:/tmp"]
と定義してしまうと、それが一つのコマンドとして入力されてしまうんですねぇ..?つまり、イメージ的には
run_options: ["-v", "/tmp:/tmp"]
のときに実行されるコマンド"docker" "run" "-v" "/tmp:/tmp" "nannka-image:0.1"
run_options: ["-v /tmp:/tmp"]
のときに実行されるコマンド"docker" "run" "-v /tmp:/tmp" "nannka-image:0.1"※ 上記を実行すると
docker: Error response from daemon
が発生します.まとめ
DEBUG ログでは、
logger.debug("Running in docker: {} {}", command.build().stream().collect(Collectors.joining(" ")), imageName);とされてたので、
run_options: ["-v /tmp:/tmp"]
でも問題ないように見えたんですが、実際にはProcessBuilder
による実行なので、問題あるんだよって話でした。「そらそうよ。だから配列なんやで。」 って感じですかね。
何か間違っている点等がありましたら、ご指摘いただけると幸いです。
- 投稿日:2019-08-30T19:59:38+09:00
digdag の docker.run_options 少しだけ試した
digdag の docker.run_options
digdagのバージョン
0.9.37
から docker に関する オプションが使えるようになったようです。リリースノート : https://docs.digdag.io/releases/release-0.9.37.html?highlight=run_options
該当PR : https://github.com/treasure-data/digdag/pull/1025
PR の方から引用すると、以下のように設定できるみたいです。
_export: docker: image: ruby:2.5.1 build: - apt install vim docker: /usr/local/bin/nvidia-docker run_options: ["-v", "/tmp:/tmp"] build_options: ["--pull"]ここの
run_options: ["-v", "/tmp:/tmp"]
ですが、run_options: ["-v /tmp:/tmp"]
みたいにできないものか と思って試したらdocker: Error response from daemon: create /: " /" includes invalid characters for a local volume name, only "[a-zA-Z0-9][a-zA-Z0-9_.-]" are allowed. If you intended to pass a host directory, use absolute path.ってエラーが出るので出来なかったよ って話 (
build_options
でも同様ですね.)digdag の DEBUGログを見てみる
digdag run -l debug
で デバッグログを出力することができます。
run_options: ["-v", "/tmp:/tmp"]
のときに実行されるコマンド2019-08-30 19:19:07 +0900 [DEBUG] (0017@[0:default]+sample_wf+sample_docker_run_options) io.digdag.standards.command.DockerCommandExecutor: Running in docker: docker run -v /tmp:/tmp -i --rm -v ..略
run_options: ["-v /tmp:/tmp"]
のときに実行されるコマンド2019-08-30 19:20:22 +0900 [DEBUG] (0017@[0:default]+sample_wf+sample_docker_run_options) io.digdag.standards.command.DockerCommandExecutor: Running in docker: docker run -v /tmp:/tmp -i --rm -v ..略DEBUGログに表示される
docker
コマンドは全く一緒で、 どちらもコマンド的には問題なさそう。コマンドだけをコピって ローカルのターミナルで実行すると実行できます。
上記の
docker: Error response from daemon
みたいなエラーは出ません。ではなぜ出来ないか
run_options
は この部分 でcommand
と宣言されたImmutableList
にaddAll
されています。この変数
command
は最終的にProcessBuilder docker = new ProcessBuilder(command.build());
としてProcessBuilder
に渡されます。
ProcessBuilder
はプロセスの実行前にコマンドを一つにつなげる訳でもなく、command.toArray(new String[command.size()]);
とするので、run_options: ["-v /tmp:/tmp"]
と定義してしまうと、それが一つのコマンドとして入力されてしまうんですねぇ..?つまり、イメージ的には
run_options: ["-v", "/tmp:/tmp"]
のときに実行されるコマンド"docker" "run" "-v" "/tmp:/tmp" "nannka-image:0.1"
run_options: ["-v /tmp:/tmp"]
のときに実行されるコマンド"docker" "run" "-v /tmp:/tmp" "nannka-image:0.1"※ 上記を実行すると
docker: Error response from daemon
が発生します.まとめ
DEBUG ログでは、
logger.debug("Running in docker: {} {}", command.build().stream().collect(Collectors.joining(" ")), imageName);とされてたので、
run_options: ["-v /tmp:/tmp"]
でも問題ないように見えたんですが、実際にはProcessBuilder
による実行なので、問題あるんだよって話でした。「そらそうよ。だから配列なんやで。」 って感じですかね。
何か間違っている点等がありましたら、ご指摘いただけると幸いです。
- 投稿日:2019-08-30T19:08:15+09:00
音ゲー作成までの道のり3
前回までのあらすじと今回の成果
前回までは、メトロノームを作成し端末を左右に振れば音が出る、というところまで作成できた。
そこから今回はBGMを付けようと思い簡易的なミュージックプレイヤーを作成したので下記にレイアウトとソースコードを記載する。レイアウト
activiti_main.xml<SeekBar android:id="@+id/Time_Seek" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="75dp" android:layout_marginLeft="75dp" android:layout_marginEnd="75dp" android:layout_marginRight="75dp" android:layout_marginBottom="250dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" /> <TextView android:id="@+id/Time_text1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="75dp" android:layout_marginLeft="75dp" android:layout_marginTop="4dp" android:text="0:00" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/Time_Seek" /> <TextView android:id="@+id/Time_text2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:layout_marginEnd="75dp" android:layout_marginRight="75dp" android:text="- 0:00" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/Time_Seek" /> <SeekBar android:id="@+id/Volume_seek" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="75dp" android:layout_marginLeft="75dp" android:layout_marginEnd="75dp" android:layout_marginRight="75dp" android:layout_marginBottom="50dp" android:max="100" android:progress="50" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> <ImageView android:id="@+id/imageView" android:layout_width="30dp" android:layout_height="30dp" android:layout_marginEnd="10dp" android:layout_marginRight="10dp" android:layout_marginBottom="38dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/Volume_seek" app:srcCompat="@drawable/sound" /> <ImageView android:id="@+id/imageView2" android:layout_width="30dp" android:layout_height="30dp" android:layout_marginStart="10dp" android:layout_marginLeft="10dp" android:layout_marginBottom="38dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toEndOf="@+id/Volume_seek" app:srcCompat="@drawable/sound2" /> <Button android:id="@+id/Music_button" android:layout_width="60dp" android:layout_height="60dp" android:layout_marginStart="150dp" android:layout_marginLeft="150dp" android:layout_marginTop="50dp" android:layout_marginEnd="150dp" android:layout_marginRight="150dp" android:layout_marginBottom="50dp" android:background="@drawable/play" app:layout_constraintBottom_toTopOf="@+id/Volume_seek" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/Time_Seek" /> <Button android:id="@+id/beat1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="75dp" android:layout_marginLeft="75dp" android:layout_marginEnd="75dp" android:layout_marginRight="75dp" android:layout_marginBottom="75dp" app:layout_constraintBottom_toTopOf="@+id/Time_Seek" app:layout_constraintEnd_toStartOf="@+id/beat2" app:layout_constraintStart_toStartOf="parent" /> <Button android:id="@+id/beat2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="75dp" android:layout_marginLeft="75dp" android:layout_marginEnd="75dp" android:layout_marginRight="75dp" android:layout_marginBottom="75dp" app:layout_constraintBottom_toTopOf="@+id/Time_Seek" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/beat1" />ソースコード
MainActivity.javapublic class MainActivity extends AppCompatActivity { private MediaPlayer mp; private Button m_but,b_but1,b_but2; private SeekBar v_bar,m_bar; private TextView s_text,e_text; private int m_time; SoundPool soundPool; int mp3; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); m_but = findViewById(R.id.Music_button); b_but1 = findViewById(R.id.beat1); b_but2 = findViewById(R.id.beat2); s_text = findViewById(R.id.Time_text1); e_text = findViewById(R.id.Time_text2); //曲の読み込み mp = MediaPlayer.create(this,R.raw.impact); mp.setLooping(true); mp.setVolume(1f,1f); m_time = mp.getDuration(); //効果音付けるのに必要なやつ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { soundPool = new SoundPool(5, AudioManager.STREAM_MUSIC, 0); } else { AudioAttributes attr = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .build(); soundPool = new SoundPool.Builder() .setAudioAttributes(attr) .setMaxStreams(5) .build(); } mp3 = soundPool.load(this, R.raw.pop, 1); m_bar = findViewById(R.id.Time_Seek); m_bar.setMax(m_time); m_bar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if(fromUser){ mp.seekTo(progress); m_bar.setProgress(progress); } } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }); v_bar = findViewById(R.id.Volume_seek); v_bar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { float v_num = progress / 100f; mp.setVolume(v_num,v_num); } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }); //再生ボタン制御 m_but.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if(mp.isPlaying()){ mp.pause(); m_but.setBackgroundResource(R.drawable.play); } else { mp.start(); m_but.setBackgroundResource(R.drawable.stop); } } }); b_but1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { soundPool.play(mp3, 2, 2, 0, 0, 1f); } }); b_but2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { soundPool.play(mp3, 2, 2, 0, 0, 1f); } }); new Thread(new Runnable() { @Override public void run() { while (mp !=null){ try { Message msg = new Message(); msg.what = mp.getCurrentPosition(); handler .sendMessage(msg); Thread.sleep(1000); } catch (InterruptedException e){} } } }).start(); } private Handler handler =new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { int currentPosition = msg.what; // 再生位置を更新 m_bar.setProgress(currentPosition); // 経過時間ラベル更新 String elapsedTime = createTimeLabel(currentPosition); s_text.setText(elapsedTime); // 残り時間ラベル更新 String remainingTime = "- " + createTimeLabel(m_time-currentPosition); e_text.setText(remainingTime); return true; } }); public String createTimeLabel(int time) { String timeLabel = ""; int min = time / 1000 / 60; int sec = time / 1000 % 60; timeLabel = min + ":"; if (sec < 10) timeLabel += "0"; timeLabel += sec; return timeLabel; } }今回の頑張り
前回から少し日が開いてしまいましたが、簡易的なミュージックプレイヤーを作成するのが結構難しく、音楽が流れなかったり、流れたのはいいが一時停止すると音楽が再生されなかったり、音楽を停止すると共にアプリも停止するし...
結構難しかったってのが感想なんですが、じゃあ何が原因だったかというと、停止した際にmpの中身をnullにしていたので再び再生する際に何かしらのエラーを起こしていたのだと私は考えています。間違ていたらすいませんm(_ _)m
ちなみに、レイアウトにある2つの何も書いてないボタンはなんぞやと思われる方が多いと思いますが、これを押すと効果音が鳴ります!...
えっ?他にはって?
・
・
・
以上です。
- 投稿日:2019-08-30T18:10:36+09:00
spring boot + spring jpaでデータベースに接続しCRUD操作
はじめに
- 前回、spring boot で web api サーバを作る で文字列を返すだけの api を作りました
- 今回は前回のコードをベースに、データの CRUD(Create, Read, Update, Delete)操作を行う api を作ります
- データベースはローカル環境に docker-compose で mysql コンテナを立ち上げる で作成した mysql コンテナを使います
- エラーハンドリングなどは考えず、一旦動く機能を実装します
環境
- macOS Mojave 10.14.6
- Java openjdk 12.0.1
- Spring Boot 2.1.7
- Intellij IDEA CE 2019.1
- docker 18.09.2
- docker-compose 1.24.1
取り扱うデータ
- 簡易版ツイッターのバックエンドサーバのようなイメージで、以下のデータを扱います
項目名 型 説明 id int ツイートに固有の ID name String ツイートした人の名前 message String ツイート内容 作成する api のイメージ
url HTTP メソッド 説明 /tweet POST ツイートを保存する /tweet GET 全ツイートを取得する /tweet/{id} PUT 指定した id のツイートを更新する /tweet/{id} DELETE 指定した id のツイートを削除する パッケージ構成
- 今回は以下のようなパッケージ構成でコードを作成していきます
src/main/java/com/example/api . ├── ApiApplication.java ├── model │ └── Tweet.java ├── repository │ └── TweetRepository.java ├── service │ └── TweetService.java └── controller └── TweetController.java
model
: エンティティ(取り扱うデータそのもの)を表すrepository
: DB へのアクセスを行うservice
: ビジネスロジックを実行するcontroller
: クライアントからのリクエスト/レスポンスを処理するbuild.gradle の設定
- データベースとデータのやり取りを行いやすくするためのライブラリを追加します
- build.gradle の dependencies に以下を追加します
org.springframework.boot:spring-boot-starter-data-jpa
RDB を java で操作するための api(JPA : Java Persistence API)を提供するライブラリmysql:mysql-connector-java
java で mysql データベースに接続するための api(JDBC : Java Database Connectivity)を提供するライブラリorg.projectlombok:lombok
java で getter / setter やコンストラクタ等のよく使うコードを自動生成してくれるライブラリ
Lombok メモ も参照くださいbuild.gradleimplementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'mysql:mysql-connector-java' compileOnly 'org.projectlombok:lombok:1.18.8' annotationProcessor 'org.projectlombok:lombok:1.18.8'DB の接続設定
src/main/resources
の下に、application.properties
またはapplication.yml
を作成し、接続する DB の情報を記載しますapplication.ymlspring: datasource: sqlScriptEncoding: UTF-8 driverClassName: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/testdb?useSSL=false&requireSSL=false username: root password: hoge # アプリケーション起動時にデータベースを初期化しないようにする jpa: hibernate: ddl-auto: update # spring boot起動時にDBにテーブル作成できない問題の対応 properties: hibernate: dialect: org.hibernate.dialect.MySQL57Dialect logging: level: sql: debug
spring.jpa.properties.hibernate.dialect
の設定を入れているのはアプリケーション起動時にテーブルを作成できずに以下のようなエラーが発生したためです。こちらを参考に解決しましたorg.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "create table tweet (tweet varchar(255) not null, primary key (tweet)) engine=MyISAM" via JDBC Statement ...(略)... Caused by: java.sql.SQLSyntaxErrorException: Specified key was too long; max key length is 1000 bytes ...(略)...Create / Read
- まずは tweet をデータベースに保存/取得する機能を実装します
- Create 時はリクエストボディ内の
name
とmessage
を DB に保存し、保存した内容をそのままレスポンスとして返します- Get 時は DB に格納されているデータを全て取得し、レスポンスとして返します
model の作成
model
パッケージ配下に、ツイート自体を表すクラスを作成しますTweet.javapackage com.example.api.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import javax.persistence.*; @Entity @Data @NoArgsConstructor @AllArgsConstructor @Table(name = "tweet") public class Tweet { @Id @GeneratedValue private int id; @Column(nullable = false) private String name; @Column(nullable = false) private String message; }
@Entity
: JPA のエンティティ(DB に保存するオブジェクト)であることを表す@Data / @NoArgsConstructor / @AllArgsConstructor
: lombok のアノテーション。getter, setter, コンストラクタなどを自動作成する@Table(name = "tweet")
: エンティティに対応するテーブル名を指定する@Id
: エンティティの主キーであることを表すアノテーション@GeneratedValue
: これをつけると DB で自動採番されるようになる@Column(nullable = false)
: DB のカラムに付与するアノテーション。nullable オプションで null を許容するかどうかを設定できるrepository の作成
repository
パッケージ配下にリポジトリクラスを作成します- CRUD 操作に必要なメソッドは JpaRepository に含まれているため、JpaRepository を継承したインターフェースを作成するだけで OK です
TweetRepository.javapackage com.example.api.repository; import com.example.api.model.Tweet; import org.springframework.data.jpa.repository.JpaRepository; public interface TweetRepository extends JpaRepository<Tweet, Integer> {}service の作成
service
パッケージ配下にサービスクラスを作成します- tweetRepository に
@Autowired
を付けることでインスタンスが DI コンテナから渡され、new することなくメソッドを呼び出せるようになります- postTweet メソッドで、Tweet を受け取り、tweetRepository の save メソッドを呼び出しています
- getTweet メソッドで、tweetRepository の findAll メソッドを呼び出しています。
TweetService.javapackage com.example.api.service; import com.example.api.model.Tweet; import com.example.api.repository.TweetRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; @Service @Transactional public class TweetService { @Autowired TweetRepository tweetRepository; public Tweet postTweet(Tweet tweet) { return tweetRepository.save(tweet); } public List<Tweet> getTweet() { return tweetRepository.findAll(); } }
@Service
: spring MVC のサービスクラスであることを表す。DI (Dependency Injection) の対象となる@Transactional
: トランザクション制御を行うためのアノテーション@Autowired
: DI コンテナのインジェクション対象に付けるアノテーションcontroller の作成
controller
パッケージ配下にコントローラクラスを作成します@RequestMapping()
アノテーションを使い、tweet
というパスに対して POST メソッドで呼び出された場合は postTweet メソッドを、GET メソッドで呼び出された場合は getTweet メソッドが呼び出されるようにしています- postTweet メソッドでは
@RequestBody
アノテーションを使って HTTP リクエストのボディを Tweet オブジェクトにマッピングし、tweetService の postTweet メソッドに渡しています。tweetService.postTweet()の実行後、結果を返却していますTweetController.javapackage com.example.api.controller; import com.example.api.model.Tweet; import com.example.api.service.TweetService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("tweet") public class TweetController { @Autowired TweetService tweetService; @RequestMapping(method = RequestMethod.POST) Tweet postTweet(@RequestBody Tweet tweet) { return tweetService.postTweet(tweet); } @RequestMapping(method = RequestMethod.GET) List<Tweet> getTweet() { return tweetService.getTweet(); } }
@RestController
: コントローラクラスに付与するアノテーション@RequestMapping()
: HTTP リクエストを受け付けるためのアノテーション。リクエストを受け付けるパスや HTTP メソッドを指定する@RequestBody
: リクエストのボディを指定したオブジェクトにマッピングするアノテーション動作確認
- アプリケーションを起動し、ターミナルから以下のコマンドを実行します
# post request $ curl -X POST -H 'Content-Type:application/json' -d '{"name":"名前", "message":"メッセージ"}' localhost:8080/tweet # response {"id":1,"name":"名前","message":"メッセージ"}
- データベースを確認し、リクエストした内容が保存されていれば OK です
mysql> select * from tweet; +----+-----------------+--------+ | id | message | name | +----+-----------------+--------+ | 1 | メッセージ | 名前 | +----+-----------------+--------+ 1 row in set (0.01 sec)
- 次に、データベースの内容が取得できるか確認するため、ターミナルから以下のコマンドを実行します
# get request curl -X GET -H 'Content-Type:application/json' localhost:8080/tweet # response [{"id":1,"name":"名前","message":"メッセージ"}]
- データベース内のレコードを取得できました!
Update / Delete
- 続いて、
/tweet/{id}
で指定された id の tweet を更新/削除する機能を実装します- JpaRepository には update/delete に用いるメソッドが既に実装されているため、repository の修正は不要です。(service から呼び出すだけ)
service の修正
- TweetService.java のクラス内に以下のメソッドを追加します
TweetService.javapublic Tweet updateTweet(Tweet tweet) { return tweetRepository.save(tweet); } public void deleteTweet(Integer id) { tweetRepository.deleteById(id); }
- update は create と同じ
save()
メソッドを用います- delete は id で指定したエンティティを削除する
deleteById()
メソッドを用いますcontroller の修正
- TweetController.java のクラス内に以下のメソッドを追加します
TweetController.java@RequestMapping(value = "{id}", method = RequestMethod.PUT) Tweet putTweet(@PathVariable("id") Integer id, @RequestBody Tweet tweet) { tweet.setId(id); return tweetService.updateTweet(tweet); } @RequestMapping(value = "{id}", method = RequestMethod.DELETE) void deleteTweet(@PathVariable("id") Integer id) { tweetService.deleteTweet(id); }
@RequestMapping
の value オプションに{id}
を指定し、/tweet/{id}
でリクエストされた場合に該当のメソッドが呼ばれるように設定します@PathVariable
アノテーションで URL 内の変数 {id} を Integer の id という変数にマッピングします- 更新時は、id を setTweet()メソッドでセットしてから tweetService の updateTweet()メソッドに渡します
動作確認
- アプリケーションを起動し、ターミナルから以下のコマンドを実行します
# put request $ curl -X PUT -H 'Content-Type:application/json' -d '{"name":"更新後の名前", "message":"更新後のメッセージ"}' localhost:8080/tweet/1 # response {"id":1,"name":"更新後の名前","message":"更新後のメッセージ"}
- データベースを確認し、データが更新されていることを確認します
mysql> select * from tweet; +----+-----------------------------+--------------------+ | id | message | name | +----+-----------------------------+--------------------+ | 1 | 更新後のメッセージ | 更新後の名前 | +----+-----------------------------+--------------------+ 1 row in set (0.01 sec)
- 最後に、データの削除を確認するため、ターミナルから以下のコマンドを実行します
# delete request curl -X DELETE -H 'Content-Type:application/json' localhost:8080/tweet/1 # responseは無し
- データベースを確認し、データが削除されていれば OK です!
mysql> select * from tweet; Empty set (0.00 sec)参考
- 投稿日:2019-08-30T16:35:37+09:00
オブジェクト指向とは?
10年くらいJavaのシステム開発に携わってきました。
何度かオブジェクト指向とは?という話を、現場で話したり、本を読んだり、ネットで調べたり。そんな中、現場面接でオブジェクト指向の良い所は?と聞かれました。
その時は、??ってなったので、改めて思考を整理して、言語化してみました。私と同じように、現場経験が長いけど、オブジェクト指向とは?と改めて聞かれると、??となる方。
そんな方の思考整理のキッカケになればと思います。参考サイト
整理する前のオブジェクト指向とは?
現実世界の考え方、見え方、をプログラミングの世界にそのまま持ってきている。
それゆえに、オブジェクト指向で作られたプログラムは、直感的に分かり易い(=現実世界と似ているから)と思っていました。
でも、あれ?ほんとう?今まで見たプログラムってそうだった?
と自問自答した結果、別の結論に至りました。整理後のオブジェクト指向とは?
参考サイトのお言葉をお借りします。
「オブジェクト指向では全体の機能を一枚岩ととらえずに,データと手続きを持った「オブジェクト」の集まりとしてとらえる」
これにつきます。
それゆえに、オブジェクト指向で作られたプログラムは、個々の部品の独立性が高いので、保守性や再利用性が高くなり、開発工数、改修工数が従来より少なくなり、生産性が上がるという結論に至っています。
「オブジェクト指向では全体の機能を一枚岩ととらえずに,データと手続きを持った「オブジェクト」の集まりとしてとらえる」
私の理解を付け加えると、
「データ」・・・ 属性
「手続き」・・・振る舞い
を持った「オブジェクト」が、「集まったなら出来ること」が、それが「機能」。開発工数、改修工数が従来より少なくなり、生産性が上がるという結論
オブジェクトは、独立しています。独立性が大切。
開発工数を少なくできる(再利用性)
独立した「たろすけオブジェクト」は、A機能にも、B機能の集合にも参加できます。
それって、A機能を作る時に、「たろすけオブジェクト」を作れば、B機能を作るときに、改めて作らずに済む。改修工数が少なくなる(保守性)
独立した「たろすけオブジェクト」は、A機能にも、B機能の集合にも参加しているので、
A機能、B機能に共通した「手続き」を直す場合、「たろすけオブジェクト」の1か所の修正で済む。上記をベースに3大特徴を自分の言葉で整理すると
ポリモーフィズム
指示する側が楽チン。
具体的に言うと、「ユーザ管理画面を作ってほしー」って思った時に、
指示する相手が、「たろすけ」だろうが、「からすけ」だろうが「ユーザ管理画面を作ってください」って指示すれば、作ってくれるということ。
中身は、「たろすけ」と「からすけ」で、個性に富んだ「ユーザ管理画面」が出来ます。更にいえば、「ものすけ」が出てきても、同じ指示で大丈夫です。
継承
共通の親分類にお任せ。
具体的に言うと、「たろすけ」と「からすけ」で同じ手続きは、「すけ分類」の「親」、「おやすけ」を作って、そこにお任せする。
という感じですかね。これも良い所は、「ものすけ」が出てきても、その手続きを使えるということ。
カプセル化
指示する側は、具体的な作業の中身を知らなくてよい。
これは、システム開発現場では、丸投げに当たりそうなので、お勧めできませんが、プログラム開発においては楽チンです。ポリモーフィズムで、お話した「ユーザ管理画面を作ってほしー」って思った時に、
指示する相手が、「たろすけ」だろうが、「からすけ」だろうが「ユーザ管理画面を作ってください」って指示すれば、作ってくれる。指示する側は、作り方を知らなくても良い。
というお話。
あれ?結局、オブジェクト指向って何が良いの?
独立性が高いから、開発工数、改修工数が従来より少なくなり、生産性が上がる。
かな。とはいえば、世の中には、銀の弾丸があるわけでは無いです。
あれば、みんな使っています(笑)
なので、全てにカッチリ当てはまるとは思っていません。ただ、便利な手法の1つとして、捉える。
使える所で使う。
っていうのが、良いかと思います。駄文
頭で整理できた!って思って、言語化すると、色々詰まりますw
言語化は大切ですねw
- 投稿日:2019-08-30T08:29:19+09:00
Spring Boot + Spring JDBC で MySQL に接続するための設定
概要
- Spring Boot + Spring JDBC (JdbcTemplate など) で MySQL に接続するための設定方法をまとめる
pom.xml
JDBC ドライバである MySQL Connector/J を利用するため、Maven の設定ファイル pom.xml の dependencies に以下を追加する。
pom.xml<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.17</version> <scope>runtime</scope> </dependency>また、 Spring JDBC を導入するため以下も追加する。
pom.xml<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency>application.properties
Spring Boot の設定ファイル application.properties に以下のような設定が必要。
application.propertiesspring.datasource.url=jdbc:mysql://localhost:3306/testdb spring.datasource.username=java spring.datasource.password=cafebabe spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.initialization-mode=always spring.datasource.schema=classpath:database/schema.sql spring.datasource.data=classpath:database/data.sql spring.datasource.sql-script-encoding=utf-8spring.datasource.url
MySQL の接続 URL。
MySQL :: MySQL Connector/J 8.0 Developer Guide :: 6.2 Connection URL Syntax
protocol//[hosts][/database][?properties]
spring.datasource.username
MySQL にアクセスするためのユーザー名。
spring.datasource.password
MySQL にアクセスするユーザーのパスワード。
spring.datasource.driver-class-name
JDBC ドライバのクラス名 com.mysql.cj.jdbc.Driver を指定する。
MySQL :: MySQL Connector/J 8.0 Developer Guide :: 6.1 Driver/Datasource Class Name
The name of the class that implements java.sql.Driver in MySQL Connector/J is com.mysql.cj.jdbc.Driver.
spring.datasource.initialization-mode
Database schema initialization mode. SQL 文を書いたスクリプトを実行する場合は always を指定する。
Spring Boot Reference Guide 2.1.7.RELEASE - Appendix A. Common application properties
spring.datasource.initialization-mode=embedded # Initialize the datasource with available DDL and DML scripts.
spring.datasource.schema
Spring Boot 起動時に実行したい SQL 文を記述したファイルのパスを指定する。
DDL (Data Definition Language: データ定義言語) テーブル作成などの実行用。application.propertiesspring.datasource.schema=classpath:database/schema.sql“How-to” Guides - 10. Database Initialization
Spring Boot can automatically create the schema (DDL scripts) of your DataSource and initialize it (DML scripts). It loads SQL from the standard root classpath locations: schema.sql and data.sql, respectively. In addition, Spring Boot processes the schema-\${platform}.sql and data-\${platform}.sql files (if present), where platform is the value of spring.datasource.platform. This allows you to switch to database-specific scripts if necessary. For example, you might choose to set it to the vendor name of the database (hsqldb, h2, oracle, mysql, postgresql, and so on).
spring.datasource.data
Spring Boot 起動時に実行したい SQL 文を記述したファイルのパスを指定する。
DML (Data Manipulation Language: データ操作言語) レコード追加などの実行用。application.propertiesspring.datasource.data=classpath:database/data.sqlspring.datasource.sql-script-encoding
SQL 文を書いたファイルの文字エンコーディングを指定する。
application.properties の資料
Spring Boot Reference Guide 2.1.7.RELEASE - Appendix A. Common application properties
application.properties# DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties) spring.datasource.continue-on-error=false # Whether to stop if an error occurs while initializing the database. spring.datasource.data= # Data (DML) script resource references. spring.datasource.data-username= # Username of the database to execute DML scripts (if different). spring.datasource.data-password= # Password of the database to execute DML scripts (if different). spring.datasource.dbcp2.*= # Commons DBCP2 specific settings spring.datasource.driver-class-name= # Fully qualified name of the JDBC driver. Auto-detected based on the URL by default. spring.datasource.generate-unique-name=false # Whether to generate a random datasource name. spring.datasource.hikari.*= # Hikari specific settings spring.datasource.initialization-mode=embedded # Initialize the datasource with available DDL and DML scripts. spring.datasource.jmx-enabled=false # Whether to enable JMX support (if provided by the underlying pool). spring.datasource.jndi-name= # JNDI location of the datasource. Class, url, username & password are ignored when set. spring.datasource.name= # Name of the datasource. Default to "testdb" when using an embedded database. spring.datasource.password= # Login password of the database. spring.datasource.platform=all # Platform to use in the DDL or DML scripts (such as schema-${platform}.sql or data-${platform}.sql). spring.datasource.schema= # Schema (DDL) script resource references. spring.datasource.schema-username= # Username of the database to execute DDL scripts (if different). spring.datasource.schema-password= # Password of the database to execute DDL scripts (if different). spring.datasource.separator=; # Statement separator in SQL initialization scripts. spring.datasource.sql-script-encoding= # SQL scripts encoding. spring.datasource.tomcat.*= # Tomcat datasource specific settings spring.datasource.type= # Fully qualified name of the connection pool implementation to use. By default, it is auto-detected from the classpath. spring.datasource.url= # JDBC URL of the database. spring.datasource.username= # Login username of the database. spring.datasource.xa.data-source-class-name= # XA datasource fully qualified name. spring.datasource.xa.properties= # Properties to pass to the XA data source.参考資料
- Spring Boot Features - 10. Working with SQL Databases
- “How-to” Guides - 10. Database Initialization
- Spring Boot Reference Guide 2.1.7.RELEASE - Appendix A. Common application properties
- Spring Boot Reference Documentation - Appendices - Appendix A: Common application properties
- MySQL :: Download Connector/J
- MySQL :: MySQL Connector/J 8.0 Developer Guide :: 6 Connector/J Reference
- Spring Boot で Spring JDBC を使う - Qiita
- 投稿日:2019-08-30T02:32:11+09:00
NettyでFINとRSTを動的に切り替える方法
Nettyを使ってサーバを構築する際に、Channel(ソケット)をcloseする際に状況によってFINとRSTを使い分けたい場合があります。例えば、正常時はFIN(遅延タイムアウト時はRST)だけど、異常を検知した際は直ちにRSTにするような要件がある場合です。
close時のデフォルト動作の指定
Channel
をcloseした際のFIN/RSTのどちらを使用するかは、ServerBootstrap
を構築する際に指定することができます。EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(0); ServerBootstrap bootstrap = new ServerBootstrap() .option(ChannelOption.SO_BACKLOG, 1024) .group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childOption(ChannelOption.SO_LINGER, 10) // FIN → 遅延タイムアウト(10秒) → RSTでcloseするための指定 .childHandler(new MyChannelInitializer()); // ...NOTE:
-1を指定するRSTによる解放は無効化されます。
close時にFIN/RSTを選択
ServerBootstrap
構築時に指定したデフォルト動作と異なる方法でcloseしたい場合は、Channel
からChannelConfig
を取得してSO_LINGERの設定値を変更することで実現することができます。@ChannelHandler.Sharable class MyHandler extends SimpleChannelInboundHandler<byte[]> { @Override protected void channelRead0(ChannelHandlerContext ctx, byte[] msg) { // ... } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { ctx.channel().config().setOption(ChannelOption.SO_LINGER, 0); // 直ちにRSTでcloseするように指定 ctx.close(); } }
- 投稿日:2019-08-30T01:44:06+09:00
[SQLite] Cursorで起こるIllegalStateException
私が開発したアプリ KUMIWAKE のユーザー様から以下のようなお問い合わせをいただきました。
以前まで普通に使えていましたが、メンバーを選んだり組分けしようとすると「問題が発生したためkumiwakeを終了します」のと出ます。どうすればよいですか?
はい…完全にこちらの不手際でユーザー側ではどうにもできないので至急アップデートにて対応させていただきました。最新ver.でなぜかクラッシュが多かったのでこういったご意見は本当に助かります。
エラーの原因
しかし、ここで困ったことが1つ。
自分の環境ではエラーが再現できないのです。実機もAVDもだめ。Play Consoleのスタックトレースを見ると以下のようにありました。
java.lang.IllegalStateException: at android.database.CursorWindow.nativeGetString (CursorWindow.java) at android.database.CursorWindow.getString (CursorWindow.java:438) at android.database.AbstractWindowedCursor.getString (AbstractWindowedCursor.java:51) ...調べてみるとデータベースのCursorによるエラーのようでした。
以前は機能していたようなので、Cursor部分の変更履歴を見直して何とか原因を特定しました。
c.getString(c.getColumnIndex(COL_NAME))
←この部分ですhttps://codeday.me/jp/qa/20190421/668862.html
こちらの方の回答にあるようにgetColumnIndex(COL_NAME)
がCOL_NAMEのIndexを見つけられず、-1を返していたようです。それによってc.getString(-1)
となり、カラムが見つからない!とエラーを吐いたわけですね。(カラム番号は0~)解決策
とりあえずは以前の状態を復元するために
getColumnIndex()
を使わない方向で落ち着きました。(分かりやすいので本当は使いたいですが...)修正前:
c.getString(c.getColumnIndex(COL_NAME))
修正後:c.getString(1)
直接カラム番号を指定しました。
修正後は正常に動作したようです。めでたしめでたし。
getColumnIndex(COL_NAME)
を使いたい場合は、カラムが見つからないときに-1が返されてしまうので、こちらの記事にあるようにgetColumnIndexOrThrow(COL_NAME)
を使うのが良さそうです。疑問は残る
https://teratail.com/questions/128793
同じような問題で悩んでいる方がおられました。この方の質問にあるように端末によってgetColumnIndex()
がうまく値を返す場合と返さない場合があるようです。Androidのバージョンには依存していなかったので、端末の問題なのか...
SQLiteまわりの詳しい方おられましたらご教授をお願いします。(泣)参考
- 投稿日:2019-08-30T00:06:09+09:00
spring data dynamodbの罠
あらすじ
spring boot
からAWS
のDynamoDB
にアクセスするライブラリspring-data-dynamodb
を使用しているときにはめられたので書こうと思った。内容
事の発端は
DynamoDB
にスネークケースで定義したテーブルと属性を作成、そこにspring
からアクセスしようとfindByitemAttr()
メソッドを定義。
そして実行しようとすると以下のようなエラー文がでて起動ができない状態に。エラー内容You have defined query method in the repository but you don't have any query lookup strategy defined. The infrastructure apparently does not support query methods!日本語に訳すと「リポジトリにクエリメソッドを定義したが、クエリ検索方法が定義されてないからクエリに対応していない。」みたいなことを言っていた。
よくわからんと悩んでたときに、そういや起動してるとき警告がでてたな。と思いだす。
多分spring boot
とspring data dynamodb
のバージョン互換の不一致でブーブー言っているのだろうと思いスルーしていた。そんなわけあるはずねぇ。と思いつつバージョンを一致させ起動したら見事起動、スネークケース定義してるテーブルにもアクセスできた。
ちなみにエラー出てた時のバージョンが以下。
・Spring Boot:2.1.3
・spring data dynamodb:5.0.4そこから
Spring Boot
のバージョンを2.0.8
に修正した。終わりに
今回自分の悪いところがもろに出てはまった。
皆様もバージョン互換には気を付けましょう。ちなみに
sprinf-data-dynamodb
のバージョン互換のお話は以下に書いてあります。
https://derjust.github.io/spring-data-dynamodb/それにしても
spring data dynamodb
、バージョン互換でエラーになるならもう少しわかりやすいエラー出してくれ・・・。