【Unity】UniRxで列数、行数から動的にセルサイズを調整するGrid Layout Groupを実現する
Unityのレイアウト用コンポーネントの1つにGrid Layout Groupがあります。 名前の通りグリッド、もといエクセルのような表形式でオブジェクトを配置する場合に使うためのコンポーネントですが、個人的にはいまいち使い勝手が悪いと感じています。Grid Layout Groupでは、基準がCell Sizeで、列数、行数はCell Sizeを基に決定される仕様となっているので、行数、列数を基準にCell Sizeを決定するということがエディタ上では難しいからです。Vertical LayoutやHorizontal Layoutでは親のサイズに合わせて子のサイズを動的に決定してくれるので、それに比べると‥‥という感じです。 一応Constraintの値をFixed Column Sizeなどに変えると列数、行数を自由に決めることはできますが、これは本当に列数、行数を固定するだけで、それに応じてセルのサイズを動的に決定してくれたりはしません。
結局のところ、「〇行〇列の表」をGrid Layout Groupを使って作ろうとすると、Item Sizeを毎度いい感じに調整するしかないというわけです。で、これを毎回設定するのは面倒なので、行数と列数を指定すると自動的にセルのサイズを調整してくれるスクリプトを書いてみました。
gistaf937e71da132d53ed8290f4fd27e25a
セルサイズの変更はUpdate関数内でやってもいいのですが、せっかくなのでUniRxを使って値が変更されたときのみ変更するようにしています。
gameObject.ObserveEveryValueChanged(_ => rectTransform.sizeDelta).Subscribe(_ => UpdateCellSize()); gameObject.ObserveEveryValueChanged(_ => gridLayout.spacing).Subscribe(_ => UpdateCellSize()); gameObject.ObserveEveryValueChanged(_ => gridLayout.padding.GetType().GetProperties()).Subscribe(_ => UpdateCellSize());
UniRxはご存じの方も多いと思いますが、以前紹介したC#のイベントをUnity向けによしなにやってくれるアセットで、今回の場合のように特定のプロパティの値を監視し、値が変更されたときに特定のメソッドを呼び出したいという処理にはピッタリだと思います。
UniRxでは色々なことができるっぽいですが、全部を使いこなすのは自分には難しそうです。ただ今回のように値の変更を監視するだけであれば、以下の感じで書けるので楽だと思います。
gameObject.ObserveEveryValueChanged(_ = 値を監視したいプロパティ).Subscribe(_ => 呼び出したいメソッド)
難点として上記スクリプトでは rectTransform.sizeDelta
を基準にセルサイズを決定しているので、AnchorにStretch(青色のやつ)を含む可変サイズのものを設定すると正しく動作しないという点があります。Stretchの場合、sizeDeltaはマイナスの値を取りうるからです。 個人的な事情で最近RectTransformのあれこれについて勉強してるので、そのうちその辺について記事を書くかもしれません。詳しいことはそちらで。それでは。
DoTweenのOnComplete()が呼ばれなくて困った話
久しぶりにDoTweenを使っていてしょうもないことでハマったので、反省の意味を込めてメモしておきます。 アニメーションが再生中であるかどうかを判定するために、再生中のSequenceをメンバー変数に持たせればいいかと思い以下のように書いていました。
using Assets.Scripts.SGUI.SGameObjects.ComponentScripts; using DG.Tweening; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class App : MonoBehaviour { [SerializeField] private GameObject obj; [SerializeField] private Button button; public Sequence sequence; void Start() { sequence = DOTween.Sequence(); button.onClick.AddListener(() => { Animate(); }); } public void Animate() { Debug.Log("STARTED"); if (sequence.IsPlaying()) { sequence.Complete(); } else sequence.Append(obj.transform.DOShakePosition(10f, 100, 100)) .OnComplete(() => { Debug.Log("COMPLETED"); }); } }
が、これはうまくいきません。sequence.IsPlaying()
は呼ばれないし OnComplete()
も呼ばれません。
理由は単純に自分が勘違いしてただけなのですが、Sequenceはインスタンスを生成して使いまわすものではなく、アニメーションを再生するたびに
DOTween.Sequence()
で生成するものだからみたいです。なので正しくはこうです。
public void Animate() { Debug.Log("STARTED"); if (sequence.IsPlaying()) { sequence.Complete(); } else sequence = DOTween.Sequence().Append(obj.transform.DOShakePosition(10f, 100, 100)) .OnComplete(() => { Debug.Log("COMPLETED"); }); }
Append()の意味を考えると当然なのですが、なまじOnComplete以外のアニメーションは動くのでしばらくハマりました‥‥
一度ちゃんとドキュメント読んでおこうと思いました。
Ubuntu 18.04(Zorin OS 15.2)にUnity+C#の開発環境を構築するメモ
はじめに
最近自宅のPCにLinuxを導入しました。仕事でMacを触るようになってから、Windowsのコマンドライン関連を含めた使いづらさが目立つようになり、その他の細かい不満点を含めて脱Windowsを目指そうと思ったからです。
LinuxディストリビューションはZorin OS 15.2を選びました。Zorinは以前にも触ったことがあるのですが、特に水色を貴重としたUIのデザインが自分の好みで、Ubuntu 18.04ベースになって安定性が増したらしいということもあり、改めて使ってみることにしてみました。今の所大きな不具合もなく、動作の軽量さも含めてとても快適に使えています。
もちろんWindowsとデュアルブートできる環境にしているので、当初はIntelliJやVS CodeでまかなえるコーディングはZorinで行い、UnityはWindowsで触ればいいかと思っていたのですが、LinuxでもUnityが動くらしいことを知り、早速インストールして使ってみました。以下はその導入メモです。
環境構築
1. Linux向けUnity Hubをダウンロードする
公式サイトからダウンロードします。ここでダウンロードされるのはAppImageというもので、異なるLinuxディストリビューション間でもインストール不要で使える形式らしいです。
ダウンロードしたら権限を与えてコマンドラインから実行します。ダウンロードしたAppImageをクリックすると実行できるかと思いきやそうでもなかったので、コマンドで実行します。
chmod +x UnityHub.AppImage ./UnityHub.AppImage
初回起動時はUnityのライセンスが求められると思うのでログインしてライセンスを更新します。Unity Hubから最新のUnityをダウンロードし、起動できることを確かめます。
2. VS Codeをインストールする
Windowsだと大体の人はVisual Studioを使っていると思いますが、残念なことにLinuxでは使えません。代わりにVS CodeのLinux版が公開されているので、それを使います。
Visual Studio Code – コード エディター | Microsoft Azure
また開発に必要な拡張機能をインストールします。必要な拡張機能は以下の通りです。
C#
必須です。
Debugger for Unity
デバッグに必要です。
MonoBehaviour Snipetts
MonoBehaviour関連の入力補助を行えるようになります。
C# FixFormat Fixed
必須ではありませんが、あると良さげです。導入する場合は下記記事の下のほうにある括弧位置の修正を行うといいと思います。
Visual Studio CodeでC#プログラミング - Qiita
あと個人的な好みですが、Ctrl + ホイールスクロールで文字サイズを変更できるようにするのがおすすめです。
Is it possible to configure control + scroll-wheel to increase/decrease zoom in VS Code? - Stack Overflow
最後にUnityエディタの Edit > preferences > External Tools > External Script Editor をVS Codeに設定します。
3. .Net Core SDK をインストール
公式ページのUbuntu 18.04の部分に従いインストールします。
Ubuntu に .NET Core をインストールする - .NET Core | Microsoft Docs
wget https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb sudo dpkg -i packages-microsoft-prod.deb sudo apt-get update; \ sudo apt-get install -y apt-transport-https && \ sudo apt-get update && \ sudo apt-get install -y dotnet-sdk-3.1
4. Monoのインストール
そのままだと.Net Frameworkの参照が足りない的なエラーが出ます。
The reference assemblies for framework ".NETFramework,Version=v4.7.1" were not found. To resolve this, install the SDK or Targeting Pack for this framework version or retarget your application to a version of the framework for which you have the SDK or Targeting Pack installed. Note that assemblies will be resolved from the Global Assembly Cache (GAC) and will be used in place of reference assemblies. Therefore your Assembly may not be correctly targeted for the framework you intend.
ので、Monoという.Net Framework互換の環境を利用可能にするオープンソースプロジェクトを導入します。公式サイトのUbuntu 18.04の項目を参考にインストールします。
sudo apt install gnupg ca-certificates sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF echo "deb https://download.mono-project.com/repo/ubuntu stable-bionic main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list sudo apt update sudo apt install mono-devel
5. VS Codeを開き、デバッグができるか確認
4まででC#スクリプトの実行はできるようになっていると思うので、最後にデバッグができるか確認します。Unityエディターを開き適当にスクリプトを追加し、それをVS Codeで開きます。
VS Codeの左のメニューでDebugを選び、 青色になっている「Create a launch.json file」をクリックするとデバッガーの一覧が出るので、
「Unity Debugger」を選択します。
生成されるlaunch.jsonが以下のようになっていれば大丈夫だと思います。
{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Unity Editor", "type": "unity", "path": "/home/shun/works/Unity/New Unity Project (1)/Library/EditorInstance.json", "request": "launch" }, .............
適当にブレークポイントを設定し、デバッグできるか確かめます。
おわりに
WindowsでのUnity開発環境が100点とすると、UbutnuでのUnityは80点くらいな感じですが、それでもLinux環境でUnityが動くという事実が嬉しいです。LinuxでのUnityはまだほとんど触っていませんが、特に問題がないようなら本格的にWindowsを触る機会が減りそうだなと思いました。
参考にさせていただいたサイト
LinuxにUnity Editorをインストールする方法 | My note
c# - How do I use Visual Studio Code to develop Unity3D projects in Ubuntu - Stack Overflow
【Unity 2D】一番簡単なHPバーの作り方+その他ダメージ演出
よく作るのでメモしておきます。コード全体へのリンクは記事の最後に貼ってあります。
とりあえず一番簡単なHPバーの作り方
本当に最低限の労力でHPバーを作るなら以下のようになります。前提としてヒエラルキーにCanvasが存在し、Render ModeがScreen Space-Cameraに設定されていることを想定しています。
- HierarchyのCanvas上で右クリック→UI>Panelを選択しオブジェクトを追加(減少したHPを表す部分)
- 適当な大きさに変更し、InspectorビューのImageコンポーネントのColorを調整(赤色で透明度を下げるなど)
- 2.のオブジェクトを複製し、複製したものを2.の子オブジェクトにしてColorを調整(HPの現在値を表す部分)
- Inspector上で3.のオブジェクトのRect Transform>PivotのXを0にする
- 3.のオブジェクトにスクリプトをアタッチし、RectTransformのScaleの値を0~1の値で変化させる(下記)
4.を行うことで、緑色のバーのオブジェクトのScaleの値を変更したとき、その左端を中心にオブジェクトの大きさが変化することになります。
例えばHPバーの対象となるオブジェクトが以下のように定義されているとします。
public class Human : MonoBehaviour { private int maxHP = 100; private int currentHP = 100; public float PerHP { get { float value = (float)currentHP / (float)maxHP; return Mathf.Clamp(value, 0, 1); } private set { } } .....
PerHPは現在のHPの割合を0~1.0の値で返すプロパティです。Mathf.Clamp()
は変数に上限値と下限値を設定し、値を超えていればそれらの設定した値を返してくれる便利なメソッドです。なおVisual Studio上では(float)CurrentHP
のキャストが冗長だとされるのですが、付けないとint型のまま計算が行われ除算の結果が0か1にしかならなくなるので気を付けましょう。基本的な事ですが自分もよく忘れてバグを生みます。
3.で作った緑色のHPバーにアタッチするスクリプトは以下のように記述します。といってもUpdate内でRectTransformのScaleの値を変化させるだけです。
public class HPBar : MonoBehaviour { [SerializeField] Human human; RectTransform rect; void Start() { rect = GetComponent<RectTransform>(); } void Update() { rect.localScale = new Vector2(human.PerHP, 1f); } }
とりあえずこれだけで見れるものは作れます。
ただこれだけだと物足りないので、今回はもう少し手を加えたものを作ります。
HPが減少したときだけバーを変化させる
上のコードのままだと、たとえば値が変化したときのみ処理を行うといったことができず、アニメーションなども付けられません。よってこれを現在のHPの値が変化したときのみ処理を行うように修正します。このような場合にはevent
を使います。具体的には以下のようになります。
public class Human : MonoBehaviour { public int MaxHP = 100; //デリゲートの宣言 //増減した値を取得するために、変更前の値を引数としています public delegate void ValueChangedHandler(int preValue); //ここに対象の値が変化したときの処理を追加していく public event ValueChangedHandler HpChanged; public int CurrentHP { get { return currentHp; } set { if (currentHp != value) { var pre = currentHp; currentHp = value; //ここで登録したデリゲートが呼ばれる HpChanged(pre); } } } int currentHp = 100; .....
HPバーのスクリプトは以下のようになります。前に紹介したDOTweenを使用して簡単なアニメーションをつけています。
void Start() { //HPが変化したときに呼びたいメソッドを追加 //メソッドを複数追加することもできます human.HpChanged += OnHpChanged; } void OnHpChanged(int preValue) { transform.DOScaleX((float)human.PerHP, 0.2f); }
event
とはプロパティのデリゲート版ということで、デリゲートの呼び出しや追加・削除に制限を加えたものと自分的には認識しています。
実体はただのデリゲートなので、例えば上のコードからevent宣言を抜いても動作します。
public delegate void ValueChangedHandler(int preValue); public ValueChangedHandler HpChanged;
何の原則というのかわかりませんが、実際にはバグを防ぐため機能に制約を設けた方がいいという考えがあるため、このような形になっているのだと思います。
なお今回、値の変更を検知する方法としてイベントを利用しましたが、他にもINotifyPropertyChanged
を使うなどの実装方法があります。イベント自体にも色々問題があるらしく、Reactive Extensions
を使うなどしたほうが良いかもしれません。自分もそのうち勉強しようと思います。
その他
上の例だとHPバーを上のほうに表示していますが、HPをもつオブジェクトにCanvasを追加し、そこにHPバーオブジェクトを表示することで、キャラクターの動きにHPバーを追従させることもできます。
その他のダメージ表現などは全てDOTweenで実装しています。コード全体は以下に上げてあるのでよかったら参考にしてください。
お借りした素材:teddy-plaza様
UnityでTransformのposition.x, y, zの各値を、拡張メソッドで直接変えられるようにする
さんざん言われてることだとは思いますが、UnityでTransformの位置、つまりpositionの値を変更したいと思ったとき、positonのx,y,zという各値を直接変更することはできません。
//これはできない transform.position.x = 10f;
これはtransform.positionが、メンバー変数ではなくプロパティとして定義されているためです。C#では、構造体がプロパティになっている場合、その各メンバーの値を直接変更することはできない仕様らしいです。そのためpositionの値を変更したいとき、通常は以下のようにすると思います。(positionを別の変数に取り出してから値を変更する、というやり方もあるみたいですが、手間を考えるとそんなに差はない気がします)
transform.position = new Vector3(x, transform.position.y, transform.position.z)
しかし処理の出現頻度の割に、これを毎回書くのは面倒です。拡張メソッドの形にすれば処理を共通化できるのではないかと思い、Transformクラスの拡張メソッドとして実装してみました。
//xの値を0に transform.SetPosX(0f); //x方向に0.5f移動 transform.AddPosX(0.5f);
という感じで、直接positionのx,y,zの値を書き換えられて便利な気がします。
ここからは余談ですが、けっこうややこしいというか勉強になったことです。上のコードは最初はVector3構造体を拡張する形で書いてみたのですが、
public static void SetPosX(this Vector3 pos, float x) { pos = new Vector3(x, pos.y, pos.z); }
これは正常に動作しません。エラーは出ませんが、実際には値が書き変わらないという面倒なことになります。このthisで参照される値は実際にはコピーした値であり、その値を変更しても元の構造体には影響がないためです。知らないままだと思わぬところでバグが発生しそうで怖いですね。
Hexo+Typoraではじめる、一番楽な技術ブログの書き方
はじめに
この度新しい技術ブログを始めることにしました。Hexoでサイト作ってNetlifyにデプロイしてます。この記事を書くためにサンプルをそのまま上げていて設定とかなんもやってないのですが、また折をみて更新していきたいと思います。
Kanchiの雑記帳 | サブブログです。日記的な感じで書いていきます
今回、ブログを書く環境をいろいろ試した結果、TyporaでMarkdownを書いてHexoでブログサイトを作るという方向性に至ったのですが、この構成だとかなり書きやすそうというか、自分にとってベストだったのでお勧めします。Markdownでブログ書くなら、多分これが一番楽だと思います。
新規ブログ開設に至った理由
自分はこのブログを技術ブログとして使ってきたのですが、書いてきた記事の内容的には、なにか自分が作ったもので完成までに苦労した点など、自分にとっての復習兼他人へのチュートリアル的な感じなものが多く、自分としてもあえてそうしてきた面があります。しかし最近多方面に勉強する機会が増えてきて、トラブルの解決方法など記事にしたい事が毎日のようにあったのですが、前述の通りこのブログは一つの記事に一つのテーマみたいなものを心掛けているため、書く内容はあるのになかなか記事を書きづらいというもどかしさを感じていました。そういうわけで、わざわざ一本のブログ記事として書くほどではないけど、他に同じ問題で困っている人がいるかもしれないからとりあえず書いておこうみたいなブログが欲しくなり、新規ブログ開設に至ったわけです。もちろんこちらのブログをやめるわけではなく、こちらのブログは今まで通りで、向こうのブログではもっと適当な、日記的な記事を書いていきたいと思います。
Hexo+Typoraのブログ環境
余談はさておき本題に入りましょう。
まずはてなブログを選ばなかった理由として、このブログははてなブログで書いているのですが、今までは記事を書くのにVS CodeでMarkDownを書いて、その内容をはてなのエディターにコピペし、画像をアップロードしてリンクを追加する‥‥という七面倒なことをやっていました。もちろん記事ごとにアクセス数解析できたりメリットも大いにあるのですが、今回はそういうの気にしない適当なブログなので大丈夫ということにします。
また他の静的サイトジェネレータとしてVuePressなどがあり、最近になってブログ用プラグインが出たので使ってみようかと思ったのですが…正直手間がかかりすぎるのと思ったより自由度が低そうなのでやめました。手間を考えるとやっぱりHexoを一番お勧めします。
Hexoのインストール
まずはHexoをインストールします。HexoはNode.jsのフレームワークなのでnpmでインストールします。
公式サイトに記載されてる通りにやると5行コマンドを打つだけで動作確認までいけます。便利ですね。
npm install hexo-cli -g hexo init blog cd blog npm install hexo server
新規記事の追加
新しく記事を書くには以下のコマンドを実行します。
hexo new hoge
すると/source/_posts
内に新しくhoge.md
が作られ、ここに記事の内容を書くことになります。
--- title: test date: 2020-03-08 12:05:35 tags: --- ~~ここに内容を書く~~
hexoではこの_posts内にあるMarkdownファイルが記事としてみなされるようです。
画像の挿入
今回一番苦労した点です。これがうまくいかなかったから他の静的サイトジェネレータを試したくらいです。自分が一番知りたかったのは相対パスで画像を指定する方法で、他のサイトだと configのpost_asset_folderをtrueにしてhexo-asset-linkプラグインを入れる…みたいなことが書かれてあるのですが、自分の環境だとどうやっても正常に画像を表示することができませんでした。
実際に生成されているhtmlを見ると、画像のリンクがsrc="/.com//image.jpg"
というものになってしまっていることがわかります。これは一応githubのissuesにも上がっているみたいで、Macで試してないのでわかりませんが、Windowsの最新版で起こる問題なのかもしれません。リポジトリがアーカイブされているため解決することも難しそうです。
それで結局どうすればよいかというと、Hexoには画像のグローバルパスというものが存在し、source/images
がそれにあたります。ここに配置した画像はサイト生成時にimagesフォルダに入れられ、![](/images/image.jpg)
のような形でアクセスすることができます。現状、上の問題が発生している場合、相対パスで画像を指定するには他の方法はおそらく無いと思われます。(むしろあったら教えてください。本当に)
Typoraの導入と設定
Typoraは最近知ったのですが、Markdownとして打っている画面がそのままプレビューになり、しかもMarkdown記法のままコピーできる優れものです。Macだと使えるBearがWindowsだと使えないのもあってこれ一択みたいなところがあります。
便利なことにTyporaはドラッグ&ドロップで画像を挿入することができ、そのままだと画像の絶対パスが挿入されます。TyporaをHexoのブログ編集用に使うため、以下のように設定を変更します。
この設定にすると、Hexoプロジェクトのsource/_posts/hoge.md
という記事をTyporaで編集したとき、そこに画像をD&Dすると source/images/hoge/
にその画像がコピーされます。Markdownでは以下のように記述されます。
![animals](../images/hoge/animals-1583642380054.jpg)
画面上でもちゃんと表示されています。
こうするとD&Dでお手軽に画像を貼れて、しかもそのままgit pushすることで記事を更新することができるため、とても楽です。加えて普通だと少し面倒なMarkdown上での画像サイズの編集も、Typoraだと画像を右クリック→画像を拡大から自由にできて、ちゃんと<img src="~~~">
の形に直してくれるのですごいです。
ただし上の記述のままだと、TyporaとHexoブログ上の記事一覧画面からは画像を表示することができるのですが、個別の記事画面からは画像が表示されません。Hexoでは記事の個別画面のURLを示すパーマリンクがデフォルトでは以下のようになっていて、記事個別画面からだと画像の相対パスが/2020/03/08/images/hoge/image.jpg
とかになってしまうためです。
permalink: :year/:month/:day/:title/
これを解決する方法は二つあって、一つは上でも書いた通り画像のパスを/images/hoge/image.jpg
にすることです。頭にスラッシュ付けないとダメなので注意してください。
![animals](/images/hoge/animals-1583642380054.jpg)
ただこれだとTypora上からはプレビューできません。もう一つの方法として、_config.yml
内のパーマリンクの指定を記事のタイトルだけにするというものがあります。
permalink: :title/
こうすると記事一覧画面と記事個別画面で画像の相対パスが一緒になり、どちらからも../images/hoge/image.jpg
で参照できるため、Typoraでプレビューした内容をデプロイ先でそのまま表示でき、面倒な画像の挿入がD&Dだけで解決してしまいまいとても便利な気がします…が、大体の人はおそらくこの部分をいじらないため、よく知らないのですが多分あんまりやらないほうがよいのではないかと思っています。自分は../images/hoge/image.jpg
で記事を書きTypora上でプレビューしつつ、デプロイ時にその部分だけ修正することにします。そもそもHexo server
でプレビューできますしね。
テーマの導入
Hexoではブログテーマの導入もすごく簡単です。themes
ディレクトリ内にダウンロードしたテーマのディレクトリを配置し、_config.yml
のtheme: ~~~
をそのディレクトリ名に変更するだけでOKです。
例えば自分はBlueLakeというテーマを使っているのですが、導入したいテーマのgithubリポジトリが
github.com/username/reponame
にあるとした場合、以下のコマンドで行うことができます。
git clone https://github.com/username/reponame.git themes/reponame
ただNetlifyを使う場合、デプロイする際にNo url found for submodule path gitmodules
とかで失敗します。直接ダウンロードしてディレクトリを移動させるか.gitディレクトリを削除すると解決します。
自分が使用しているテーマ:https://github.com/chaooo/hexo-theme-BlueLake
デプロイ
デプロイは簡単にできます。github.ioで公開する場合は
username.github.io
の形でレポジトリを作り、_config.ymlを編集します。レポジトリ名は必ず自分のユーザー名.github.ioに形にしないといけないので注意してください。
deploy: type: git repo: https://github.com/username/username.github.io.git branch: master
以下のコマンドでデプロイできます。username.github.io
にアクセスして確認してみてください。
hexo deploy -g
自分はNetlifyを使うことにしたのですが、これもレポジトリを登録するだけで簡単にできます。この場合はhexoプロジェクトでgit remote add origin ~~~~
でレポジトリを登録してmasterにpushするだけでOKなので、個人的にはこちらが好みですね。
おわりに
今回はHexo+Typoraでお手軽に技術ブログを書く環境の作り方を紹介しました。新しいブログではアクセス数とかあまり気にせず日記的な感じで雑に書いていきたいと思います。
Spring Data JPAの使い方メモ
最近になってJavaのSpring Bootを使ったWeb開発の勉強をはじめました。その中でSpring Boot JRAというデータベースを操作するためのライブラリが便利だったので使い方をまとめておきます。
依存関係
以下はビルドツールにgradleを使用する場合の例です。Spring InitiallizerでSpring Data Jpaにチェックを入れるとbuild.gradleのdependenciesに次の項目が追加されていると思います。
dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' }
Spring Data JPAとは
そもそもSpring Data JPAとは、Repositoryクラスを便利に実装するためのライブラリです。JavaのWeb開発で理想とされるMVCモデルにおいて、メインのControllerクラスなどからデータベースを直接扱うことは好ましいとされておらず、そのために用いられるデータベース操作のためのクラスがRepositoryクラスです。別の言い方だとDAO(Data Access Object)と言ったりもします。これを自分で実装するとSQLのクエリの種類ごとに記述しないといけないのでやや手間がかかるのですが、Spring Data JPRを使うとこれがごく簡単に書けます。たとえばEmployeeというエンティティクラス(データベースの入れ物となるクラスで、データベースのレコード=一つのデータに相当します)があって、nameというフィールドで指定した文字列に完全一致するものを取得したいときは次のように書けます。
@Repository public interface EmployeeRepository extends JpaRepository<Employee, String>{ List<Employee> findByName(String name); }
@Autowired EmployeeRepository employeeRepository; @RequestMapping(value = "/{name}", method = RequestMethod.GET) public String findEmployeeByName(@PathVariable("name") String name) { return employeeRepository.findByName(name); }
基本的な書き方はドキュメントを見てもらえればわかるのですが、検索したいフィールド名の最初を大文字にして、findBy~などの特定のフォーマットに合わせてメソッドを定義することで、JPAが自動で実装してくれます。便宜上引数もnameとしていますが、実際には何でも構いません。
以下は個人的につまずいた点などをまとめたメモです。
クエリを自分で定義したい
メソッドの前に@Query
アノテーションを付けます。例えば上のfindByNameメソッドのクエリ部分を自分で記述するなら以下のように書きます。?1
はメソッドの引数を表し、例えばメソッドの引数を増やしたときは?2, ?3 ...
とすることでそれぞれアクセスできます。
@Query("select e from Employee e where e.name = ?1") public List<Employee> findByName(String name);
このクエリ文はJPQLといい、SQLのクエリ文とは異なることに注意です。上のクエリをSQL上で実行しても当然ですがエラーが出ます。SQL構文をそのまま使いたいときは、アノテーション内でnativeQuery=true
とします。
@Query(value = "SELECT * FROM Employees WHERE name LIKE ?1", nativeQuery=true) public List<Employee> findByName(String name);
部分一致検索をしたい
findBy(フィールド名)Containing
を使います。
public List<Employee> findByNameContaining(String name); @Query("select e from Employee e where e.name like %?1% ") public List<Employee> findByLikeName(String name);
特定の引数を省略したい
Spring Data JPAではメソッド名にANDを加えることで複数のフィールド(=カラム)を検索できます。
public List<Employee> findByNameAndStatusContaining(String name, String status);
このとき、特定の引数にnullが入るとうまく検索できなくなります。これを防ぐためには、Controller側でデフォルトの値をワイルドカードである%
に指定しておくといいと思います。以下の例ではdefaultValue = "%"
がそれにあたります。
@RequestMapping(value = "/", method = RequestMethod.GET) public String getEmployeesByQuery(@RequestParam(value = "name", required = false, defaultValue = "%") String name, @RequestParam(value = "status", required = false, defaultValue = "%") String status){ ~~~~ }
Ordinal parameter not bound エラーが出る
@Query
アノテーション内で、?1
を省略するとおそらく次のエラーが出ると思います。一番目の引数である?1
は必ず使用しなければならないようです。
There was an unexpected error (type=Internal Server Error, status=500). Ordinal parameter not bound : 5; nested exception is org.hibernate.QueryException: Ordinal parameter not bound : 5 org.springframework.dao.InvalidDataAccessResourceUsageException: Ordinal parameter not bound : 5; nested https://kaige.org/2019/07/10/org-hibernate-QueryException-Ordinal-parameter-not-bound/