ソフトウェアエンジニアの雑記

日々思ったことをまとめます

JavaでDataFrame操作をDFLibで試す

JavaでもDataFrameを使いたいとおもって、探すと、TableSawとかDFLibとかdataframe-ecがありました。軽量ライブラリのDFLibを使ってみようと思います。

dflib.org

サクッと試したいのでjbangで試すこととします。jbangの記事はこちら

サンプルソースはこちらになります。

DFLib Sample ( DataFrame implements Java) · GitHub

DataFrameに読み込ませて、ソートする

対象のデータファイルは下記のようになっています。

data.tsv

a   b   c   d
--  --  --  --
4   5   6   a
40  41  42  a
46  47  48  a
52  53  54  b
58  59  60  b
64  65  66  c
94  95  96  c

Javaコードは下記のようになります。DFLibはデータロード時にデフォルトでは文字列として扱います。数値として扱う場合は、intCol()で設定する必要があります。例の場合は、カラムcを数値として扱うようにします。こうするとソートが機能します。

///usr/bin/env jbang "$0" "$@" ; exit $?

//DEPS org.dflib:dflib:1.3.0
//DEPS org.dflib:dflib-csv:1.3.0
//JAVA 25+

import org.apache.commons.csv.CSVFormat;
import org.dflib.DataFrame;
import org.dflib.Printers;
import org.dflib.csv.Csv;

void main(String... args) {
    CSVFormat csvFormat = CSVFormat.Builder.create().setDelimiter("\t").build();
    DataFrame dataFrame = Csv.loader().format(csvFormat).header("a","b","c","d").intCol("c").offset(2).load("data.tsv").sort("c",false);
    IO.println(Printers.tabular(100,100).toString(dataFrame));
}

====出力結果

a  b   c d
-- -- -- -
94 95 96 c
64 65 66 c
58 59 60 b
52 53 54 b
46 47 48 a
40 41 42 a
4  5   6 a

上記のようにソートができます。

2つのファイルはDataFrameに読み込ませて、結合する

上記のdata.tsvとtype.tsvをDFLibで結合する。ファイルは下記。

// type.tsv

type    name
--  --
a   TypeA
b   TypeB
c   TypeC

DataFrameを別々に読み込みませて、カラムaとカラムtypeを指定して結合してみます。

void main(String... args) {
    CSVFormat csvFormat = CSVFormat.Builder.create().setDelimiter("\t").build();
    DataFrame dataFrame = Csv.loader().format(csvFormat).header("a","b","c","d").intCol("c").offset(2).load("data.tsv").sort("c",false);
    DataFrame dataFrameType = Csv.loader().format(csvFormat).header("type", "name").offset(2).load("type.tsv");

    DataFrame selectFrame = dataFrame.innerJoin(dataFrameType).on("d", "type").select();
    IO.println(Printers.tabular(100,100).toString(selectFrame.cols("a","b","c","name").select()));
}

a  b   c name 
-- -- -- -----
94 95 96 TypeC
64 65 66 TypeC
58 59 60 TypeB
52 53 54 TypeB
46 47 48 TypeA
40 41 42 TypeA
4  5   6 TypeA

上記のように結合できました。出力時にカラムも絞ることができます。

まとめ

Javaでも簡単にDataFrameが使えるようになります。グラフ描画もできますし、そこそこ使えるかなと思います。

ポエム

Java25になって、mainメソッドとかなくても簡単に書けるようになっているのは、便利です。void main() でコードが書けるので、サンプルコードもシンプルになりますし、jbangの機能も十分に使えるので、どんどん使っていければと思いますー。

MarpでMarkdownでスライドを作る

勉強会や登壇でスライドを作るのって結構大変で、ツールごとにお作法みたいなものがあって割と面倒だなーと思っておりました。Markdownで書けると最高だなって思ってましたが、なんかツールがあるっぽいので、ご紹介です。

Marp

Markdownでスライドが作れるMarpです。

marp.app

CLIVSCodeの拡張がありますので、好きな方を使うと良いと思います。

Marp記法

基本的にMarkdownですが、先頭にMarpであることを宣言します。

---
marp: true
theme: sample-theme
paginate: true
---

themeは、CSSで記述します。

/* @theme sample-theme */
/* モダンでクリーンなテーマ */

@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;500;700;900&family=Inter:wght@400;500;700&display=swap');

section {
  background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
  font-family: 'Noto Sans JP', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  color: #1a1a1a;
  padding: 60px 80px;
  font-size: 20px;
  line-height: 1.7;
}

h1 {
  color: #c41e3a;
  font-size: 56px;
  font-weight: 900;
  letter-spacing: -0.02em;
  margin-bottom: 20px;
  line-height: 1.2;
  border-left: 8px solid #c41e3a;
  padding-left: 24px;
}
...
...
...

VSCodeでのサンプル

VSCodeのサンプルはこちらになります。(最近、JavaでDataFrameを触ってて、結構いいので紹介スライドを作っているところです) VSCodeの拡張はリアルタイム反映なので、作業もサクサクです。

まとめ

PDF出力もPDF,HTML,PowerPointと十分な出力を標準で装備していますので、凝ったスライドでなければ、十分なツールになります。

dokcer composeを使う時に、プロジェクト名(name:)を入れよう

docker composeはローカル環境構築に大変便利ですが、複数プロジェクトで命名が競合することが多々あります。(portも競合しますが、それは仕方ない)

docker compose を利用する際には、必ずプロジェクト名(name:)を入れましょうってだけです。なお、container_name:が設定されている場合は、そちらが優先されます。

name: example-project # <- これを入れるだけ
services:
  adminer:
     image: adminer:latest
     ...
     ...

  db:
     image: mysql:8.4
     ...
     ...

確認してみます。

$  docker compose ps | awk '{print $1}'

NAME
example-adminer-1
example-db-1

対象は、cotainer_name, volume_name, network_name になります。複数プロジェクトを運営されているチームではやっておくと良いかと思います。

ThymeleafとAlpine.jsの簡単な連携

ThymeleafとAlpine.jsの簡単な連携の小ネタです。Thymeleafには、th:inlineというインライン処理というものがあり、指定した形式に加工してくれる便利なものがあります。

例えば、下記のようなJavaのMapデータがあります。

@Controller
@RequestMapping("sample")
class SampleController {
    @GetMapping
    public String index(Model model){
        Map<String,String> userMap = Map.of("user", "Taro");
        model.addAttribute("user" , userMap);
        return "index";
    }
}

Thymeleafの方でJSに渡したいとしたときに下記のような記述でJSで使えるように展開してくれます。(JSONにしてくれます)

<script th:inline="javascript">
   const userMap = /* [[ ${user} ]] */  { user: 'Taro'};
</script>

Alpine.jsで使用するときに、x-dataの中にThymeleafから直接データを入れようとすると、相性があんまりよくないので、このThymeleafのインライン処理を経由してバックエンドのデータを渡します。

<script th:inline="javascript">
   const userMap = /* [[ ${user} ]] */  { user: 'Taro'};
</script>

<div x-data="{data: userMap}"></div>

Alpine.js側のx-dataは、JS経由でデータをもらいます。Alpine.jsとの連携に割と便利なので、サクッと使い時にはよさそうです。

SpringBootでLocalDateTime.now()などの時刻管理を使用したときに、ユニットテストできるようにする方法

はじめに

SpringBootを利用していると、静的メソッド(LocalDateTime.now()など)を使用したくなる場面があります。静的メソッドはユニットテストをしづらくする側面があるのでなるべく使用を控えたいので、その回避方法になります。

目次

要約

要約としては、下記の2点になります。

  • java.util.Clockを利用する
  • @ConfigurationでBean化する
  • 利用したいクラスでDIする

静的メソッドのメリット・デメリット

静的メソッドは便利ですが、状態を持つと途端に面倒なツールに早変わりします。

デメリットとしては、

  • DIとの相性が悪い
  • テスタビリティが低い(PowerMockやPowerMockito、Mockito.mockStaticなどを使用する必要があり、十分ではない)
  • グローバルな情報を参照していると予期しない副作用のリスク
  • マルチスレッド処理での懸念

などなど、小さいアプリケーションではあんまりデメリットにはならないですが、ユニットテストが威力を発揮するような中〜大規模になってくると状態をもった静的メソッドは邪魔にしかなりません。よくあるのが、LocalDateTime.now()などをコアロジックで直接書いてしまうと、ユニットテスト時にMockito.mockStaticやPowerMockitoなどを使用しないといけなく不便になります。

特に、テスト対象のメソッド内で複数スレッドを作成し、その中でLocalDateTimeを呼んでいる場合は、Mockito.mockStaticで設定しても、範囲外なので設計を大きく直す必要があります。

Clock を使った解決策

1. Clock のBean定義

まず、ClockをSpringのBeanとして定義します。

@Configuration
public class ClockConfig {
    
    @Bean
    public Clock clock() {
        return Clock.systemDefaultZone();
    }
}

2. Service層での利用

ClockをDIして使用します。

@Service
@RequiredArgsConstructor
public class ClockServiceImpl implements ClockService {
    private final Clock clock;

    @Override
    public LocalDateTime getAfter5Minute() {
        return LocalDateTime.now(clock).plusMinutes(5);
    }
}

3. テストでの活用

テストでは、固定された時刻を返すClockを使用できます。

@ExtendWith(MockitoExtension.class)
class ClockServiceImplTest {

    @InjectMocks
    private ClockServiceImpl clockService;

    @Spy
    private Clock clock = Clock.fixed(Instant.parse("2026-01-01T00:00:00z"), Clock.systemUTC().getZone());;

    @Test
    void getNow() {
        LocalDateTime now = clockService.getAfter5Minute();
        assertEquals(LocalDateTime.parse("2026-01-01T00:05:00"), now);
    }
}

上記のようにすれば、ミリ秒やマイクロ秒の差でのテスト失敗が発生しません。

まとめ

Spring BootのDIの思想に沿ってClockを活用することで下記の恩恵に預かれます。

  • テストが容易になる
  • 時刻を完全に制御できる
  • タイムゾーンを明示的に管理できる
  • コードの保守性が向上する

小さな変更ですが、テスタビリティと保守性に大きな違いをもたらします。ぜひ実践してみてください。

JJUG CCC 2025 Fall 参加と登壇の記録

2025-11-15(土)にJJUG CCC 2025 Fall に参加してきました。今回は45分セッションにも登壇させていただいたので、その簡単なまとめを行います。

登壇パート

「レガシーで硬直したテーブル設計から変更容易で柔軟なテーブル設計にする」で登壇させていただきました。中身はほとんどJavaJDKまわりに関係ありませんが、採択していただきJJUG幹事の懐の大きさを感じた次第であります。JJUG CCC (Japan Java User Group Cross Community Conference)のコンセプトは素晴らしいと思います。

speakerdeck.com

登壇の振り返り

サマリは、下記になります。

  1. 初の45分セッション, そして割と大きめの部屋
  2. 理解してもらえてるかの不安
  3. スタッフの方が優しい
  4. 質問とかあって嬉しかった

初の45分セッション, そして割と大きめの部屋

20〜30分のセッションは割とやってきましたが、45分は初でそして、結構大きめの部屋ということで多くの方に聴いていただくことを考えると、内容的には抽象度の高いことも多いので理解していただけるのかが不安でした。

理解してもらえてるかの不安

今回は内容が割と一般的ではないことで、データベースの内情(テーブル設計など)は、コードよりも世の中に出回っていません。とはいえ、オーディエンスが感じているペインを想像し、具体に感じていただきながら、その他のセグメント(言語やミドルウェアなど)との進化の「対比」や既存のテーブル設計のベストプラクティスに対する「問い」を織り交ぜることに腐心しました。しかし、伝え方が上手くまとまらず、資料作りは結局徹夜になってしまいました。

スタッフの方が優しい

担当していただいたスタッフの方の対応が優しくて、登壇前も少し話したりとかして緊張が和らいだ気がします。ありがとうございます。

質問とかあって嬉しかった

セッションないで挙手制のアンケートなどを実施させていただきましたが、オーディエンスのみなさんも結構参加していただいて進めやすかったです。ありがとうございます。@suke_masaさんとか@web_shogo_nakaoさんとか話を振らせていただいたりもして、ありがとうございました。セッション終了後にも、ユミ駆動さんにも質問等いただいてフィードバックもしていただいて、登壇してよかったと思いました。

まとめ

今回、テーブル設計についてお話させていただきました。全体の10〜20%くらいの内容なので、アンケート等で評判がよければ次のCfPとかも実践編等で応募してみようと思います。

謝辞

JJUG幹事およびJJUG CCCスタッフの方々、運営お疲れ様でした。ありがとうございました。次回の開催も楽しみにしております。

また、いつもJJUG CCCのスライド等をまとめていただいてるYujiSoftwareさんのブログにもスライドのリンク先を載せていただきました。ありがとうございます。

yujisoftware.hatenablog.com

引き続きよろしくお願い致します。

GradleのVersion Catalogが面倒なので、extでそれっぽく済ませる方法

GradleのVersion Catalogがあっち書いて、こっち書いてが面倒なので、extで簡単に済ませる方法を書いておく。Version CatalogだとIDEの支援があるので便利なのだが、toml形式なのと、tomlだと-(ハイフン)でbuild.gradleだと.(ドット)なので統一感がないし、あんまりメリットを見いだせないので代替手段をメモ。

Version Catalogで解決したいこと

Version Catalogで主に解決したいことは下記になる。

  • ライブラリ、プラグインのバージョン管理
  • バンドル機能
    • ライブラリのグループ化みたいなもの

Version Catalog

Version Catalogはlibs.version.tomlを設置する。形式は最近流行りのTOML形式になります。

[versions]
spring-boot = "3.2.0"
spring-dependency-management = "1.1.4"
mysql = "8.0.33"
testcontainers = "1.19.3"
lombok = "1.18.30"
mapstruct = "1.5.5"

[libraries]
# Spring Boot
spring-boot-starter-web = { module = "org.springframework.boot:spring-boot-starter-web" }
spring-boot-starter-validation = { module = "org.springframework.boot:spring-boot-starter-validation" }
spring-boot-starter-actuator = { module = "org.springframework.boot:spring-boot-starter-actuator" }
spring-boot-starter-test = { module = "org.springframework.boot:spring-boot-starter-test" }

# 開発ツール
lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" }

[bundles]
spring-web = ["spring-boot-starter-web", "spring-boot-starter-validation"]
spring-data = ["spring-boot-starter-data-jpa", "mysql-connector"]

dev-tools = ["lombok", "mapstruct"]

[plugins]
spring-boot = { id = "org.springframework.boot", version.ref = "spring-boot" }
spring-dependency-management = { id = "io.spring.dependency-management", version.ref = "spring-dependency-management" }

利用するときは下記のようになります。(省略しています。)

plugins {
    id 'java'
    alias(libs.plugins.spring.boot)
    alias(libs.plugins.spring.dependency.management)
}

dependencies {
    // ライブラリを設置
    annotationProcessor libs.lombok
    annotationProcessor libs.mapstruct.processor

    // バンドルを使用してまとめて追加
    implementation libs.bundles.spring.web
    implementation libs.bundles.spring.data
    compileOnly libs.bundles.dev.tools
}

build.gradleのext でやる方法

extでやる方法だと下記になります。ext内でgroovyのMap構造に値を詰めて、 dependencies {}ブロックで取り出す形です。配列の順番に気を使いますが、versionsで宣言したものは、librariesで使用できます。

ext {
     versions = [
         mysql: '8.1.0'
     ]

    libraries = [
            spring: [
                    web    : 'org.springframework.boot:spring-boot-starter-web',
                    dataJpa: "org.springframework.boot:spring-boot-starter-data-jpa"
            ],
            mysql_jdbc: "com.mysql:mysql-connector-j:${rootProject.ext.versions.mysql}" 
    ]
}

dependencies {
    implementation rootProject.ext.libraries.spring.web
    runtimeOnly rootProject.ext.libraries.mysql_jdbc
}

メリット・デメリット

Version Catalog

  • メリット

    • IDEの支援がある
    • OpenRewriteなどのmigrationツールのサポートはきっとある
    • バンドル機能は便利そう
    • プラグイン管理もできるのは便利
  • デメリット

    • 管理するファイルが増える
    • TOMLだと-なのが、build.gradleだと.になって検索とかしづらい

build.gradleのext

  • メリット

    • 古いバージョンでも動作する
    • バージョン管理用のファイルを作らなくていい
    • 記法はある程度自由(Groovyスクリプトも動作させられる)
  • デメリット

    • IDEの支援はない(AIはある程度支援してくれる)
    • OpenRewriteなどのmigrationツールのサポートはなし
    • プラグインの管理はできない(プラグインにも同じ記法を入れる必要がある)

まとめ

Version CatalogはIDE支援やGradle公式がサポートしている機能なので今後の拡張性を期待したいところです。現時点では巨大プロジェクトでない限りはbuild.gradleまわりの依存関係を触る頻度なども多くないので、IDE支援はないですがextで対応しても良いかなと思います。