楽観的排他制御と悲観的排他制御

楽観的排他制御(optimistic exclusive control)と悲観的排他制御(pessimistic exclusive control)は、両方の方法が大規模なスケーラビリティを持つため、システムの排他制御において非常に一般的な方法論です。

近年のWebシステムでは、特に楽観的排他制御が用いられることがほとんどです。本記事では、楽観的排他制御と悲観的排他制御のどちらが適しているのかを考察し、それらがシステム設計に及ぼす影響について説明します。

排他制御とは

排他制御は、同時に複数のスレッド/プロセス/ユーザーがデータを更新しようとする問題を解決するために使用されます。排他制御方法には悲観的排他制御と楽観的排他制御の2つの主要な方法があります。

悲観的排他制御(pessimistic exclusive control)

悲観的排他制御は、データ更新リクエストについての楽観的な予測を行わない方法です。つまり、一度に1人のユーザーしかデータを操作できないように、予約をすることで競合を回避しようとします。悲観的排他制御は、ロック機構やトランザクションを使用することが一般的です。

楽観的排他制御(optimistic exclusive control)

一方、楽観的排他制御は、データを更新する前に、競合を検出して回避するための最適な方法論を提供することにフォーカスしています。これは、別のユーザーがデータを更新したときに、その影響を判断することができるデータバージョン管理を含むことがよくあります。ユーザー操作中に競合が発生した場合、変更をロールバックするか、サーバから新しいデータを取得してマージするか選択できます。

楽観的排他制御では、競合を検出するためにバージョンまたは更新日時を使用することが一般的です。更新日時を使用する場合には日付の精度に注意が必要で、精度が低いと他ユーザーの更新を検出できない場合があります。

排他制御がシステムに与える影響

悲観的排他制御は、大規模なWebシステムのパフォーマンスに深刻な影響を与える可能性があります。つまり、トランザクションロックが長時間存在する場合には、他のトランザクションによってコミットされたデータに対して待たなければならず、それによって生じる遅延リスクがあります。一方、楽観的排他制御には、ユーザーが変更したい場合には非常に簡単であるため、パフォーマンスの限界に到達する前に、他のタイプの制御よりも多数のデータ更新を許容できます。

また、トランザクションが複数のデータをロックする場合、ロック順番に気をつけないとデッドロックが発生するリスクがある点についても注意が必要です。

楽観的排他制御は、特にオンラインWebアプリケーションにおいて、より親和性があると考えられています。ただし、その欠点が存在する場合があります。書き込みの競合性が高い場合、やはり悲観的排他制御で処理することが必要であるかもしれません。そしてこの場合、楽観的排他制御は厳密ではありません。Webアプリケーションには、トランザクション処理スレッド特有の問題も存在するため、慎重に検討する必要があります。

ただし、頻繁にデータ競合が発生する場合は、ユーザーの生産性やパフォーマンスに悪影響を与える場合があります。

アクセス制御による排他制御

楽観的排他制御とも悲観的排他制御とも異なるアプローチで排他制御を行うという選択肢もあります。

例えば、以下のような方法があります。

  • データを更新する前にデータに対するロックを獲得し、ロックしているユーザーのみがデータを更新できるようにする
  • ワークフローによってデータをユーザーに割り当て、割り当てられているユーザーのみが更新できるようにする

いずれの方法においても、ユーザーに与えて、アクセス権限を有しているユーザーだけが更新を行うことができるようにシステムを設計しています。これにより、更新時に排他制御を行わなくても安全にデータを更新することができるようになりますし、楽観的排他制御で安全に排他制御することができます。

結論

楽観的排他制御と悲観的排他制御は、両方とも重要な排他制御手法です。また、これら以外にも排他制御の手法はあります。システム環境、データ同期の頻度、書き込みの競合状況に応じて、適切な排他制御方法を選択し、最適な手法を使用することが重要です。

楽観的排他制御を使用することで、システムのパフォーマンスが向上し、ユーザーエクスペリエンスを向上させることができます。悲観的排他制御を使用することで、強力なトランザクション管理と競合回避制御を実現できます。

したがって、基本的には、両者が制御戦略の好みに応じて異なる選択肢であり、特定のシナリオと目的に基づいて使用されるべきです。

Yarnをインストールする(v3系)

過去にYarnを使っていましたが、最近は全然使用していませんでした。ふと使おうかと思ったのですが、インストール方法がだいぶ変わっており、初めてインストールを行う人やしばらくぶりにインストールをしようとすると手順に戸惑いそうだったので、基本的なインストール手順をまとめました。

前提条件

まずは記事執筆時点でのLTSバージョンである18.16.0を使用してインストールを行います。

$ node -v
v18.16.0
$ npm -v
9.6.6

npmについても執筆時点の最新バージョンにアップデートしています。

手順はmacOS上で確認していますが、特にシェルコマンドなどは使っていないので、どの環境でも同様の手順でインストール可能です。

また、インストール手順は公式の手順に準じて進めます。

公式の手順で十分な方はそちらを参照してください。

corepackを有効化する

corepacknpmyarnなどのパッケージマネージャを管理するツールです。デフォルトでは有効になっていないため有効します。

$ corepack enable

以前はnpmコマンドでインストールしていましたが、今はcorepackでインストールします。npm install yarnでもインストールできるのですが、v1系がインストールされるため、最新バージョンをインストールしたい場合はcorepackコマンドを使用する必要があります。

yarnをインストールする

次にcorepack を使ってyarnをインストールします。執筆時点では3.5.1が最新版のようですので、3.5.1をインストールすることにします。

yarnリポジトリはv1系のリポジトリとなっているのでご注意ください。

corepack prepareコマンドでインストールと有効化を行います。

$ corepack prepare yarn@3.5.1 --activate
Preparing yarn@3.5.1 for immediate activation...

Node.jsをasdfでインストールしている場合は、追加で以下のコマンドを実行してyarnコマンドを使用できるようにします。

$ asdf reshim

指定したバージョンがインストールできていることを確認します。

$ yarn -v
3.5.1

これでインストールが完了しました。

package.jsonを作成する

ついでにyarnコマンドを使ってpackage.jsonを作成してみましょう。

$ yarn init -y
{
  name: 'test',
  packageManager: 'yarn@3.5.1'
}

corepackの管理下にあるパッケージマネージャを使ってpackage.jsonを作成すると、packageManagerというプロパティが追加されるようになります。これがあるとyarn以外のパッケージマネージャを使用するとエラーが出るようになります。

Apple Silicon MacにStable Diffusion WebUIをインストールする

Stable DiffusionをApple SiliconのmacOSにインストールしてみましたが、少しコツが必要でした。

インストール時期やApple Siliconの種類によっては若干変わるかもしれませんが、私がM1 Maxチップ搭載のMacBook Proで成功した手順をまとめてみました。

必要なパッケージをインストールする

こちらは公式に従ってインストールします。私の環境では最終的にPythonはここでインストールしたものではなく、Asdfで導入したものを使用しているので、3.10系であればここでインストールしなくてもよいと思われます。

brew install cmake protobuf rust python@3.10 git wget

Stable Diffusion WebUIをダウンロードする

GithubのReleasesでもダウンロードできそうですが、macOSの場合はリポジトリをクローンする必要があります。

任意のディレクトリにリポジトリをcloneしてください。

git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui

追加データをダウンロードする

手順の中でここが一番面倒かもしれません。ここでは執筆時点最新のv2.1を使用します。

上記サイトからvae-ft-mse-840000-ema-pruned.ckptというファイルをダウンロードし 、stable-diffusion-webui/models/Stable-difusionに保存してください。

保存後、ファイル名をv2-1_768-ema-pruned.vae.ptに変更してください。

上記サイトからv2-1_768-ema-pruned.safetensorsというファイルをダウンロードし、stable-diffusion-webui/models/Stable-difusionに保存してください。

ここを開いて、表示された内容をコピーし、stable-diffusion-webui/models/Stable-difusionv2-1_768-ema-pruned.yamlというファイル名で保存してください。

Stable Diffusion WebUIを起動する

cd stable-diffusion-webui
./webui.sh

起動すると、以下のようなログが表示されます。URLが出ているので、ここにアクセスしてください。

Model loaded in 3.6s (load weights from disk: 0.3s, create model: 0.2s, apply weights to model: 1.8s, apply half(): 0.6s, load VAE: 0.1s, move model to device: 0.5s).
Running on local URL:  http://127.0.0.1:7860

To create a public link, set `share=True` in `launch()`.
Startup time: 11.2s (import torch: 2.1s, import gradio: 1.7s, import ldm: 0.4s, other imports: 2.9s, load scripts: 0.3s, load SD checkpoint: 3.6s, create ui: 0.2s).

ブラウザでアクセスすると、以下のような画面が表示されます。

設定変更を行う

同じようにApple Silicon Macにインストールしている方の記事で、この点に触れていない方も結構見かけるので、もしかすると環境依存かもしれませんが、私の環境ではv2.1でGenerateしようとすると以下のようなエラーが表示されました。

  0%|                                                                                                                                                                                                                                    | 0/20 [00:02<?, ?it/s]
Error completing request
Arguments: ('task(6gvckovujc9xon0)', 'sailing ship', '', [], 20, 0, False, False, 1, 1, 7, -1.0, -1.0, 0, 0, 0, False, 512, 512, False, 0.7, 2, 'Latent', 0, 0, 0, [], 0, False, False, 'positive', 'comma', 0, False, False, '', 1, '', 0, '', 0, '', True, False, False, False, 0) {}
Traceback (most recent call last):
  File "/Users/t0k0sh1/Workspace/stable-diffusion-webui/modules/call_queue.py", line 56, in f
    res = list(func(*args, **kwargs))
  File "/Users/t0k0sh1/Workspace/stable-diffusion-webui/modules/call_queue.py", line 37, in f
    res = func(*args, **kwargs)
  File "/Users/t0k0sh1/Workspace/stable-diffusion-webui/modules/txt2img.py", line 56, in txt2img
    processed = process_images(p)
  File "/Users/t0k0sh1/Workspace/stable-diffusion-webui/modules/processing.py", line 503, in process_images
    res = process_images_inner(p)
  File "/Users/t0k0sh1/Workspace/stable-diffusion-webui/modules/processing.py", line 653, in process_images_inner
    samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, seeds=seeds, subseeds=subseeds, subseed_strength=p.subseed_strength, prompts=prompts)
  File "/Users/t0k0sh1/Workspace/stable-diffusion-webui/modules/processing.py", line 869, in sample
    samples = self.sampler.sample(self, x, conditioning, unconditional_conditioning, image_conditioning=self.txt2img_image_conditioning(x))
  File "/Users/t0k0sh1/Workspace/stable-diffusion-webui/modules/sd_samplers_kdiffusion.py", line 358, in sample
    samples = self.launch_sampling(steps, lambda: self.func(self.model_wrap_cfg, x, extra_args={
  File "/Users/t0k0sh1/Workspace/stable-diffusion-webui/modules/sd_samplers_kdiffusion.py", line 234, in launch_sampling
    return func()
  File "/Users/t0k0sh1/Workspace/stable-diffusion-webui/modules/sd_samplers_kdiffusion.py", line 358, in <lambda>
    samples = self.launch_sampling(steps, lambda: self.func(self.model_wrap_cfg, x, extra_args={
  File "/Users/t0k0sh1/Workspace/stable-diffusion-webui/venv/lib/python3.10/site-packages/torch/autograd/grad_mode.py", line 27, in decorate_context
    return func(*args, **kwargs)
  File "/Users/t0k0sh1/Workspace/stable-diffusion-webui/repositories/k-diffusion/k_diffusion/sampling.py", line 145, in sample_euler_ancestral
    denoised = model(x, sigmas[i] * s_in, **extra_args)
  File "/Users/t0k0sh1/Workspace/stable-diffusion-webui/venv/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1130, in _call_impl
    return forward_call(*input, **kwargs)
  File "/Users/t0k0sh1/Workspace/stable-diffusion-webui/modules/sd_samplers_kdiffusion.py", line 152, in forward
    devices.test_for_nans(x_out, "unet")
  File "/Users/t0k0sh1/Workspace/stable-diffusion-webui/modules/devices.py", line 152, in test_for_nans
    raise NansException(message)
modules.devices.NansException: A tensor with all NaNs was produced in Unet. This could be either because there's not enough precision to represent the picture, or because your video card does not support half type. Try setting the "Upcast cross attention layer to float32" option in Settings > Stable Diffusion or using the --no-half commandline argument to fix this. Use --disable-nan-check commandline argument to disable this check.

ちなみに、v1.4やv1.5ではこの事象は起きなかったので、v2.x系固有のエラーかもしれません。

これを解消するために、設定変更を行います。画面上部のタブからSettingsを選択し、

左のメニューからStable Diffusionを選択します。

バージョンにもよるかもしれませんが、一番下にUpcast cross attention layer to float32というチェックボックスがあるので、ここにチェックをつけてください。

最後に設定を反映させるため、画面上部のApply settingsボタンを押してください。

これでエラーが出ずに画像生成ができるようになります。

気になった話題のまとめ(2023/04/11)

M3チップが出るんじゃないかという噂も出ていますが、こんなハイペースで出されるといつで買おうか迷いますよね。

私もしばらくは買わないと思いますが、持ち運びしやすい13インチMacBook Airはどこかのタイミングで買いたいなと思っています。

最初、「新しいモバイルモニターか」と思っていましたが、よくよく読んでみるとモバイルモニターを縦置きするためのスタンドでした。価格的には3千円程度と手頃な価格ですので、モバイルモニターを使っている方にはお勧めできそうです。

ただ、ベニア板などから切り出せる人からすると自分で作ったほうが早いかもしれません。

気になった話題のまとめ(2023/04/07)

Tailwind CSSの拡張機能、結構増えているんですね。本記事では以下の4つの拡張機能を紹介していますが、tailwindで検索すると、他にも星5の拡張機能はたくさんありました。拡張機能パックでも他の拡張機能がパックされていたりするので、自分に合う拡張機能が他にもあるかもしれません。

気になった話題のまとめ(2023/04/06)

折りたたみ機能は昨今のエディタやIDEには大抵備わっていますが、それをより見やすくするVS Codeの拡張機能です。

折り畳んだ行数が表示されるのと、閉じブラケットも折りたたんでくれるので、より見やすくよりわかりやすくなります。

ショートカットを覚えられたら使いやすいんだろうなとは思いますし、コードを綺麗に保つことでより可読性が向上することが見込めるので、これを機に使ってみようと思います。

気になった話題のまとめ(2023/04/05)

ChatGTPに関する話題

ChatGPT、使っていますか?

私は仕事では結構使っているのですが、正直普段の生活ではあまり活用してきませんでした。

ふと目に入った知らない言葉を説明してもらう、というのであれば確かに普段の生活でも使えそうです。仕事で使っていると、最終的に正しいかどうかを検証するようにしているためか、レシピとかを生成するのはちょっと抵抗がありますね。

ChatGPTを活用したいけど、自社の情報を学習したことによってどこかで流出したらどうしよう、自社や業界に特化して学習してほしいという要望は多いと思います。

ただ、自社で運用するにはコストが高すぎる、そういったことを叶えるサービスになります。これからもこういったサービスは出てくると思いますが、クラウドベンダーからも提供されるのも時間の問題だと思います。

低価格化が進むだけでなく、ChatGPTのミドルウェア化、サービス化が加速していきそうです。

いくつか入れていますが、注意点としては自動から手動へ設定を変えたほうがいいということです。自動は楽ですが、ChatGPTのログがどんどん残るので使いたい時に限定して使ったほうがいいです。

直接ChatGPTに聞いた内容が流れていってしまっては元も子もないですからね。ログを外部に出力して検索できるようなサービスが出てくれば自動でもいいと思いますね。

[Pandas]代表値を求める(平均値、中央値、最頻値)

一般的によく知られている代表値といえば、平均値(mean)、中央値(median)、最頻値(mode)です。

Pandasを使ってこれらの値を求めてみます。

下準備

今回はTitanicデータセットから年齢(Age)を使用します。

import pandas as pd
import math

df = pd.read_csv('./titanic/train.csv')

平均値を求める

平均値を求めるにはmean()を使用します。

# 平均値(mean)を求める
df['Age'].mean()
# -> 29.69911764705882

中央値を求める

中央値を求めるにはmedian()を使用します。

# 中央値(median)求める
df['Age'].median()

最頻値を求める

最頻値を求めるには一工夫が必要です。最頻値は最も大きい度数の階級値になるため、一旦度数分布表を作成します。

ヒストグラムを確認する

度数分布表を作成する前にヒストグラムを確認しておきましょう。

df['Age'].hist(bins=10)

ビンの数を10に設定してヒストグラムを作成すると、20代が最頻値のようだということがわかります。

最頻値を求める

視覚的に確認できたところで、最頻値を求めます。度数分布表の作成方法はいくつかありますが、ここでは簡易的にcut()value_counts()を組み合わせて、先ほど確認したグラフと同じ区間で度数分布表を作成します。

# 度数分布表を作成する
pd.cut(df['Age'], 10).value_counts()
---
(16.336, 24.294]    177
(24.294, 32.252]    169
(32.252, 40.21]     118
(40.21, 48.168]      70
(0.34, 8.378]        54
(8.378, 16.336]      46
(48.168, 56.126]     45
(56.126, 64.084]     24
(64.084, 72.042]      9
(72.042, 80.0]        2
Name: Age, dtype: int64

作成した結果を確認しても20代が最頻値のようです。最頻値は区間の最小値と最大値の中間になりますので、$24.294-(24.294-16.336)\div2=20.315$となります。

[Oracle]WITH句で階層問い合わせを行う

OracleではWITH句を使って階層問い合わせを行うことができます。

サンプルデータ

説明として、階層を持つフォルダを管理するFOLDER_TBLとフォルダに格納するファイルを管理するFILE_TBLを用意します。

CREATE TABLE FOLDER_TBL (
  FOLDER_ID NUMBER(10) NOT NULL,
  PARENT_ID NUMBER(10),
  FOLDER_NAME VARCHAR2(255) NOT NULL,
  PRIMARY KEY (FOLDER_ID),
  FOREIGN KEY (FOLDER_ID) REFERENCES FOLDER_TBL(FOLDER_ID) ON DELETE CASCADE
);

FOLDER_TBLはフォルダを識別するためのFOLDER_IDとあるフォルダとその上位フォルダを紐づけるためのPARENT_IDを持っています。PARENT_IDは最上位フォルダの場合はNULLになります。

CREATE TABLE FILE_TBL (
  FILE_ID NUMBER(10) NOT NULL,
  FOLDER_ID NUMBER(10) NOT NULL,
  FILE_NAME VARCHAR2(255) NOT NULL,
  FILE_SIZE NUMBER(10),
  PRIMARY KEY (FILE_ID),
  FOREIGN KEY (FOLDER_ID) REFERENCES FOLDER_TBL(FOLDER_ID) ON DELETE CASCADE
);

ファイルはFILE_IDによって識別され、格納先のフォルダに紐づけるためのFOLDER_IDを持っています。FOLDER_IDNULLになることはなく、必ずどこかのフォルダに紐づきます。

-- FOLDER_TBLテーブルにデータを登録するSQL
INSERT INTO FOLDER_TBL (FOLDER_ID, parent_id, FOLDER_NAME) VALUES (1, NULL, 'Folder 1');
INSERT INTO FOLDER_TBL (FOLDER_ID, parent_id, FOLDER_NAME) VALUES (2, 1, 'Folder 1-1');
INSERT INTO FOLDER_TBL (FOLDER_ID, parent_id, FOLDER_NAME) VALUES (3, NULL, 'Folder 2');

-- FILE_TBLテーブルにデータを登録するSQL
INSERT INTO FILE_TBL (FILE_ID, FOLDER_ID, FILE_NAME, FILE_SIZE) VALUES (1, 1, 'file1.txt', 100);
INSERT INTO FILE_TBL (FILE_ID, FOLDER_ID, FILE_NAME, FILE_SIZE) VALUES (2, 1, 'file2.txt', 200);
INSERT INTO FILE_TBL (FILE_ID, FOLDER_ID, FILE_NAME, FILE_SIZE) VALUES (3, 2, 'file3.txt', 150);
INSERT INTO FILE_TBL (FILE_ID, FOLDER_ID, FILE_NAME, FILE_SIZE) VALUES (4, 2, 'file4.txt', 300);
INSERT INTO FILE_TBL (FILE_ID, FOLDER_ID, FILE_NAME, FILE_SIZE) VALUES (5, 3, 'file5.txt', 50);
INSERT INTO FILE_TBL (FILE_ID, FOLDER_ID, FILE_NAME, FILE_SIZE) VALUES (6, 3, 'file6.txt', 250);

サンプルデータを用意しました。

FOLDER_TBLは以下のようなデータとなっています。最上位階層は二つ、うち一つにはサブフォルダがあります。

FOLDER_IDPARENT_IDFOLDER_NAME
1NULLFolder 1
21Folder 1-1
3NULLFolder 2

FILE_TBLは以下のようなデータになっています。各フォルダに2ファイルずつファイルが格納されています。

FILE_IDFOLDER_IDFILE_NAMEFILE_SIZE
11file1.txt100
21file2.txt200
32file3.txt150
42file4.txt300
53file5.txt50
63file6.txt250

下層から上層に向かって階層問い合わせをする

あるファイルを指定し、その格納フォルダから最上位フォルダまでの階層を取得してみます。ここではサブフォルダにあるfile3.txtが格納されているフォルダから上位フォルダへ向かって階層問い合わせを行います。

WITH FOLDER_HIERARCY (
    FOLDER_ID,
    PARENT_ID,
    FOLDER_NAME,
    FOLDER_PATH
) AS (
  -- 最下層のフォルダ
  SELECT
    FOLDER_ID,
    PARENT_ID,
    FOLDER_NAME,
    FOLDER_NAME FOLDER_PATH
  FROM
    FOLDER_TBL FD1
  WHERE
    -- フォルダに格納されているファイルのファイル名が'file3.txt'
    EXISTS(SELECT 1 FROM FILE_TBL FL1 WHERE FD1.FOLDER_ID = FL1.FOLDER_ID AND FL1.FILE_NAME = 'file3.txt')
  UNION ALL
  SELECT
    FD2.FOLDER_ID,
    FD2.PARENT_ID,
    FD2.FOLDER_NAME,
    FD2.FOLDER_NAME || ' / ' || BASE.FOLDER_PATH
  FROM
    FOLDER_HIERARCY BASE -- このテーブルに対して
    INNER JOIN FOLDER_TBL FD2 -- このテーブルを結合していく
    ON
      BASE.PARENT_ID = FD2.FOLDER_ID -- 下位フォルダのPARENT_IDに一致するFOLDER_IDのフォルダは1つ上位のフォルダとなる
)
SELECT
  FOLDER_PATH || ' / file3.txt'
FROM
  FOLDER_HIERARCY
WHERE
  PARENT_ID IS NULL -- PARENT_IDがNULLのレコードに絞り込むと最上位フォルダまでのパスが得られる
;

ポイントは、

  • 階層問い合わせをするときは項目を明示する(しないとエラーになる)
  • 基準となるレコードに対して上位のレコードを結合していく

です。下層から上層に向かって階層問い合わせする場合は、基準となるテーブル(BASE)の親IDと一つ上位となるテーブル(FD2)のIDを結合する点に気をつけましょう。

上層から下層に向かって階層問い合わせをする

同じことを逆からやってみましょう。

WITH FOLDER_HIERARCY (
    FOLDER_ID,
    PARENT_ID,
    FOLDER_NAME,
    FOLDER_PATH
) AS (
  -- 最上層のフォルダ
  SELECT
    FOLDER_ID,
    PARENT_ID,
    FOLDER_NAME,
    FOLDER_NAME FOLDER_PATH
  FROM
    FOLDER_TBL FD1
  WHERE
    FD1.PARENT_ID IS NULL
  UNION ALL
  SELECT
    FD2.FOLDER_ID,
    FD2.PARENT_ID,
    FD2.FOLDER_NAME,
    BASE.FOLDER_PATH || ' / ' || FD2.FOLDER_NAME
  FROM
    FOLDER_HIERARCY BASE -- このテーブルに対して
    INNER JOIN FOLDER_TBL FD2 -- このテーブルを結合していく
    ON
      BASE.FOLDER_ID = FD2.PARENT_ID -- 上位フォルダのFOLDER_IDに一致するPARENT_IDのフォルダは1つ下位のフォルダとなる
)
SELECT
  FOLDER_PATH || ' / ' || FILE_NAME
FROM
  FOLDER_HIERARCY T1
  INNER JOIN FILE_TBL T2
  ON
    T1.FOLDER_ID = T2.FOLDER_ID
WHERE
  FILE_NAME = 'file3.txt'
;

上層から下層に向かって階層問い合わせする場合は、基準となるテーブル(BASE)のIDと一つ下位となるテーブル(FD2)の親IDを結合する点に気をつけましょう。

いずれの検索結果もFolder 1 / Folder 1-1 / file3.txtとなります。

[Spring Boot]IntelliJ IDEAでLombokを使っていてエラーになる場合の対処方法

以下の記事に解決方法が載っていました。

https://stackoverflow.com/questions/72583645/compile-error-with-lombok-in-intellij-only-when-running-build

遭遇した事象

Spring Initializrからダウンロードした時点でLombokは依存関係に追加されています。

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.0.4'
	id 'io.spring.dependency-management' version '1.1.0'
}

group = 'com.t0k0sh1'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.projectlombok:lombok:1.18.22'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
	useJUnitPlatform()
}

IntelliJ IDEAでLombokを使用可能にするためには、設定のビルド、実行、デプロイ>コンパイラー>アノテーションプロセッサーで、「アノテーション処理を有効にする」にチェックをつけ、「プロジェクトクラスパスからプロセッサーを取得する」にチェックがついている状態にします。

ここまでがよく知られているLombokの導入方法になります。

lombok.Dataアノテーションを定義したクラスを作成し、

package com.t0k0sh1.tutorial.entity;

import lombok.Data;

@Data
public class User {
    private Long id;
    private String name;
    private String email;
    private String password;
}

自動生成されているであろうgetterを使ってみます。

package com.t0k0sh1.tutorial.controller;

import com.t0k0sh1.tutorial.entity.User;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

@RestController
public class UserController {
    private final List<User> users = new ArrayList<>();

    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        return users.stream().filter(a -> a.getId().equals(id)).findFirst().orElse(null);
    }

}

すると、getterが見つからずにコンパイルエラーとなります。

/Users/t0k0sh1/Workspace/tutorial/src/main/java/com/t0k0sh1/tutorial/controller/UserController.java:15: エラー: シンボルを見つけられません
        return users.stream().filter(a -> a.getId().equals(id)).findFirst().orElse(null);
                                           ^
  シンボル:   メソッド getId()
  場所: タイプUserの変数 a

対処方法

build.gradleを以下のように書き換えることで問題を解消することができます。

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.0.4'
	id 'io.spring.dependency-management' version '1.1.0'
}

group = 'com.t0k0sh1'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.projectlombok:lombok:1.18.22'
	implementation 'org.modelmapper:modelmapper:3.1.1'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'

	// 以下の1行を追加する
	annotationProcessor 'org.projectlombok:lombok:1.18.22'
}

tasks.named('test') {
	useJUnitPlatform()
}

追加でannotationProcessor 'org.projectlombok:lombok:1.18.22'を追記します。(バージョン部分は元の記述に合わせてください)

Gradleプロジェクトの再ロード(右端のGradleタブを開いて更新ボタンをクリックする)し、プロジェクトのビルドを行なってください。

すると、先ほどまでエラーとなっていましたが、今度はビルドに成功します。

[Spring Boot]プロジェクトを作成する(Spring Initializr)

Spring Bootのプロジェクトの作成方法はいくつかありますが、本記事ではSpring Initializrを使って作成します。

Projectを選択する

執筆時点では「Gradle – Groovy」「Gradle – Kotolin」「Maven」から選択できます。使いたいものを選択してください。

ここでは「Gradle – Groovy」を選択しています。

Languageを選択する

執筆時点では「Java」「Kotolin」「Groovy」から選択できます。使いたいものを選択してください。

ここでは「Java」を選択しています。

Spring Bootのバージョンを選択する

Spring Bootのバージョンを選択してください。

ここでは「3.0.4」を選択しています。

Project Metadataを入力する

プロジェクトのメタデータを入力します。

内容はプロジェクトに合わせて設定してください。

Dependenciesを選択する

Dependenciesの選択は任意ですが、Webアプリケーションのプロジェクトを作成するので、Spring Webだけ追加しておきます。すでに使用するものが決まっている場合はそれも追加しておいてください。

プロジェクトをダウンロードする

GENERATEボタンをクリックしてプロジェクトをダウンロードしてください。

プロジェクトを展開・配置してIDEで開く

ではダウンロードしたプロジェクトを展開・配置して、IDEで開いてみましょう。ここではIntelliJ IDEA Ultimateを使用しています。

まずはダウンロードしたZIPファイルを展開し任意のフォルダに配置します。

$ unzip ~/Downloads/tutorial.zip -d ~/Workspace/

次にIDEでプロジェクトを開きます。

動作確認のためにアプリケーションを実行してみましょう。

アプリケーションが起動したらhttp://localhost:8080/にアクセスします。

Spring BootではMainメソッド持ったクラス以外何もないため、表示するページがなく、エラー画面が表示されますが、この画面が表示しているのであれば問題ありません。

[Oracle]ユーザー(スキーマ)を作成する

ローカル環境で使用する開発用・テスト用のユーザーを作成します。

今回使用した環境は以下のとおりです。

  • Oracle Database XE 21c

本記事で実行しているSQLは、すべてSYSTEMユーザーで実行しています。

テーブルスペースを確認する

まずは使用するテーブルスペースを確認します。

-- テーブルスペースを一覧表示する
SELECT TABLESPACE_NAME, INITIAL_EXTENT FROM DBA_TABLESPACES;
TABLESPACE_NAMEINITIAL_EXTENT
SYSTEM65536
SYSAUX65536
UNDOTBS165536
TEMP1048576
USERS65536

ここではDEFAULT TABLESPACEUSERSを使用し、TEMPORARY TABLESPACETEMPを使用します。

ユーザーを作成する

どのテーブルスペースを使用するかが決まったら、ユーザーを作成します。

-- ユーザーを作成する
CREATE USER EXAMPLE IDENTIFIED BY PASSWORD DEFAULT TABLESPACE USERS TEMPORARY TABLESPACE TEMP PROFILE DEFAULT;

ここではEXAMPLEというユーザー(スキーマ)をPASSWORDというパスワードで作成しています。

このままではログインすらできないユーザーですので、作成したユーザーに権限を与えます。ここではローカルでの開発用・テスト用のユーザーですので、作成しそうなオブジェクトを作成する権限は一通り与えておきます。

-- ユーザーに権限を与える
GRANT CREATE SESSION, CREATE TABLE, CREATE VIEW, CREATE SEQUENCE, CREATE TRIGGER, CREATE SYNONYM, UNLIMITED TABLESPACE TO EXAMPLE;

作成したユーザー名とパスワードでログインすれば、ユーザー名と同じスキーマが使用可能になっています。

パスワードの有効期限を確認・設定する(オプション)

この手順は必須ではありませんが、念のためパスワードの有効期限を確認しておきます。プロジェクトや職場のポリシーにもよりますが、開発用・テスト用のユーザーですので、パスワードの有効期限切れにはならない方が運用が楽だと思います。

今回使用したプロファイルはDEFAULTですので、DEFAULTプロファイルの有効期限を確認します。

-- DEFAULTプロファイルの有効期限を確認する
SELECT PROFILE, RESOURCE_NAME, RESOURCE_TYPE, LIMIT FROM DBA_PROFILES WHERE RESOURCE_NAME = 'PASSWORD_LIFE_TIME' AND PROFILE = 'DEFAULT';
PROFILERESOURCE_NAMERESOURCE_TYPELIMIT
DEFAULTPASSWORD_LIFE_TIMEPASSWORDUNLIMITED

今回はOracle XEを使用していますが、特に設定変更していない状態でUNLIMITEDになっています。

ここがUNLIMITED以外になっていて、無期限に変更したい場合は以下のSQLを実行します。

-- DEFAULTプロファイルの有効期限を無期限に変更する
ALTER PROFILE DEFAULT LIMIT PASSWORD_LIFE_TIME UNLIMITED;

SQL実行後に再度確認して、UNLIMITEDになっていることを確認してください。

モバイルバージョンを終了