2026年6月アーカイブ

Spring Batch ネタで度々「ネット上の情報は、まだ Spring Batch 6 に対応したものが少ないようだ」という話を書いているが、そのためか Google 検索の AI様(生成 AI モデル「Gemini」?)に「バージョン 6 に対応したソースに変更して」などとお願いしても、今のところ今一つな結果になることが多い。

ライブラリの import パスについても毎回古いものをセットして、でも自信満々に「これが現在の 6
に対応した最適な形です」と言い切るのである。なんやねん、その自信。マジで AI 怖いわ(笑)

例えば、AI様が提示したこれ。

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.database.JpaItemWriter;
import org.springframework.batch.item.database.builder.JpaItemWriterBuilder;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;

実際には

import org.springframework.batch.core.job.Job;
import org.springframework.batch.core.step.Step;
import org.springframework.batch.infrastructure.item.ExecutionContext;
import org.springframework.batch.infrastructure.item.database.JpaItemWriter;
import org.springframework.batch.infrastructure.item.database.builder.JpaItemWriterBuilder;
import org.springframework.batch.infrastructure.item.file.FlatFileItemReader;
import org.springframework.batch.infrastructure.item.file.builder.FlatFileItemReaderBuilder;

こうである。

そのことを指摘すると、

「その通りです!ご指摘が完全に正しいです。」
「Spring Batch 6 で内部設計の整理が行われ、ご提示いただいた新しいインポートパスへ完全移行されました。」
「私の知識が古いバージョンのパス(Spring Batch 5以前)のままになっており、ご迷惑をおかけして大変失礼いたしました。」

と謝罪の言葉を発しながら、それ以外にも新たに気付いた点も修正したソースを返してくる(この、指摘したこと以上の間違いに自ら気付いて修正してくるところがすごい・・・というか、怖いな AI様)

ほんと、プログラマーの仕事がなくなる日も近い。これからはより上位の要件定義レベルの仕事しかなくなるだろうな。プログラマの大失職時代の幕開けや(^^;;;。俺はその前に引退できそうでよかったわ(笑)
昨日の晩飯は中広通りにある「長春飯店」で食べた。

なんで中広町の店で?って話なんだけど、広島西郵便局まで荷物受け取りに行ったのよ。
なんか、俺がいない土曜日に横川の仕事場に配達に来たみたいで、そのまま郵便局まで持って帰っちゃったんで。

20260624_choshun2.jpeg
郵便局って宅配の会社と違って、時間にルーズなんよね。平気で配達希望時間の一時間前とかに来て不在だったってすぐ局まで持って帰っちゃうからな。

ちゅうわけで、しかたなく土砂降りの雨の中を、片道2.2km、徒歩で30分もかけて広島西郵便局まで行ったのよ。
その帰り、下半身はびしゃ濡れで雨のかからないところで一休みしたかったし、しかもあまりに腹が減ったので飛び込んだのが「長春飯店」だったのだ。

入口の看板には「本場中国長春の味 ラーメン・定食・一品料理」って書いてある。
長春料理って知らんなあ。今ネットで調べたら、「中国東北部・吉林省長春市周辺の郷土料理で、寒冷な気候に合わせたボリュームのある肉料理や小麦粉料理、塩味・にんにく・醤油を効かせた素朴で食べ応えのある味わいが特徴」だって。へぇ。

この日食べたのは、特に長春料理というわけでもなさそうな、ラーメン・ミニ中華丼・点心・デザートの「定食Aセット」900円であった。

ラーメンは醤油味だね。スープがこぼれそうなくらい量が多い。中華屋のラーメンで多いパターンやな。腹がたっぷんたっぷんになるのよね(^^; 俺、残さず全部飲んじゃうから、スープ。

ミニ中華丼は鶏肉が入っててあとは野菜だけやね。うずらの卵や海鮮が全然入ってないのでちょっとさみしい

20260624_choshun1.jpeg
あと、「点心」ってどれ?高菜の漬物がついてきたけど、これじゃないよな?となると、このキャベツの千切りのことか?「点心」って言うのかね?

・・・てな具合に、運ばれてきた料理を見た瞬間には思ったんだけど、まあ、ラーメンも美味かったよ。なんか、スープにすごく旨味があって美味かったわあ。麺はいまいちだったけど(中華の麺料理って基本的に「麺を煮る」んで、ラーメンもちょっと茹ですぎ感があるんよね)・・・でも、しっかり腰のある麺で、グダグダにはなってなかったので食べられた。

中華丼は・・・まあ、味は普通。ただ、うずらの卵も海鮮も入ってないのでさみしい(笑)

また機会があればもう一回食べに行こうって思うんだけど、横川から行くと手前に「ばり馬ラーメン」があるからなあ・・・そっちに吸い寄せられちゃうんよねえ・・・デブ的に(^^;;;
火曜日の夜は「銀だこハイボール酒場 カープロード店」で反省会

なんの?そりゃ当然、この日のカープx巨人戦のよ。高校時代の同級生N◎とレフトスタンドから観てました。

20260623_carp1.jpeg
まあ、真に反省すべきは新井監督なんだけど、反省せんけえのお、一切、あの男は。代わりにわしらあが反省よお。

相変わらずまったく何もしない、したとしても後手後手の悪手ばかり。結局四年間、スタメンを固めることもできず・・・なんなん、新井って監督は。佐々岡よりはマシってこの三年間皆我慢してきたけど、もう限界やで、わしらあとしても。佐々岡よりマシじゃなかった。完全に同列の「監督をするべきではなかった人」や。

今日もチャンスで出してくる代打は皆不発。持丸に変えて石原って・・・何なん?持丸が左苦手なのはわかるわ。じゃけえつうて、石原出してなにか変わるん?「何で石原???」って叫んだわ、あの場で(^^;;; そしたらやっぱり翌日ネットはこの話題がバズってる(笑)

未だに「新井さーん」とか言って、「あの選手たちでは誰がやっても結果は同じ。てか、新井さんはしっかりやってる。新井さんが辞めたら勝てるんですか?あんな打てない野手たちじゃ誰がやっても一緒でしょ?新井さん、絶対来年もやってほしい。絶対結果でるから!」とか言うてる新井信者たちが一番たちが悪い。

火曜日も負け試合を観てとぼとぼ帰る俺たちの前に背番号25のユニ着たおばさん二人連れがいて、ずっと選手の悪口言ってるの。「なんで、あそこで初球に手を出すんかね」と、新井監督は悪くない、悪いのは選手とずっとしゃべってる・・・嫌な感じ。

新井が監督を続ける限り、カープはどんどんチームもファンも嫌な感じになっていくんじゃわ。

20260623_carp2.jpeg
ちなみに、この日の試合で反省せんといかんやつがもう一人。ピッチャーの

お前、なんなん?何を戸郷に四球出しとるん?そっから大量失点になったやん。いや、マジでカープのピッチャーって、ピッチャー相手に四球出すこと多いけど(カープ投手のお家芸?)、なんなん?ど真ん中に三球投げればええやん。それでヒット打たれる可能性なんか多くて30%やぞ。四球は100%塁に出るし、守ってる野手もだれて、ヒット打たれる以上にダメージが大きい。

ほんま、心が弱すぎるんじゃないんか?なんでピッチャー相手にあんなに慎重になり、結局逃げるんや?これも新井監督が三年かけて作り上げてきたチームの「雰囲気」がそうさせるんかの?
この間、CSV ファイルを処理する Spring Batch のソースをサンプルとして載せたので、今度は固定長ファイルを扱うプログラムを載せときます。ま、単なる自分用のメモですけど。

とは言っても、現時点で最新の Spring Batch 6 ですんなり動くサンプルコードがあまりネット上にもないので、初心者の人にはなんかの参考になるかも(初心者が Spring Batch でバッチなんか作らんやろうというご意見は聞き流します(笑))

■テストデータ(C:\Users\lovelyman\Documents\testdata_20260623_091037.txt)

000000100120260623091102001S00001
000000100120260623091102001E00001
000000110220260623091102001S00001
000000100320260623091102001S00001
000000100320260623091102001E00001
000000110220260623091102001E00001
000000100420260623091102001X00001
000000120520260623091102001S00001
000000120520260623091102001E00001

※トレイルランレースのデータをちょっと加工(笑)
1~10 ユーザID, 11~27 測定した時間(ミリ秒 3桁), 28~28 ステータス, 29~33 反応回数

■resources/application.properties ※データベースは H2 を使用

spring.application.name=BatchTest3
# H2 を PostgreSQL 互換モードで使用(テーブル名等は小文字で)
spring.datasource.url=jdbc:h2:./.data/h2/db;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
# H2 Console を有効化
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
# JPA/Hibernate設定
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
# バッチ完了後もサーバーを起動したまま(Webコンソール有効状態維持)
spring.batch.job.enabled=true
# Webアプリケーションとして常駐
spring.main.web-application-type=servlet

■Entity/TTimeRecordsEntity.java ※DB の table 構造

package com.netandfield.test.Entity;

import java.time.LocalDateTime;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Entity
@Table(name = "t_time_records")
@NoArgsConstructor
public class TTimeRecordsEntity {

    public TTimeRecordsEntity(Integer userId, LocalDateTime keepTime,
String stateCode, Integer readCount) {
        this.userId = userId;
        this.keepTime = keepTime;
        this.stateCode = stateCode;
        this.readCount = readCount;
    }

    // 主キーは userID
    @Id
    @Column(name = "user_id")
    public Integer userId;
    @Column(name = "keep_time")
    public LocalDateTime  keepTime;
    @Column(name = "state_code")
    public String stateCode;
    @Column(name = "read_count")
    public Integer readCount;

}

■Dto/TTimeRecordsDto.java ※読み込むデータ(テキスト)1行の構造

package com.netandfield.test.Dto;

import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 固定長レコードからの読み込みデータクラス
 */
@Data
@NoArgsConstructor
public class TTimeRecordsDto {

    private String userId;    // Integer だがファイルからは文字列として読み込む
    private String keepTime;  // LocalDateTime だがファイルからは文字列として読み込む
    private String stateCode;
    private String readCount; // Integer だがファイルからは文字列として読み込む

}

■Processor/TTimeRecordsItemProcessor.java ※読み込んだデータの編集処理(特に日時)

package com.netandfield.test.Processor;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

import org.springframework.batch.infrastructure.item.ItemProcessor;
import org.springframework.stereotype.Component;

import com.netandfield.test.Dto.TTimeRecordsDto;
import com.netandfield.test.Entity.TTimeRecordsEntity;

@Component
public class TTimeRecordsItemProcessor implements
ItemProcessor<TTimeRecordsDto, TTimeRecordsEntity> {

    // テキストデータの日付のフォーマット(例:20260623091102001)※ミリ秒以下 3桁
    private static final DateTimeFormatter FORMATTER =
DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");

    @Override
    public TTimeRecordsEntity process(TTimeRecordsDto dto) throws Exception {
        // テキストファイルから取得したデータをDBの項目の型に変換

        TTimeRecordsEntity record = new TTimeRecordsEntity();

        // Integer に変換
        record.setUserId(Integer.parseInt(dto.getUserId()));

        // String から LocalDateTime に変換
        if (dto.getKeepTime() != null && !dto.getKeepTime().isEmpty()) {
            record.setKeepTime(LocalDateTime.parse(dto.getKeepTime(),
FORMATTER));
        }

        // そのまま String
        record.setStateCode(dto.getStateCode());

        // Integer に変換
        record.setReadCount(Integer.parseInt(dto.getReadCount()));

        return record;

    }

}

@Component アノテーションがついているので、DI(依存性注入)コンテナが自動でコンポーネントとして注入される

■Configuration/BatchTest3Configuration.java

package com.netandfield.test.Configuration;

import jakarta.persistence.EntityManagerFactory;

import org.springframework.batch.core.job.Job;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.Step;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.infrastructure.item.ItemProcessor;
import org.springframework.batch.infrastructure.item.ItemReader;
import org.springframework.batch.infrastructure.item.ItemWriter;
import org.springframework.batch.infrastructure.item.database.JpaItemWriter;
import org.springframework.batch.infrastructure.item.database.builder.JpaItemWriterBuilder;
import org.springframework.batch.infrastructure.item.file.FlatFileItemReader;
import org.springframework.batch.infrastructure.item.file.builder.FlatFileItemReaderBuilder;
import org.springframework.batch.infrastructure.item.file.transform.Range;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileSystemResource;
import org.springframework.transaction.PlatformTransactionManager;

import com.netandfield.test.Dto.TTimeRecordsDto;
import com.netandfield.test.Entity.TTimeRecordsEntity;

import lombok.extern.slf4j.Slf4j;

@Configuration
@Slf4j
public class BatchTest3Configuration {

    // データを読む reader
    @Bean
    public FlatFileItemReader<TTimeRecordsDto> reader() {
        // 文字位置: 1-10 ID, 11-27 計測時間, 28-28 ステータス, 29-33 読込数)
        return new FlatFileItemReaderBuilder<TTimeRecordsDto>()
                .name("tTimeRecordsFileReader")
                .resource(new
FileSystemResource("C:\\Users\\lovelyman\\Documents\\testdata_20260623_091037.txt"))
                .fixedLength()  // 固定長ファイル(Fixed-Length File)処理
                .columns(new Range(1, 10), new Range(11, 27), new
Range(28, 28), new Range(29, 33)) // バイト位置を指定
                .names("userId", "keepTime", "stateCode", "readCount")
 // セットする項目名
                .targetType(TTimeRecordsDto.class)
                .build();
    }

    // データをDBに書き込む writer
    @Bean
    public JpaItemWriter<TTimeRecordsEntity>
writer(EntityManagerFactory entityManagerFactory) {
        return new JpaItemWriterBuilder<TTimeRecordsEntity>()
                .entityManagerFactory(entityManagerFactory)
                .build();
    }

    // 実際の処理(reader で読んで、processor で編集して、writer で書き出す
    // ※ processor は TTimeRecordsItemProcessor.java。@Component でコンポーネント化
    @Bean
    public Step tTimeRecordsStep(JobRepository jobRepository,
                    PlatformTransactionManager transactionManager,
                    ItemReader<TTimeRecordsDto> reader,
                    ItemProcessor<TTimeRecordsDto, TTimeRecordsEntity>
processor,
                    ItemWriter<TTimeRecordsEntity> writer) {
        return new StepBuilder("tTimeRecordsStep", jobRepository)
                // chunk メソッドには「件数(サイズ)」のみを渡す (V6仕様)
                // トランザクションマネージャーはメソッドチェーンで明示的に指定する
                .<TTimeRecordsDto, TTimeRecordsEntity>chunk(100)
                .transactionManager(transactionManager)
                .reader(reader)
                .processor(processor)
                .writer(writer)
                .build();
    }

    // ジョブの実行フローの設定(今回は Chunk Model の Step ひとつだけなので、単純)
    // これがないとジョブは実行されない
    @Bean
    public Job tTimeRecordsJob(JobRepository jobRepository, Step step1) {
        return new JobBuilder("tTimeRecordsJob", jobRepository)
                .start(step1)
                .build();
    }

}

■BatchTest3Application.java

package com.netandfield.test;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class BatchTest3Application {

    public static void main(String[] args) {
        SpringApplication.run(BatchTest3Application.class, args);
    }

}

実行結果は、「H2 コンソールは tomcat 経由で提供される」というエントリーの H2 コンソール画面をごらんください(笑)

ちゃんと DB 登録までできとるよ。
Spring Batch のプログラムを実行すると、

org.springframework.batch.infrastructure.item.ItemStreamException:
Failed to initialize the reader

というエラーになる。reader の初期化失敗

AI様にお伺いを立てると、「指定したファイルが読めない」ということのようだ。

ネットで見つけたサンプルコードのファイル名だけ変えてみたのだが、それがいけなかった。やっぱちゃんと出てきたステートメントの意味を調べつつやらないといかんね(^^;

読み込むファイルは、'src/main/resources' といったクラスパス(プロジェクト内のパス)ではなく、その外に存在している。
具体的には、'C:\Users\lovelychan\Documents\testdata_20260623_091037.txt' である。

最初、FlatFileItemReaderBuilder の resource を

.resource(new ClassPathResource("C:\\Users\\lovelychan\\Documents\\testdata_20260623_091037.txt"))

という具合に、ClassPathResource として指定していたので、プログラムは

src/main/resources/C:/Users/lovelychan/Documents/testdata_20260623_091037.txt

というわけわからんパスを探しに行って、Failed to initialize the reader になっちゃうのよね。

.resource(new FileSystemResource("C:\\Users\\lovelychan\\Documents\\testdata_20260623_091037.txt"))

という具合に OS のファイルシステム(外部の特定のフォルダ)のパスとして指定することで正常に実行されるようになった。

ちなみにクラスパス(ClassPathResource)として指定するファイルは、JARファイルなどに同梱する静的ファイルのようだ。例えばメッセージ番号とメッセージの対応表とか、そんな感じのやつかな。
サンプルプログラムだと、sample.txt をクラスパスに置いてるケースが多いが、実際の業務用のバッチであれば、処理対象のファイルは絶対クラスパスの外にいるわけで、基本的には FileSystemResource の設定となるね。
現在、Spring Batch の勉強をしている環境が MySQL や PostgreSQL を勝手にインストールできないため、勉強用のプログラムの試作では H2 というデータベースを使っている。

インストールをしなくても、プロジェクトの依存関係に H2 ライブラリを登録すれば開発したプログラムですぐ使えるようになるので便利。ただ、こんな DB があることをつい最近まで全然知らなかった(^^;;;。

AI様によると「Javaで記述された非常に軽量かつ高速なオープンソースRDBMS。サイズは約2.5MBとコンパクト。インストール不要で利用可能」というもので、データをメモリにロードしプログラムの実行が終わったらすぐに破棄するという使い方ができるので、単体テストで使うと便利・・・ということのようだ。
ちなみに俺はメモリ上への展開ではなく、ファイルに出力する形をとっている。

ただ、DBの読み書きがうまくいっているかはログとかで確認できるけど、プログラム実行後にテーブル内が確認したいという時はどうすんの?インストールしないから、PostgreSQL の psql みたいな操作ツールはないんよね?
心配ご無用。Web 画面で「H2 コンソール」というのが提供されている。

なるほどって思って、下記のような設定でコンソール利用可能にした。(H2 関係の設定のみ抜粋)
resource/application.properties

# H2 を PostgreSQL 互換モードで使用(テーブル名等は小文字で)
spring.datasource.url=jdbc:h2:./.data/h2/db;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
# H2 Console を有効化
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
# JPA/Hibernate設定
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

バッチが終了するとコンソールも閉じてしまうという話をチラっとみたので、バッチを途中でブレークして


にアクセスしてみたのだが「このサイトにアクセスできません localhost で接続が拒否されました。」になってしまう。8080 ポートで受信状態になってないときのエラーやん。
なんと、コンソール用の Webサービスは tomcat を使うようだ。H2 そのものが 8080 ポートへのアクセス機能を提供しているのかと思ってたが、よく考えたら、それだと Web アプリの開発のときに面倒だよな。8080 のポート番号も変えなきゃいけないし・・・

というわけで tomcat の依存関係の登録が必要。
pom.xml に以下の記述を追加し、

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Alt + F5 キー押下でプロジェクトの更新を行おう。
(当ブログの「Spring Batch の依存関係をあとから追加する(Maven 版)」も参照)

また、テスト用のバッチはあっという間に終わってしまうので、途中でブレークしてその間にコンソールを開くなどしないといけない。
そこで、バッチ終了後にコンソールが使えるよう、以下の記述も application.properties に追加する。

# バッチ完了後もサーバーを起動したまま(Webコンソール有効状態維持)
spring.batch.job.enabled=true
# Webアプリケーションとして常駐
spring.main.web-application-type=servlet

これで、バッチ終了後も Eclipse で見ると赤いストップボタンが有効になってて、まだ「実行状態」なのがわかる。

20260624_H2_0.jpg

この状態でもう一度


にアクセス。
無事、ログイン画面が表示されるでしょ?

20260624_H2_1.jpg

表示は左上のセレクターで「日本語」にできるよ。
JDBC URL にディフォルト値の「jdbc:h2:~/test」がセットされているので、application.properties
に記述しているとおり「jdbc:h2:./.data/h2/db」に変更する。

これで「接続」(Connect)ボタンを押下すればコンソール画面が開く。

20260624_H2_2.jpg

SQL文を実行してみる。お、ちゃんとテーブルにデータが登録されてるじゃん。

Eclipse 側で赤い押して処理を終了したら、もうアクセスはできません(笑)
org.springframework.batch 以下のライブラリを import しようと思ったら、「インポートされた
org.springframework.batch は見つかりません」のエラーとなる。

20260623_springbatch1.jpg

ああ、確かに、依存関係の設定で Spring Batch にチェックしとらんがな。

こういう場合は手動で(Maven プロジェクトなので)pom.xml に依存関係を記述する。

<略>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-h2console</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-batch</artifactId>
        </dependency>
    </dependencies>
<略>


依存関係を追加したら、プロジェクトを選び右ボタンメニューで、「Maven」→「プロジェクトの更新」を選択する。
これは、Alt + F5 キー押下でもいい。
「Maven プロジェクトの更新」窓が開くので、選択したプロジェクトにチェックがされているのを確認し「OK」ボタン押下。

プロジェクトの更新が行われれば、「インポートされた org.springframework.batch は見つかりません」のエラーも消える。
午後4時すぎから「3・6・5酒場」「銀だこ ハイボール酒場」とハシゴした日。〆ラーメンを立町の「中華蕎麦 やぶき」でキメた。さあ、ラーメンも食ったし帰るか・・・と思ったらまだ 6時過ぎかい!!(笑)
ああ、毎日午後 4時に仕事が終わればいいのに(笑)

「やぶき」は初めてだったのだが、飲み仲間のMイ氏曰く。「店内はレゲエ感があってオシャレじゃないすか?」。うん、そんな感じ(笑)
でも、客は俺みたいなおっさんばっかり。あのアフロヘアのスタッフ氏、「なんでおっさんばかり来るんだよ」って思ってたかな?ごめんね(笑)。あ、アフロ氏、店を出た俺を追いかけてきて、「今度来られた時使えるトッピングのサービス券です」って手渡ししてくれた。良い人だ(笑)

20260616_yabuki1.jpeg
で、「中華蕎麦」950円。お味の方は・・・。

スープ、美味いな。醤油スープなんだけど、澄んだきれいな見た目なのに旨味がすごい。いくらでも飲めるな。日頃醤油スープのラーメン、あまり食べないんで、その少ない経験で言うのはおこがましいんだけど、マジで最近食べた醤油ラーメンでは一番のスープだわ。

カウンターに置いてある「魚介ゆず」で味変しても更に美味い。

・・・が、残念なことに「麺の茹で方が緩い」「麺の表面がぬるぬる」と感じ、麺はイマイチだった。茹ですぎだし、湯切りが不十分?

このくらいの湯で具合が最近は流行りなんかね?パスタにもアルデンテってあるくらいで、やっぱ麺って「やや硬」くらいが一番美味いと思うのよね。
金曜日は、夕方千田町のお客さんのところで打ち合わせをして、終了後に広電のちんちん電車で横川駅まで移動。そのまま横川の仕事場には寄らず岩国に移動する予定だった。

20260619_akamaru1.jpeg
いつものように弁当を作っていくと、ずっと弁当殻を岩国まで持ち歩くことになるので昼飯は外食にした。袋町の職場から近い居酒屋「大手町酒場 赤まる AKAMARU」の昼定食を食べた。

実は、8年くらい前にも NHK ビルで仕事をしていたことがあり、その頃何度か訪店したことがある。そう、実に 8年ぶりの再訪だったのだ。

もう、その時何を食べたかは忘れたのだが、むっちゃ美味かったという記憶が残っており、この間から「昼を外食することがあれば『赤まる』に寄ってみよう」とずっと思っていたのだ。

今、ビルの工事中なのか入口がネットで囲まれているため店の存在に気づかず素通りしてしまった(^^;
本通りあたりまで歩いて「え?赤まるが無い!!?」と異変に気づき慌てて引き返し、やっと「赤まる」を発見した俺は

ひっきりなしに客は来るのだが、店内が広いので席は十分空いている。そして俺が注文したのは「からあげ定食」979円也。

実は今過去のブログを見てみると、8年前にも俺はからあげ定食を食べて絶賛しているのだが、そのときの価格は 680円であった。なんと、300円も値上がりしているのだ!!43%アップである・・・いや、俺は日本がデフレから脱却するのは必要なことで、物の値段が上がることは容認派・・・というより積極的に物の値段は上がるべきと思っているのだが、8年で 43%は若干俺の許容範囲をオーバーしているような・・・

20260619_akamaru2.jpeg
・・・が、やはり「赤まる」の唐揚げは美味かった。
もう、色が最高のきつね色で、衣はカリカリ、もちろん味も旨し、旨し。これはかなりレベルの高い「からあげ定食」だ。

8年前のブログを読むと、やはり俺は「からあげ定食」を絶賛していた。酒屋の唐揚げって美味いよね。一杯やりたくなっちゃう(笑)。間にコロナを挟みながらずっと本業は居酒屋の昼定食営業が続いているのも頷ける。

・・・が、43%の値上げかあ・・・と思ってしまうのである。

ま、仕方ない。時代は動いているのである。昔を懐かしんでも仕方ない。デフレ時代は遠い過去だ。俺もお前も、キスも知らなかったあの頃のピュアな子供時代には決して戻れないのと一緒である。
もう 3月下旬の話なんだけど、しばらく夜 8時、9時までの残業が続き、そのかいあって週末にはプログラム修正作業完了。金曜日の午後、「しばらく残業続いたし、週明けにならないとエンドユーザの検収も受けられないんで、まだ 4時ですけど帰られていいですよ。開いてる店があればどうぞお酒を・・・」と元請けの飲み女子の方に言っていただいたので(さすが酒飲みの気持ちがわかってらっしゃる(笑)日が高いうちから呑む酒は最高ですけえのお(笑))、遠慮なく退社。帰り道にある「立ち呑み処 サケサケ」に寄らせていただいた。

20260319_SAKESAKE1.jpeg
「サケサケ」の開店時間、17時と思ってたんだけど、16時だったんやぁ。なにせ一年ぶりくらいの訪店っていう、全然常連じゃないんでその辺の情報があいまいで・・・(^^;;;

この日は俺が口開けの客で、「すみませ~ん」と言いながら店に入ると、店主も「え?客?それともなんかのセールスマン?」と戸惑ったようで、ちょっと困ったような顔で俺を見つめる。「もう、やってますか?」と俺が確認したことで客と判断できたようで、「どうぞ」と店内にいざなってくれたのであった(笑)

カウンターの前に立ってお薦めの酒を確認。なんと「金雀」の熱燗があるという。「金雀」は大吟醸ばかりで熱燗なんかで呑んだことねえなあ。「金雀」にも吟醸酒じゃない純米酒があるの?
人見知りなので店主に聞くことも出来ず、とりあえず「金雀」の熱燗 660円を注文。

アテには地物の「柳たこ煮」450円を。
小さなタコを煮たやつだけど、これが美味い。勇気を持って店主に「柳たこってなんですか?」と聞くと「イイダコの仲間で、サイズはイイダコより小さいんですけど・・・」とのこと。「美味いですね」と言うと「ですね」と笑顔であった。

この日は早く帰れた分、何店かハシゴしてみたいと思っていたので、熱燗一杯でお暇することに。
ああ、やっぱり「サケサケ」の料理は美味いなあ。大満足であった。人間、やっぱり16時には仕事をあがって一杯呑むべきやね。(反対に定年退職して暇になったら、酒は絶対18時以降にならないと飲まないなどの自制が必要であろう)
いやあ、早い時間から酒呑むの最高!!

20260616_gindako1.jpeg
ちゃんと作られた美味い日本酒を置いている店にしか興味がなかった俺だが、先日行った焼鳥一本 39円の店といい、最近はハイボールやサワーをやりながら安いツマミをつつくという下賤の民、貧乏人的な飲み方もするようになってきた。もしかして、俺死ぬの?(^^;;; だから、とりあえず酒が飲めればいいやと深層心理で投げやりになってるんじゃないやろな?(^^;;;

ま、俺が単に貧乏なだけですけど(^^;;;

というわけで、地下の居酒屋を出た俺は、すぐ隣の「銀だこ ハイボール酒場」の暖簾をくぐったのである。
あの「築地銀だこ」がやってる安い呑み屋である。全国にあるね。以前の俺なら「まともな日本酒を置いてない店には行くだけ無駄なので行かん」と近寄らなかった系の店だ(笑)

いや、マジ、俺死ぬんとちゃう?(^^;;;

店は若い女の子だけでまわしているようだ。女子大生くらいの若い子が二人だけ。あとでバイトを始めて間もない感じの若い子が更に増えた。

この店には、飲み物とツマミのセットが 1,000円の「せんべろ」セット、1,500円の「せんごひゃくべろ」セット、2,000円の「にせんべろ」セットと三種類用意されているようだ。相手が小汚いおっさんなら間違いなく「せんべろ」を選択したのだが、相手は若い女子スタッフ。ついつい見栄を張って「せんごひゃくべろ」セットを選択した。

20260616_gindako2.jpeg
内容は、飲み物2杯(ハイボールとレモンサワーを選択した)、おつまみ三種(これは選択不可。この日は「マヨたこ4個」「タコのマリネ」「)で 1,500円也。ツマミも美味いし、まあ、1,500円でこれなら美味いよね。

しかし、この歳になってもまだ女の前で見栄を張ってしまうのか・・・男の業も罪よのお・・・

俺は熱いたこ焼きをハフハフと口の中で冷まして飲み込み、最近はただ尿を排出するだけのゴムホースを化した黒く巨大な肉茎が収まる股間をそっと見たのであった。

目に涙が浮かんでいたのは、熱いたこ焼きのせいだけだったのだろうか・・・
中国地方一の都市である広島市内でも、夕方16時から飲める店は少ない。

20260616_356_1.jpeg
広島東年金事務所での用事は 16時前には終わった。ふらふらと城南通りから相生通りに移動しつつ、何軒か気になる店を覗いてみたのだが、どこも 17時や 18時開店であった。

しかたないので中央通りの王将で餃子とビールでもやるか・・・と行ってみると、同じビルの地下に 15時からやってる居酒屋があるじゃあないの。看板に「お一人様大歓迎!!」って書いてあるし(笑)

それが、「格安ビールと鉄鍋餃子 3・6・5酒場(さんろくごさかば)」である。

いかにも全国チェーン店ですって佇まい。俺はあまりチェーン店系の飲み屋には行かないのだが、もう、一杯やれるのならどこだって良い(笑)。俺は意気揚々と地下への階段を駆け下りたのだ。

平日の 16時だったので店内はガラガラだったが、こんな時間から客はいる。年寄り二人連れ(ま、暇だしな)、サラリーマン二人連れ(仕事終わったんか!?) 、そして若い娘さんの二人連れの三組。娘さんたちグビグビいってる。なんか、いいなあ(笑)
俺も、早く引退して夕方から毎日呑みたいわ・・・祖生じゃ無理だけど(^^;

この店の特徴は生ビールの安さだ。中ジョッキではなく小ジョッキなんだけど、218円である。小にしても安いよね。
当然、もう生ビール一択だ。日本酒なんか、この手の居酒屋にありがちな「銘柄不明の熱燗と冷や」だけだし、焼酎も安いやつばっかだな(笑)

20260616_356_2.jpeg
そして肴は「冷やっこ」328円、「ハツ刺し」438円、「タコさんウィンナー」438円。値段は微妙(笑)。なんか(もちろん高くはないけど)食べ物は普通の値段。生ビールの安さが際立つなあ。肴をたくさん頼んだら普通の居酒屋になっちゃう?

ちなみに、「ハツ刺し」美味かったわあ(笑)。安い店の生肉に若干の不安はあったのだが杞憂だった。臭みがなく、それでいてハツ感はあった。
まあ、これで 438円なら、やっぱり安いな。

お代の合計は 1,600円。生ビール二杯飲んでもこの価格なのは、やっぱ安いか(笑)
4月の下旬に久しぶりに「竹乃屋 MINAMOA広島駅店」を訪ねたときの話。

ここはまずスマホで QR コードを読んで注文画面を出す必要があるのだが(最近、安い店はどこもそうだよね(笑))、その画面が何分待っても出てこないのである。
「しばらくお待ちください」「伝票データを準備中です。」と出たまま、リロードしようがまったく状況変わらずなのである。
いや、マジで 10分以上待ったよ(笑)。でも駄目(^^;;; どうしたらいいのかよくわからないし、俺、極端な人見知りなので店員を呼ぶことも出来ず・・・

20260427_takenoya1.jpeg
結局、店員のほうが気づいて来てくれたので「が、画面が出ないんだけど」と涙声で訴えたところ、やっと口頭で注文することができた。「な、生ビールを・・・」と伝える俺の声は、安堵か、怒りか、複雑な思いで震えていちゃのであった。

ちゅうか、10分間何も注文せずブツブツ言いながら画面を見て立ち尽くす熟年オヤジがいるのに何ですぐ気づかないのか?周りの客も気持ち悪そうに俺を見てぞ(^^;;; 相当異常な光景。立ち飲みコーナーは儲からないからと軽く扱われているんじゃないのか!?(^^;;;

その後も相変わらず画面は出なかったのだが、生ビールが運ばれてきたのと同じタイミングで男性スタッフがやってきて「すみません。サーバを再起動したのでもう大丈夫だと思います」と。確かに試してみると画面が出てきた。他の人たちは普通に画面から注文していたのでサーバそのものが落ちていたわけではなく、新しいセッションが張れない状況だったのかな?まあ、そういう障害ってネットワークの世界ではよくあるけど、だいたいケチってしょぼい設備で運用してることが多い。あるいはプログラムがしょぼくて古いセッション情報が残ったまま同時可能セッション数をオーバーしてるとか。

どちらにしても、もし、竹乃屋と同じメーカーのオンライン注文システムを検討している店はよく考えたほうがいいよ。俺が今まで使ってきた中で、一番駄目なのが竹乃屋のシステムやね。

その後も、立ち飲み専用の飲み物メニューが出てこなくて、「黒霧島ロック」539円を注文して震えたり(精算時には 99円で計算されるのだが、履歴にはずっと 539円で残ってて、それを見るたびに俺はちょっとだけ失禁していたのである。怖くて、怖くて)、この日は散々であった。

串は美味かったけどな。「鶏もも梅肉」「心残り」そして「親鶏ふりそで」。それだけにこのしょぼい注文システムが残念。

実は、先週訪店したときもなかなか画面が出てこなかった。俺の斜め前にいた女の人も同じ状況のようだった。
ホンマに、竹乃屋のシステムは最低である。外注して作ったのなら、竹乃屋からクレームを入れるべきだ。まあ、俺はもうそんな竹乃屋システムに慣れてしまったので、躊躇なく目の前のベルを鳴らし店員を呼び出し、「画面出ないんで、口頭で注文していいですか?」と声をかけられるようになった。極度の人見知りなのに。竹乃屋さん、俺を鍛えてくれてありがとう!!・・・じゃねえんだよ!(^^;;;
もう 4月30日の話だが、数年ぶり、いや、今ブログチェックしたら 16年ぶりだった(^^;;;段原で作業した帰りに「赤竜 段原店」で汁なし担々麺を食べたのである。

実はこの日は、午後から休みをもらったのだ。ゴールデン・ウィークだからね(笑)。一応「仕事してます」感を出すために午前中だけ段原の仕事場に顔を出し、月末の報告書とか作って12時で仕事を上がったのだ(^^;;;

20260430_akatatsu1.jpeg 20260430_akatatsu2.jpeg 20260430_akatatsu3.jpeg

「赤竜 段原店」は昼飯時しか営業していない。帰宅時に前を通るといつも店は閉まっていて、訪店する機会が全然なかったのである。(店主が熱烈なカープファンなんで、ナイターに行くためなんかね?(笑))

で、早速注文した「赤竜麻辣担々麺」。久しぶりに食べた感想。「美味いけど、うーん、ちょっと刺激が足りないような・・・」
この店はカウンターに唐辛子や花椒(ホアジャオ)が置いてない。うーん・・・と思いながらそのまま食べ続ける。

これも、後で自分のブログを検索してみてわかった。以前も俺は「なんか大人しめで物足りない」と感じ、その後「この店は、辛めで注文するのがディフォ」と気づいていたのだ。16年ぶりなんで、そんなこと忘れてたよ(笑)
今度行ったときは「辛め」で注文しよう。今度行けるのは年末か来年のゴールデン・ウィークになりそうだけど(^^;;;

そうそう。16年ぶりなので、もう一つ粗相をしていた。
「ええと、じゃあ、せきりゅうまーらーたんたんめんを一つ」って注文したんだけど、この店の名前は「せきりゅう」じゃねえ!「赤竜(あかたつ)」やぁ!!(^^;;;

恥ずかしい・・・。店主はどんな思いで俺の注文を聞いていたのであろうか・・・(^^;;;
先日の兵庫県ツーリング。

20260606_ibonoito2.jpeg
昼飯も食べずに「姫路城」方面や「はりまシーサイドロード」などを走ったので、ハイエースを置いている「道の駅 みつ」に帰ってきたときには相当な空腹を感じていた。

俺はハイエースに愛車 DUCATI MONSTER S2R 1000 を積んでいき、道の駅で降ろしてツーリングに出発。ハイエースはそのまま駐車場に置かせてもらうことがある。道の駅の駐車場は道の駅利用者のものなので、そういうときは必ず(金額にして 5千円前後は)道の駅を利用していくことにしている。

この日も 4千円ほどお土産を買ったあとで、飯も道の駅のレストラン「魚菜屋」で食べていくことにした。

注文したのは「海老天そうめん」1,200円である。
「そうめんに 1千円オーバーか・・・」と思わないでもなかったが、実はここ、揖保乃糸の地元なのである。そりゃ、まあ、そうめん食べるよね。
で、出てきた料理をみると、思ったよりしっかりした海老天が付いていて、これ、1,200円でもお得なんじゃないの?

なにより、そうめんの茹で方が最高である。キリッと氷で冷やされた麺が、喉越し最高!!完璧な茹で具合である。

20260606_ibonoito1.jpeg
そう。そうめんは「茹で方次第」なのだ。子供の頃は、祖母がぐだぐだに柔らかく煮たそうめんを食べて「ああ、そうめん美味くねえなあ。」と思ってたんだけど、大人になって自分で茹でて、ちゃんと冷たい水で洗ってから食べたそうめんは別物で、今でもそうめんだけは俺が茹でる担当である。そう、そうめんは茹で方次第でむちゃ美味い。

ぜひ、「道の駅 みつ」を訪れた方は「魚菜屋」でそうめんを食べてみてほしい。

あ、今回特にオチはありません(笑)
先週月曜日は土橋で作業があったので、19時前に作業が終了したらそのまま小網町の「汁なし担々麺専門店 楽(らく)」へ。

20260608_morijiro1.jpeg
実は、作業終了したらお好み焼食べようかなとか、とりあえず立ち飲み屋(「寅卯」ね(笑))で一杯やってからうどんでも食べて帰るかなとか、作業中は色々考えてたんだけど、いざ終わったらなにか不思議な力に誘われたように「楽」へ向かったのである(笑)。ビルの下でお客さんに「どっか寄っていくんですか?」って聞かれて「楽に」って即答してたからな。自分自身で「あ、そうなんや。今日は楽に寄るんだ、俺」とか思った(笑)

「楽」は汁なし担々麺の店なんだけど、「鶏そば」とか他にも美味いものがあって、俺はもっぱら二郎インスパイア系のラーメンを食べている。周りを見ても二郎インスパイア系の「楽二郎」を頼む人が多いみたい。
この日の俺も、汁なしの二郎インスパイア系ラーメン「森二郎」をチョイス。都会に行けば汁なし二郎系ってのも沢山あるのかもしれないが、広島近郊だと俺はここしか知らないなあ。情報があれば教えてほしい。

「楽」がいいところは、二郎系定番の太麺の他に細麺(ちゅうても、中太麺だけどね。二郎系の太麺からしたら細いって話)をチョイスできるってところ。俺は細麺原理主義者なので当然細麺を選ぶ。細麺にすると、ディフォルトで大盛りになる。太麺の場合と麺の量を合わせるってことだろうね。店主から一応「大盛りで?」って確認があるので「はい、大盛りで」と答える。それから「にんにくは?」と聞かれるので「マシで」と。マシマシは無いのでマシでね。でも、けっこうな量のおろし生にんにくが追加されるよ(笑)、マシマシな量の。

最後に「ご飯は?」と確認があるので「お願いします」と応えよう(笑)。「森二郎」には「ご飯(小)」がサービスで付いてくる。最後に残った汁に投入しておじや状態にしてフィニッシュするのだ。そこにたまったにんにくたちとともに(笑)

初めて「森二郎」を注文したとき、「いや、森二郎だけでこの量なんだから、最後に飯なんか絶対腹に入らんやろ」と思い「あ、ご飯はいいです」って断ったんだけど、いざ食べ終わると「あ、いけたかも」と後悔した。
俺はデブにしては食事の量は普通なんだけど、いやいや、意外にイケるんやなあ。自分の肥満児としての卑しさを過小評価してたで(笑)
それ以降、「森二郎」のご飯サービスは必ず受けているのである(笑)

20260608_morijiro3.jpeg
今回は初めてビールも一緒に注文した。いつも「森二郎食ったら、帰りに寅卯に寄って一杯やっていこう」と思うのだが、食べ終わったら「いや、もう食べ物どころか飲み物ももう腹に入れたくない」とそのまままっすぐ帰ってしまう。なので今回は最初からアルコールを摂取したのである。瓶ビールを注文したのだが小瓶だったので量的にちょうど良かった。満足、満足。「そうか。毎回もう(腹に)入らないと思ってたが、小瓶のびーるくらいは入ったんや」と、またひとつ肥満熟年男として一皮むけた気がする。

最近二郎系にハマっている娘に写真を送り、「今度一緒に森二郎食べに行こうね。汁なし二郎系ってなかなか無いので、(娘はオンラインゲームとかよくやっているので)ネットの向こうの豚どもが羨ましがるよ」というと「くくく・・・」と笑っていた。
水曜日。段原勤務だったので帰りに広島駅へ。

20260610_takenoya1.jpeg
これがよくない。吸い込まれるようにエレベーターに乗った俺は MINAMOA の 6階のレストラン街へ。その一番奥にある「竹乃屋 MINAMOA店」の立ち呑みコーナーを目指すのだった(笑)

以前もこのブログで書いたが、立ち呑みコーナーは焼酎が 99円なのである。ロックでもお湯割りでも 99円。5杯のんでも 495円。普通の居酒屋で 1杯の焼酎を飲むのと同じ金額で 5杯飲めるのだ。素敵じゃーん(笑)

そして、俺がこの店に吸い寄せられるもう一つの理由。それは「焼鳥が美味い!」である。そもそも竹乃屋は博多鶏皮串の店。だから他の焼鳥も美味い。ただ、酒の異常な安さに対して、焼鳥の値段は「普通」である。それがこの日、俺に「立ち呑み勝負敗退」の屈辱を与えることになるのである。

話を続ける。

何度かこの店を訪れ、すっかり俺にも「この店に来たら絶対頼んじゃう好物の串」ができた。それが「親鳥ふりそで」と「焼さば串」であり。どちらも一本税込み 209円である。いや、「焼さばって焼鳥じゃないじゃん!」という無粋なツッコミはやめていただきたい。俺はその好物の串を 2ずつと、これまた好物の「とりもも梅肉」焼鳥 1本、と一緒に 5本の串を注文。

20260610_takenoya2.jpeg
・・・がこの日は酒を飲みながらメッセージアプリで高校の同級生と弱い新井カープを嘆き、地元の飲み仲間と最近祖生で飲み会が開かれていないことを嘆いていると、なんか酒が進み、いつの間にかレモンサワー 1杯、芋焼酎ロック 2杯、芋焼酎お湯割り 2杯を飲み干していたのだ。
レモンサワーは 209円だけど、それでも飲み代は合計 605円である。5杯酒飲んで 605円。笑えるほど安い。

・・・が、結局この日のお代は 2,497円。完全な敗北である。
初めてこのブログを訪れた人は「え?なに?なんで急に敗北とか言い出してんの?」と不思議に思われたであろう。俺は立ち飲み屋を愛する立ち呑み戦士。俺には俺の譲れない立ち飲み屋で飲むときの「美学」がある。

それは「勘定が 2,000円を超えないこと!」・・・だ。

立ち飲み屋でだらだらと粘って大量の酒を飲んだり、あるいはバクバクと卑しく大量の肴を口にする、そのどちらも俺の「立ち呑み美学」では「格好悪いこと。立ち呑み戦士として負け」なのである。立ち飲み屋は「ぱっと飲んで、さっさと立ち去る」姿こそ美しい。その「大酒も飲んで無いし、バクバク食ってもない」証が 2,000円以内の勘定である。

20260610_takenoya3.jpeg
ついつい instagram やメッセンジャーでの会話が楽しくて長居してしまった。そのため、竹乃屋ではいつも肴は 5本の串と決めていたのに、「親鳥ふりそで」を 1本追加。さらに「心残り」「なんこつ」「とりハツ」と 3本もの串を更に追加注文してしまったのである。

しかし、メッセージのやり取りが楽しくて己の美学を蔑ろにしてしまうなんて・・・俺はいつの間に寂しい立ち呑み独居老人となってしまっていたのか!?

立ち飲み屋で 2,497円も飲み食いしてしまうなんて・・・俺は卑しい豚野郎です。デブ男です。女王様、そのムチでこの情けない敗者に罰を・・・
先日、仕事の関係で並木通りを歩いているときに「焼鳥全品39円」の看板が目に入った。

20260611_sankyu3.jpeg
その時は仕事中だったのであまりはっきりと確認する暇もなく通り過ぎたのだが、数日後、本屋に行こうと思って並木通りを横切ったときにそのことを思い出したのである。

記憶を頼りに探してみたが見つからず、結局、平和大通りに達する寸前、並木通りの本当に端っこにその店はあった。

「やきとりさんきゅう 並木本店」。名前も「39」である。

19時開店ということで、まだ一時間あったが、好奇心にかられて店を確認するため、小さな古ぼけたビルの2階へ。

廊下の先のどん詰まり。そこに確かに「さんきゅう」はあったが、ビルそのものの雰囲気も怪しいし、「焼鳥全品39円」では集まってくる客層もまともではないのでは・・・と不安になり、久しぶりに俺は「一人で入るのが怖い店に一緒に行ってあげる会」の理事をしている呑み仲間のMイさんを呼び出したのであった。

20260611_sankyu1.jpeg
そして、19時前にアリスガーデンで待ち合わせをして、いざ「さんきゅう」へ。
少し早く着いたので店の前で待っていると、本当に19時ぴったりに店の中からおじさんが出てきて、「準備中」の札を「営業中」に変えながら、俺たちの姿に気づき「おまたせしてすみません。どうぞ」と中に招き入れてくれたのだ。

あり?なんかまともな人だぞ。入れ墨だらけの若い男とか、完全なコミュ障のじいさんとか、まともじゃない店主を想像していたので驚くとともに、なんか「一串39円の店が開くのを待っている貧乏くさい熟年男性」という己の姿に少し羞恥を覚えたのであった。Mイさんもそうだったようで、「なんか開くのを待っていたのを店主に見られて恥ずかしいですね」と言うので、俺は激しく頷いたのであった(笑)

で、焼き鳥だが、本当に 39円だった。5本盛り合わせセットも当然 195円である。「マジか!?」と驚き、更に「さえずり」「ぼんじり」「なんこつ」を注文する。全部39円だ(笑)。
ただ、飲み物は普通の値段である(笑)。最初に注文した「りんご酢サワー」は 430円。糞不味かった(^^; いや、この店のりんご酢サワーが不味いのではなく、りんご酢サワーというものそのものが不味いのだ。絶対日頃であればこんな酸っぱいもの注文しないのだが、一串39円という値段に俺も相当動揺していたのだろう・・・

さっさとその酸っぱい飲み物を飲み干して、口直しに「雨後の月」の熱燗を一合。これは 630円。

20260611_sankyu2.jpeg
というわけで、この日の俺のお代は「串 8本と、サワーと日本酒。あ、わさび 30円も追加したわ」で 1,752円。あれ?計算合わんな?と気づかれた方、なかなかの数学脳ですな。そう、ここには「お通し」のお代が入っているのだ。350円也。

日本の居酒屋特有の商習慣「お通し」。いや、まあ、俺的には席のチャージ料くらいに思ってて「お通し文化」そのものには文句ないんだけど、「あのショボい切り干し大根の煮物が 350円!?」と一気にボラれた感が出て、39円串へのお得感も吹っ飛んでしまったのであった・・・。100円でもいいくらいのショボさだったもので・・・(^^;;;

ちなみに 39円の串は「スーパーで買ってレンチンして食べるパックの焼鳥」そのものである(^^;。そんなんわざわざ店で食べたくないって思う人もいるだろう。でも、スーパーで買うと、「ねぎま」「もも」「かわ」くらいしかないじゃん。39円で「ぼんじり」とか「なんこつ」とかも楽しめる(そこそこの味だけど(^^;)のはやっぱり良いね。350円のお通しさえなければ・・・(^^;
20260613_nodejp1.jpg

Electron でのアプリ開発のために Git インストール後、今度は Node.jp を・・・

実際に使うアプリを開発するので(実験目的ではないので)、Node.js LTS版(Long Term Support(長期サポート)版)をインストールする。


2.下の方にある Node.js 24.16.0 (LTS) のアイコンをクリック。


4.node-v24.16.0-x64.msi を実行。ひたすら「Next」で進み、「Install」実行。

5.新しく立ち上げたコマンドプロンプトで、Node.js 関係のコマンドにパスが通っていることを確認

Microsoft Windows [Version 10.0.26200.8524]
(c) Microsoft Corporation. All rights reserved.

C:\Windows\System32>where npm
C:\Program Files\nodejs\npm
C:\Program Files\nodejs\npm.cmd

C:\Windows\System32>where node
C:\Program Files\nodejs\node.exe

C:\Windows\System32>

通ってる。
※コマンドプロンプトには「環境変数を読み直す」機能はないので、新しいウィンドウを起動しような。

6.一応、バージョンも確認。

C:\Windows\System32>npm --version
11.13.0

C:\Windows\System32>node --version
v24.16.0

OK。いよいよ、Electron のプロジェクト作って簡単なプログラムを書いてみる。
なんか最近、ラーメンの話よりプログラム開発ネタの方が多いな。いかん、いかん。しっかりしないと痩せてしまう

・・・しかし、今回も技術的なメモ。Git のインストール。ほんとにインストールのメモだけで、中身はありませんけど(笑)

Spring Boot + Spring Batch で作ろうと思っていたアプリを(結局 Web 画面ベースだとローカルファイルのパスをサーバ側に渡せないことがわかったので)、Electron で作る方向に方針変更。まずは Electron のお勉強を。

というわけで、こちらのサイト


を参考に、Electron セットアップのために必要な Git(バージョン管理システム)をインストールします。

ちゅうわけで、コマンドプロンプトを起動(管理者権限でね)し、Git のインストール開始。

Microsoft Windows [Version 10.0.26200.8524]
(c) Microsoft Corporation. All rights reserved.

C:\Windows\System32> winget install --scope machine --id Git.Git -e --silent --disable-interactivity --force --accept-source-agreements --accept-package-agreements --override "/VERYSILENT /NORESTART /NOCANCEL /SP- /CLOSEAPPLICATIONS /RESTARTAPPLICATIONS /COMPONENTS=""icons,ext\reg\shellhere,assoc,assoc_sh"" /o:PathOption=Cmd /o:CRLFOption=CRLFCommitAsIs /o:BashTerminalOption=MinTTY /o:DefaultBranchOption=main /o:EditorOption=VIM /o:SSHOption=OpenSSH /o:UseCredentialManager=Enabled /o:PerformanceTweaksFSCache=Enabled /o:EnableSymlinks=Disabled /o:EnableFSMonitor=Disabled"
'msstore' ソースでは、使用する前に次の契約を表示する必要があります。
Terms of Transaction: https://aka.ms/microsoft-store-terms-of-transaction
ソースが正常に機能するには、現在のマシンの 2 文字の地理的リージョンをバックエンド サービスに送信する必要があります (例: "US")。

見つかりました Git [Git.Git] バージョン 2.54.0
このアプリケーションは所有者からライセンス供与されます。
Microsoft はサードパーティのパッケージに対して責任を負わず、ライセンスも付与しません。
ダウンロード中 https://github.com/git-for-windows/git/releases/download/v2.54.0.windows.1/Git-2.54.0-arm64.exe
  ██████████████████████████████  60.4 MB / 60.4 MB
インストーラーハッシュが正常に検証されました
パッケージのインストールを開始しています...
インストールが完了しました

C:\Windows\System32>

で、次は、Node.js をインストールします。Electron は Chromium と Node.js でデスクトップアプリを作るためのフレームワークじゃけえね。
そういえばそうだった。ずいぶん昔悩んだことがあるのだがすっかり忘れていた。

ファイルをアップロードするのではなく、単純にフルパスだけ Controller に渡してやって、実際のファイルの読み込みは Javaで行いたかったのだが、そういえばセキュリティ上の対策でローカルファイルのフルパスはブラウザからサーバ側には渡せないんだった。

一応、ファイル名を取ってくる処理を Spring Boot + Thymeleaf で書いてみたので、備忘録で載せておく・・・が、ファイル名しか取れないんじゃ意味がないんだよな(^^;;;
(ちなみに、これだけなら Thymeleaf で書かなくても、普通の HTML でいいじゃん・・・というご意見はあるでしょうが、フルパス取れたら色々拡張していこうと思ってたのよね)

■resources/templates/SelectLogs.html

<!DOCTYPE html>

<html xmlns:th="http://www.thymeleaf.org" lang="ja">

<head>
	<title>対象ログファイル選択</title>

	<script>
		document.addEventListener('DOMContentLoaded', function() {
			document.getElementById('fileInput').addEventListener('change', function(e) {
				// 選択されたファイルを取得
				const file = e.target.files[0];
				const displayArea = document.getElementById('fileNameDisplay');

				if (file) {
					// ファイル名を表示要素にセット
					displayArea.textContent = file.name;
				}
				else {
					// キャンセルされた場合などのリセット処理
					displayArea.textContent = 'ファイルが選択されていません';
				}
			});
		});
	</script>
</head>
<body>
	<form>
		<!-- ファイル選択ダイアログ -->
		<input type="file" id="fileInput" style="display: none;" />

		<!-- ダイアログを呼び出すボタン -->
		<button type="button" onclick="document.getElementById('fileInput').click();">
		ファイルを選択
		</button>

		<!-- ファイル名を表示する要素 -->
		<span id="fileNameDisplay">ファイルが選択されていません
	</form>
</body>

</html>

※注意
ちなみに、addEventListener な処理を頭(head の中とか)に書くと、まだ実際には body部の要素(例えば、'fileNameDisplay' って名前の span要素)が読み込まれていないので、document.getElementById('fileNameDisplay')って指定でエラーになって addEventListener されない。
この場合は、この 'change'イベントの addEventListener を 'DOMContentLoaded'イベント のaddEventListener で囲ってやれば、すべての読み込みが終了後に 'change'tイベント追加処理が実行されるので問題ない。
body の一番後ろに script を書けば良いんだけど、古い人間なので、script は head 内に書きたいのよね(笑)


■java/com/netandfield/test/controller/SelectLogsController.java

package com.netandfield.test.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/SelectLogs")
public class SelectLogsController {

	/**
	* 初期表示
	* @return
	* @throws Exception
	*/
	@GetMapping
	public String init() throws Exception {

		return "SelectLogs";

	}

}

ローカルファイルのパスをどうしても Java に渡す処理を Web技術(HTML,CSS,JavaScript)を使って書きたいのなら、Electronなどで「デスクトップアプリ」として作れってことみたい。

Spring Boot と Electron を組み合わせるというのはありだな。Electron でフロントエンドを作り、Spring Boot で Electronデータの画面とデータのやり取りをするだけの REST API を動かすって形で。
なるほど。しかし、これだと Thymeleaf の出番がない?(^^;;; そもそも Thymeleaf の勉強もしたくて、Spring Boot + Thymeleaf の業務システムを作ろう思ったのよね。

ま、ブラウザがサーバにフルパスを渡せられるようになったら「セキュリティ上、大変問題」ってのはわかるので、Electron の勉強するか・・・
Kato83 さんの Web サイト「Pulog プログラムのブログ 略してプログ」の中の「Spring BootでSpring BatchのChunkモデルを試す」というエントリーを参考に Spring Batch を勉強させてもらっているんですが、この記事が書かれたのが約 6年前ということで対象バージョンが古い。今は SpringBatch も 6 までバージョンが上がっていて、そのままソースを拝借したのでは動きません。
 
そこで、Spring Batch 6 に対応した修正を行ったので、ここで公開しておきます。
俺以上に Spring Batch 素人で、それでも「とりあえず動かしてみんとようわからん」という方向けです(笑)
 
プロジェクトの依存関係(Dependencies)とかは Kato83 さんのサイトを参考にしてください。
ちなみに、今回は com.netandfield.test フォルダの下に全ファイル配置しています。
(なので、Config用のクラスの中で、User や UserRepository の import は行っていません)
 
 
■BatchTest1/src/main/resourses/application.properties
 
spring.application.name=BatchTest1
spring.datasource.url=jdbc:h2:./.data/h2/db;MODE=MySQL
spring.jpa.hibernate.ddl-auto=update
spring.h2.console.enabled=true
 
 
■BatchTest1/src/main/java/com/netandfield/test/User.java
 
package com.netandfield.test;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Entity(name = "`user`")
@NoArgsConstructor
public class User {
    public User(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    public Integer id;
    public String firstName;
    public String lastName;
}
 
※注意
h2 データベースを MODE=MySQL で使っているせいか、テーブル名=user が MySQL の予約語 USER とかぶって expected "identifier" エラーとなってしまいます。
@Entity(name = "user") の user をバックスラッシュで囲んで、@Entity(name = "`user`") のようにしてください。
 
 
■BatchTest1/src/main/java/com/netandfield/test/UserRepository.java
 
package com.netandfield.test;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Integer> {
}
 
 
■BatchTest1/src/main/java/com/netandfield/test/BatchTest1Application.java
 
package com.netandfield.test;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class BatchTest1Application {

	public static void main(String[] args) {
		SpringApplication.run(BatchTest1Application.class, args);
	}

}
 
 
■BatchTest1/src/main/java/com/netandfield/test/BatchTest1Configuration.java
 
package com.netandfield.test;

import java.text.Normalizer;

import org.springframework.batch.core.job.Job;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.job.parameters.RunIdIncrementer;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.Step;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.infrastructure.item.ItemProcessor;
import org.springframework.batch.infrastructure.item.data.RepositoryItemWriter;
import org.springframework.batch.infrastructure.item.data.builder.RepositoryItemWriterBuilder;
import org.springframework.batch.infrastructure.item.file.FlatFileItemReader;
import org.springframework.batch.infrastructure.item.file.builder.FlatFileItemReaderBuilder;
import org.springframework.batch.infrastructure.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileSystemResource;
import org.springframework.transaction.PlatformTransactionManager;

import lombok.extern.slf4j.Slf4j;

@Configuration
@Slf4j
public class BatchTest1Configuration {

    @Autowired
    public UserRepository userRepository;

    /**
     * sample.csv ファイルを読み込む
     * @return バッチ処理用 Reader item
     */
    @Bean
    public FlatFileItemReader<User> reader() {
        return new FlatFileItemReaderBuilder<User>()
                .name("userItemReader")
                .resource(new FileSystemResource("sample.csv"))
                .delimited()
                .names("firstName", "lastName")
                .fieldSetMapper(new BeanWrapperFieldSetMapper<>() {{
                    setTargetType(User.class);
                }})
                .build();
    }

    /**
     * Reader から渡されたオブジェクトを保存する形に変換する
     * 今回はアルファベットの大文字変換と、半角カナを全角カナに変換している
     * @return 変換後に保存するアイテム
     */
    @Bean
    public ItemProcessor<User, User> processor() {
        return user -> {
            final var firstName = Normalizer.normalize(user.getFirstName().toUpperCase(), Normalizer.Form.NFKC);
            final var lastName = Normalizer.normalize(user.getLastName().toUpperCase(), Normalizer.Form.NFKC);
            final var transformedUser = new User(firstName, lastName);

            log.info("before converted : " + user);
            log.info("transformed user : " + transformedUser);
            return transformedUser;
        };
    }

    /**
     * Processor から渡されたオブジェクトを書き込む(保存処理をかける)
     * @return 保存処理用のリポジトリ
     */
    @Bean
    public RepositoryItemWriter<User> writer() {
        return new RepositoryItemWriterBuilder<User>()
                .repository(userRepository)
                .methodName("save")
                .build();
    }

    /**
     * 1つのバッチ処理の処理順やエラーハンドリング等の定義を行う
     * @return Jobを返却
     */
    @Bean
    public Job importUserJob(JobRepository jobRepository, Step step1) {
        return new JobBuilder("importUserJob", jobRepository)
                .incrementer(new RunIdIncrementer())
                .flow(step1)
                .end()
                .build();
    }

    /**
     * Jobの中に含まれるステップを定義する
     * @return ステップを返却する
     */
    @Bean
    public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
        return new StepBuilder("step1", jobRepository)
                .<User, User>chunk(10)
                .reader(reader())
                .processor(processor())
                .writer(writer())
                .build();
    }

}
 
 
テストデータは、Kato83 さんのものをそのまま使っています。
 
■BatchTest1/sample.csv
 
ジョン, スミス
田中, 太郎
ジョセフ, ジョースター
Erik, Satie
 
 
これらのファイルを用意して、BatchTest1Application.java を Java アプリケーションとして実行します(俺は Eclipse 上で実行してるもんで)
 
これで、
 
2026-06-09T17:18:46.581+09:00 INFO 684 --- [BatchTest1] [ main] o.s.batch.core.step.AbstractStep : Executing step: [step1]
2026-06-09T17:18:46.648+09:00 INFO 684 --- [BatchTest1] [ main] c.n.test.BatchTest1Configuration : before converted : User(id=null, firstName=ジョン, lastName=スミス)
2026-06-09T17:18:46.648+09:00 INFO 684 --- [BatchTest1] [ main] c.n.test.BatchTest1Configuration : transformed user : User(id=null, firstName=ジョン, lastName=スミス)
2026-06-09T17:18:46.649+09:00 INFO 684 --- [BatchTest1] [ main] c.n.test.BatchTest1Configuration : before converted : User(id=null, firstName=田中, lastName=太郎)
2026-06-09T17:18:46.649+09:00 INFO 684 --- [BatchTest1] [ main] c.n.test.BatchTest1Configuration : transformed user : User(id=null, firstName=田中, lastName=太郎)
2026-06-09T17:18:46.649+09:00 INFO 684 --- [BatchTest1] [ main] c.n.test.BatchTest1Configuration : before converted : User(id=null, firstName=ジョセフ, lastName=ジョースター)
2026-06-09T17:18:46.650+09:00 INFO 684 --- [BatchTest1] [ main] c.n.test.BatchTest1Configuration : transformed user : User(id=null, firstName=ジョセフ, lastName=ジョースター)
2026-06-09T17:18:46.650+09:00 INFO 684 --- [BatchTest1] [ main] c.n.test.BatchTest1Configuration : before converted : User(id=null, firstName=Erik, lastName=Satie)
2026-06-09T17:18:46.650+09:00 INFO 684 --- [BatchTest1] [ main] c.n.test.BatchTest1Configuration : transformed user : User(id=null, firstName=ERIK, lastName=SATIE)
2026-06-09T17:18:46.818+09:00 INFO 684 --- [BatchTest1] [ main] o.s.batch.core.step.AbstractStep : Step: [step1] executed in 236ms
2026-06-09T17:18:46.821+09:00 INFO 684 --- [BatchTest1] [ main] o.s.b.c.l.s.TaskExecutorJobLauncher : Job: [FlowJob: [name=importUserJob]] completed with the following parameters: [{JobParameter{name='run.id', value=1, type=class java.lang.Long, identifying=true}}] and the following status: [COMPLETED] in 248ms
2026-06-09T17:18:46.831+09:00 INFO 684 --- [BatchTest1] [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
 
※一部抜粋
 
このような結果が得られます。やった(笑)
 
今日は兵庫県内をツーリング。
ハイエースに愛機 Ducati Monster S2R 1000 を乗せて、まずは 4時間かけて兵庫県たつの市御津町の「道の駅 みつ」へ。4時間かかった(^^;;;
そこで S2R をおろして、まず向かったのは姫路城である。ベタな観光地だが、一度は本物を見ておきたいからな、城好きとしては。

・・・すんません、本当はそんなに城好きじゃありません。日本人の一般教養として押さえておきたいだけで(笑)

20260606_himejijyo1.jpeg 20260606_mitsu1.jpeg 20260606_himejigokoku1.jpeg

話を戻す。
「道の駅 みつ」からは主に二つのコースがあって、バイパス(?)を抜けると 30分。2号線を走ると 40分みたいな感じ?(詳しくは自力で調べてください(笑)。Google Maps とかで)
とりあえず行きは時間の短いコースで行くことにする。「道の駅 みつ」の駐車場は 17:30には閉鎖してしまうようなので、それまでに確実に帰ってこないといけないからな。

すでに時間は 15時。現地に着くのは 15:30頃である。どうも姫路城は「開城時間:午前9時00分から午後5時00分まで(閉門は午後4時00分)」とのことなので、中を見ることはできそうにないが、まあ、外観だけでも拝めればね・・・と。

拝めました(笑)。
姫路城の真正面の交差点から、遠くて少し霞んでいるけど、白鷺城と呼ぶに相応しい美しい姿を拝めましたよ(笑)

うん、これで満足。今後姫路城の話題が出た時、「ああ、俺も本物見たことあるよ。」と言えるぞ(笑)。「え?実物見たことないの?実物見ないと、なかなかあの美しさは理解できないと思うよ」とか言える(笑)
もう、2ヶ月以上前の話。

朝、7時過ぎの電車で岩国に帰ったんだが、電車に乗る前に腹ごしらえ。
横川駅には立ち食いうどんはないので、横川商店街の「立喰い 辰屋」へ。

20260327_tatsuya1.jpeg 20260327_tatsuya2.jpeg 20260327_tatsuya3.jpeg

もう、俺が以前横川に住んでいた 40年近く前からある店だ。実際、もっと昔からあるんだろう。
十日市に仕事場を借りていたときは通勤に横川駅を使っていたのでたまに寄ることもあったが、部屋を解約してからはすっかりその機会もなくなってしまった。

実に 8年ぶりの訪店である。

あの頃はおじいちゃんとおばあちゃんが店を切り盛りしていたが、今は俺と同世代のおっちゃん一人である。あのおじいちゃんの息子さんなのか、まったく赤の他人なのか・・・

若干戸惑いながら、俺は「天ぷらうどん」を注文。それに「しゃけのおむすび」を一つ付ける。
8年前は 400円だった天ぷらうどんも、今では 550円だ。店主の代替わりもあり、本当に時の流れを感じるなあ。

ああ、このやわやわの蒸し麺とでっかい天ぷら。最高。
そしてスープ。ここのスープの味が好きなのよ。「駅麺家」とは微妙に違う味。このスープとどろどろに溶けた天ぷらをずずずずずと啜る快感(笑)

帰りに、前日余ったものであろう「おつとめ品」のおはぎを嫁さんへのお土産に購入。
ごっつい大将の口から「早めに食べてくださいね」と優しい言葉。やっぱ立ち食いうどんは人情だ(わけわからん〆ですまん(^^;)

このアーカイブについて

このページには、2026年6月に書かれたブログ記事が新しい順に公開されています。

前のアーカイブは2026年5月です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

月別 アーカイブ

電気ウナギ的○○ mobile ver.

携帯版「電気ウナギ的○○」はこちら