2026年5月26日火曜日

SpringBoot でリクエスト情報を DTO に使うサンプルコード

SpringBoot でリクエスト情報を DTO に使うサンプルコード

概要

前回 SpringBoot から MySQL に接続する方法を紹介しました
その際はリクエストの情報をそのまま Entity に渡していましたがそれだといろいろと脆弱です (マスアサインメントなど)

なので通常は DTO(Data Transfer Object) と呼ばれる内部で扱うクラスに変換してから Entity に渡します

今回は DTO を使ったサンプルコードを紹介します
更に応用として Service レイヤーを追加して Repository とまとめる方法も紹介します

環境

  • macOS 26.4.1
  • openjdk 26.0.1
  • SpringBoot 4.0.6
  • jasypt-spring-boot 4.0.4
  • gradle 9.5.1
  • VSCode 1.121.0
  • MySQL 9.6.0

バリデーション用のライブラリ追加

spring-boot-starter-validation を使うので以下を追加します
バージョンは plugins 側で制御しています

  • vim build.gradle
plugins {
	id 'java'
	id 'org.springframework.boot' version '4.0.6'
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-validation'
}

DTO 用のクラスを作成する

基本的にはここでリクエスト値などのバリデーションを行います
今回はバリデーション用のライブラリにある基本的なバリデーションのみを追加します

  • vim src/main/java/com/example/demo/dto/UserRequest.java
package com.example.demo.dto;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;

public class UserRequest {

    @NotBlank
    private String name;

    @Email
    private String email;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

コントローラの修正

コントローラ側ではリクエスト情報を先程の RequestUser クラスに自動的に変換するように修正します
@Valid, @RequestBody を使います

  • vim src/main/java/com/example/demo/MainController.java
package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.example.demo.dto.UserRequest;
import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;

import jakarta.validation.Valid;

@Controller
@RequestMapping(path = "/demo")
public class MainController {
	@Autowired
	private UserRepository userRepository;

	@PostMapping(path = "/add")
	public @ResponseBody String addNewUser(
			@Valid @RequestBody UserRequest req) {
		User user = new User();
		user.setName(req.getName());
		user.setEmail(req.getEmail());

		userRepository.save(user);
		return "Saved";
	}

	@GetMapping(path = "/all")
	public @ResponseBody Iterable<User> getAllUsers() {
		return userRepository.findAll();
	}
}

とりあえずここで動作確認

まずはここでちゃんと動作するか確認します

  • ./gradlew bootRun --args='--jasypt.encryptor.password=xxx'

application.properties を暗号化しているので jasypt.encryptor.password を指定していますが暗号化していなければ不要です

curl で動作確認しましょう

  • curl -XPOST http://localhost:8080/demo/add -d '{"name": "First", "email": "someemail@someemailprovider.com"}' -H 'content-type: application/json'

正常系は上記です
バリデーションエラーの確認をする場合は以下のようにリクエストしてみましょう

  • curl -XPOST http://localhost:8080/demo/add -d '{"name": "First", "email": "error_email"}' -H 'content-type: application/json'
{"timestamp":"2026-05-23T00:54:34.921Z","status":400,"error":"Bad Request","path":"/demo/add"}

コンソールのエラーメッセージには以下のように表示されると思います

2026-05-23T09:54:34.907+09:00  WARN 69601 --- [demo] [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.lang.String com.example.demo.MainController.addNewUser(com.example.demo.dto.UserRequest): [Field error in object 'userRequest' on field 'email': rejected value [error_email]; codes [Email.userRequest.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userRequest.email,email]; arguments []; default message [email],[Ljakarta.validation.constraints.Pattern$Flag;@2601ac31,.*]; default message [電子メールアドレスとして正しい形式にしてください]] ]

更に Service レイヤーを追加する

これでも十分ですが通常は更に Service レイヤーを追加します
Service レイヤーでは Repository を扱い受け取った UserRequest を使用します
基本的には何かしらのビジネスルールなどを実装する必要があります

  • vim src/main/java/com/example/demo/service/UserService.java
package com.example.demo.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.example.demo.dto.UserRequest;
import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;

@Service
public class UserService {

	@Autowired
	private UserRepository userRepository;

	public void createUser(UserRequest req) {

		// 例:ビジネスルール
		if (userRepository.existsByEmail(req.getEmail())) {
			throw new IllegalArgumentException("既に登録されています");
		}

		User user = new User();
		user.setName(req.getName());
		user.setEmail(req.getEmail());

		userRepository.save(user);
	}

	public Iterable<User> getAllUsers() {
		return userRepository.findAll();
	}
}

UserRepository.java 修正

ビジネスロジック用の処理を追加します

  • vim src/main/java/com/example/demo/repository/UserRepository.java
package com.example.demo.repository;

import org.springframework.data.repository.CrudRepository;

import com.example.demo.entity.User;

public interface UserRepository extends CrudRepository<User, Integer> {
	boolean existsByEmail(String email);
}

MainController の修正

MainController で Repository は扱わずに Service を扱うように修正します

  • vim src/main/java/com/example/demo/MainController.java
package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.example.demo.dto.UserRequest;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;

import jakarta.validation.Valid;

@Controller
@RequestMapping(path = "/demo")
public class MainController {

	@Autowired
	private UserService userService;

	@PostMapping(path = "/add")
	public @ResponseBody String addNewUser(@Valid @RequestBody UserRequest req) {
		userService.createUser(req);
		return "Saved";
	}

	@GetMapping(path = "/all")
	public @ResponseBody Iterable<User> getAllUsers() {
		return userService.getAllUsers();
	}
}

動作確認

  • curl -XPOST http://localhost:8080/demo/add -d '{"name": "First", "email": "someemail2@someemailprovider.com"}' -H 'content-type: application/json'
  • curl http://localhost:8080/demo/all

同じメールアドレスで登録しようとするとエラーになります
現状は特にエラーを指定していないので500エラーになります

  • curl -XPOST http://localhost:8080/demo/add -d '{"name": "First", "email": "someemail@someemailprovider.com"}' -H 'content-type: application/json'
{"timestamp":"2026-05-23T01:50:23.001Z","status":500,"error":"Internal Server Error","path":"/demo/add"}

最後に

SpringBoot で DTO を使って直接リクエスト情報を扱わない方法を紹介しました
Service レイヤーを入れる場合にはビジネスロジックも実装するようにしましょう

個人的にメールアドレスの重複チェックはどちらかと言えばバリデーションに近いのではと思うのですがデータベースを使うバリデーションはビジネスロジックになるようでその場合は Service でバリデーションするのが良いようです

レスポンスに関しても DTO を使うのが定石なので次回はレスポンス用の DTO を作成し使うように修正します

参考サイト

2026年5月25日月曜日

SpringBoot で application.properties の設定を共通化する

SpringBoot で application.properties の設定を共通化する

概要

application.properties に共通設定を記載し application-dev.properties や application-prd.properties に差分の設定を記載します

環境

  • macOS 26.4.1
  • openjdk 26.0.1
  • SpringBoot 4.0.6
  • gradle 9.5.1
  • VSCode 1.121.0
  • MySQL 9.6.0

application.properties

ここに共通設定を記載します

spring.application.name=demo
# update はアプリケーション起動時に、Entityに対応するテーブルがなければ作成します
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/mydatabase
spring.datasource.username=ENC(bFMupckSRHJ/9QmXd1EVTw==)
spring.datasource.password=ENC(j64PBY1HLuuLo6Qoxo2xUg==)

# Jasypt 暗号化キーの設定(環境変数から取得)
jasypt.encryptor.algorithm=PBEWithMD5AndDES
jasypt.encryptor.iv-generator-classname=org.jasypt.iv.NoIvGenerator
jasypt.encryptor.password=${JASYPT_PASSWORD:default_password}

application-dev.properties

あとは各環境ごとの設定ファイルに差分や上書き設定を記載します

# Dev profile only: datasource URL override
spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/mydatabase_dev

application-prd.properties

# Prd profile only: datasource URL override
spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/mydatabase

実行

  • ./gradlew bootRun --args='--spring.profiles.active=dev'
  • ./gradlew bootRun --args='--spring.profiles.active=prd'

application.yaml ならまとめて記載もできる

  • vim application.yaml
spring:
  config:
    import:
      - classpath:application-common.yaml

---
spring:
  config:
    activate:
      on-profile: dev
    import:
      - classpath:application-dev.yaml

---
spring:
  config:
    activate:
      on-profile: prod
    import:
      - classpath:application-prod.yaml

で application-common.yaml に共通部分を記載しそれぞれの設定を application-dev.yaml/application-prd.yaml に記載すれば OK です

最後に

設定項目が増えた場合はファイルを分割しましょう
さらに共通設定が増えた場合は共通設定ファイルを作成し差分だけを各環境ごとに管理するようにしましょう

2026年5月24日日曜日

SpringBoot で appilcation.properties を暗号化して管理する

SpringBoot で appilcation.properties を暗号化して管理する

概要

王道は jasypt を使う方法なのでまずはこれを試します

環境

  • macOS 26.4.1
  • openjdk 26.0.1
  • SpringBoot 4.0.6
  • jasypt-spring-boot 4.0.4
  • gradle 9.5.1
  • VSCode 1.121.0
  • MySQL 9.6.0

パスワードなどの暗号化

password の部分は復号化に必要な秘密鍵を入力します
あとで jasypt.encryptor.password などアプリケーション内でパスワードを復号化する場合に使用するので忘れないようにしましょう

  • java -cp jasypt-1.9.3.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input="root" password="xxx" algorithm=PBEWithMD5AndDES
  • java -cp jasypt-1.9.3.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input='""' password="xxx" algorithm=PBEWithMD5AndDES

OUTPUT で表示された値を application.properties に記載します
パスワードの暗号化方法は mvn やシェルでも提供されているのでお好みに応じて変更してください

application.properties

暗号化した情報を記載します
かならず ENC で囲って定義します

  • vim src/main/resources/application.properties
spring.application.name=demo
# update はアプリケーション起動時に、Entityに対応するテーブルがなければ作成します
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/mydatabase
spring.datasource.username=ENC(bFMupckSRHJ/9QmXd1EVTw==)
spring.datasource.password=ENC(j64PBY1HLuuLo6Qoxo2xUg==)

# Jasypt 暗号化キーの設定(環境変数から取得)
jasypt.encryptor.algorithm=PBEWithMD5AndDES
jasypt.encryptor.iv-generator-classname=org.jasypt.iv.NoIvGenerator
jasypt.encryptor.password=${JASYPT_PASSWORD:default_password}

build.gradle

jasypt を spring-boot で使うための設定を追加します

implementation 'com.github.ulisesbocchio:jasypt-spring-boot-starter:4.0.4' を追記します

  • vim build.gradle
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-webmvc'
	implementation 'com.github.ulisesbocchio:jasypt-spring-boot-starter:4.0.4'
	developmentOnly 'org.springframework.boot:spring-boot-docker-compose'
	runtimeOnly 'com.mysql:mysql-connector-j'
	testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa-test'
	testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
	testImplementation 'org.springframework.boot:spring-boot-testcontainers'
	testImplementation 'org.testcontainers:testcontainers-junit-jupiter'
	testImplementation 'org.testcontainers:testcontainers-mysql'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

アプリケーション

@EnableEncryptableProperties を追記します
これだけでアプリで自動的に使ってくれます

  • vim src/main/java/com/example/demo/AccessingDataMysqlApplication.java
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.ulisesbocchio.jasyptspringboot.annotation.EnableEncryptableProperties;

@EnableEncryptableProperties
@SpringBootApplication
public class AccessingDataMysqlApplication {

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

}

起動

  • JASYPT_PASSWORD="xxx" ./gradlew bootRun

もしくは

  • ./gradlew bootRun --args='--jasypt.encryptor.password=xxx'

もし jasypt.encryptor.password の復号化パスワードが違う場合はアプリが起動できません

最後に

SpringBoot で jasypt を使って秘密情報を暗号化する方法を紹介しました
かなり簡単にできるので必須の設定かなと思います
暗号化方式もたくさんあるので好きなものを使うといいかなと思います

参考サイト

2026年5月23日土曜日

SpringBoot のプロジェクトにフォーマッターとリンターを導入する

SpringBoot のプロジェクトにフォーマッターとリンターを導入する

概要

フォーマッタは spotless でリンターは checkstyle + spotless を使います
googleJavaFormat は Java26 では使えないので eclipse を使います

環境

  • macOS 26.4.1
  • openjdk 26.0.1
  • SpringBoot 4.0.6
  • gradle 9.5.1
  • VSCode 1.121.0
  • MySQL 9.6.0

build.gradle

plugins {
	id 'java'
	id 'org.springframework.boot' version '4.0.6'
	id 'io.spring.dependency-management' version '1.1.7'

	id 'checkstyle'
	id 'com.diffplug.spotless' version '6.25.0'
}

checkstyle {
	toolVersion = '10.12.0'
}

spotless {
	java {
		eclipse()
		target 'src/**/*.java'
	}
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(26)
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-webmvc'
	developmentOnly 'org.springframework.boot:spring-boot-docker-compose'
	runtimeOnly 'com.mysql:mysql-connector-j'
	testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa-test'
	testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
	testImplementation 'org.springframework.boot:spring-boot-testcontainers'
	testImplementation 'org.testcontainers:testcontainers-junit-jupiter'
	testImplementation 'org.testcontainers:testcontainers-mysql'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

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

追記しているのは以下の部分だけです

plugins {
	id 'java'
	id 'org.springframework.boot' version '4.0.6'
	id 'io.spring.dependency-management' version '1.1.7'

	id 'checkstyle'
	id 'com.diffplug.spotless' version '6.25.0'
}

checkstyle {
	toolVersion = '10.12.0'
}

spotless {
	java {
		eclipse()
		target 'src/**/*.java'
	}
}

config/checkstyle/checkstyle.xml

<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
  "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
  "https://checkstyle.org/dtds/configuration_1_3.dtd">

<module name="Checker">
  <module name="TreeWalker">
    <module name="UnusedImports"/>
  </module>
</module>

実行

spotless はフォーマットしてリントもします

  • ./gradlew spotlessApply
  • ./gradlew spotlessCheck

最後の stylecheck でもリントします

  • ./gradlew checkstyleMain

vscode 側の設定

保存時にフォーマットしたい場合には settings.json に以下を追記します

{
  "editor.formatOnSave": true
}

全部一気に lint する場合

build.gradle に以下を追記します

tasks.register('format') {
	dependsOn 'spotlessApply'
}

tasks.register('lint') {
	dependsOn 'spotlessCheck', 'checkstyleMain'
}
  • ./gradlew format
  • ./gradlew lint

最後に

SpringBoot のプロジェクトにフォーマッタとリンタを導入してみました

VSCode で Extention Pack for Java を使っている場合は LSP がフォーマットしてくれますがそれはあくまでも VSCode 側のフォーマッタなので別途 CI などでチェックする場合には spotless などを使いましょう

2026年5月22日金曜日

SpringBoot で MySQL に接続する方法

SpringBoot で MySQL に接続する方法

概要

前回 SpringBoot の開発環境を構築しました
今回はアプリケーションを修正し MySQL を使ったアプリにします

環境

  • macOS 26.4.1
  • openjdk 26.0.1
  • SpringBoot 4.0.6
  • gradle 9.5.1
  • VSCode 1.121.0
  • MySQL 9.6.0

MySQL 準備

使えれば何でも OK です

  • brew install mysql

あとで説明もしますが今回は docker compose を使ってアプリ起動時に自動で mysql も起動して勝手にその mysql を使ってくれる構成なので最悪 mysql はインストールしないでも OK です

Spring Initializr を使ってプロジェクトを作成

https://start.spring.io/ でプロジェクトの雛形を作成します
前回と違うのは Dependencies に MySQL に関連する項目も選択する部分です

  • Project -> Gradle - Groovy
  • Language -> Java
  • Spring Boot -> 4.0.6
  • Dependencies -> Spring Web, Spring Data JPA, MySQL Driver, Testcontainers, Docker Compose Support
  • Java -> 26

で Generate すると demo.zip がダウンロードできます

demo.zip を展開し VSCode で開く

展開してできた demo フォルダを VSCode で開きましょう
拡張は以下をインストールします

  • Extension Pack for Java
  • Spring Boot Extension Pack

Entity ファイルの作成

データベースの構造を定義します
Spring はこの Entity ファイルの定義に従って自動的にデータベースをマイグレートとしてくれます

  • vim src/main/java/com/example/demo/User.java
package com.example.demo;

import org.jspecify.annotations.Nullable;

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

@Entity // これはHibernateにこのクラスからテーブルを作成するように指示します
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private @Nullable Integer id;

    private String name;

    private String email;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

Repository の作成

Spring は Entity 用のリポジトリを作成すると最低限の CRUD 処理を暗黙的に作成してくれます

それ以外の複雑な SQL を実行したい場合はここに別途定義します
ファイルに基本的な CRUD 処理がないのは Spring が自動で生成してくれているためです

  • vim src/main/java/com/example/demo/UserRepository.java
package com.example.demo;

import org.springframework.data.repository.CrudRepository;

// これはSpringによってuserRepositoryというBeanに自動的に実装されます

public interface UserRepository extends CrudRepository<User, Integer> {

}

コントローラの作成

今回はユーザを登録する処理と登録したユーザを取得する処理を追加します

  • vim src/main/java/com/example/demo/MainController.java
package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller // これは、このクラスがコントローラーであることを意味します
@RequestMapping(path = "/demo") // これはURLが/demoで始まることを意味します(アプリケーションパスの後)
public class MainController {
    @Autowired // これは、userRepositoryというBeanを取得することを意味します
    // Springによって自動的に生成されるため、データ処理に使用します
    private UserRepository userRepository;

    @PostMapping(path = "/add") // POSTリクエストを/demo/addにマッピングします
    public @ResponseBody String addNewUser(@RequestParam String name, @RequestParam String email) {
        // @ResponseBody は、返される文字列がレスポンスであり、ビュー名ではないことを意味します
        // @RequestParam は、GET または POST リクエストからのパラメータであることを意味します

        User n = new User();
        n.setName(name);
        n.setEmail(email);
        userRepository.save(n);
        return "Saved";
    }

    @GetMapping(path = "/all")
    public @ResponseBody Iterable<User> getAllUsers() {
        // これはユーザーのJSONまたはXMLを返します
        return userRepository.findAll();
    }
}

メインアプリケーションの作成

@Controller で定義したクラスは自動的に読み込まれるので特にアプリケーション側で指定する必要はありません

  • vim src/main/java/com/example/demo/AccessingDataMysqlApplication.java
package com.example.demo;

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

@SpringBootApplication
public class AccessingDataMysqlApplication {

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

}

アプリ起動

ローカルでアプリを起動します

  • ./gradlew bootRun

各種必要な jar のダウンロードなどが始まります
基本的には ~/.gradle/ にダウンロードされます

最終的に EXECUTING になれば起動しています

また今回は docker compose plugin を使用しているためプロジェクト配下に compose.yaml を自動的に作成しその compose.yaml を bootRun 時に自動で起動します
なので mysql コンテナが自動的に作成されかつ Entity に定義したテーブル情報も自動で生成されます

mysql> use mydatabase;
Database changed

mysql> show tables;
+----------------------+
| Tables_in_mydatabase |
+----------------------+
| user                 |
| user_seq             |
+----------------------+
2 rows in set (0.002 sec)

動作確認

  • $ curl http://localhost:8080/demo/add -d name=First -d email=someemail@someemailprovider.com
Saved
  • curl http://localhost:8080/demo/all
[{"email":"someemail@someemailprovider.com","id":1,"name":"First"}]

他のデータベースに接続する方法

org.springframework.boot:spring-boot-docker-compose は自動的に compose.yaml を読み込み接続します
手動で設定した MySQL に接続する場合は application.properties に以下のように記載します

spring.application.name=demo
# update はアプリケーション起動時に、Entityに対応するテーブルがなければ作成します
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/mydatabase
spring.datasource.username=root
spring.datasource.password=

これで更にプロジェクトは以下にある compose.yaml を削除するかサブディレクトリに移動するか compose.yaml にラベルを追記すれば application.properties にあるデータベースに接続します

services:
  mysql:
    image: 'mysql:latest'
    environment:
      - 'MYSQL_DATABASE=mydatabase'
      - 'MYSQL_PASSWORD=xxx'
      - 'MYSQL_ROOT_PASSWORD=xxx'
      - 'MYSQL_USER=myuser'
    ports:
      - '3306'
    labels:
      org.springframework.boot.ignore: true

最後に

SpringBoot で MySQL に接続する方法を紹介しました
Rails や Jango の用に CRUD ツールも備わっているのでかなり簡単に接続/操作できる印象です

本番では Liquibase や Flyway などのマイグレーションツールを使ってバージョン管理しましょう

参考サイト

2026年5月21日木曜日

macOS + VSCode で SpringBoot を試す

macOS + VSCode で SpringBoot を試す

概要

VSCode を使ってコーディングできる環境を構築しアプリをビルドし動作確認する環境まで構築します

環境

  • macOS 26.4.1
  • openjdk 26.0.1
  • gradle 9.5.1
  • VSCode 1.121.0

準備

  • brew install openjdk
  • sudo ln -sfn /opt/homebrew/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk
  • brew install gradle

Spring Initializr を使ってプロジェクトを作成

https://start.spring.io/ でプロジェクトの雛形を作成します

  • Project -> Gradle - Groovy
  • Language -> Java
  • Spring Boot -> 4.0.6
  • Dependencies -> Spring Web
  • Java -> 26

で Generate すると demo.zip がダウンロードできます

demo.zip を展開し VSCode で開く

展開してできた demo フォルダを VSCode で開きましょう
拡張は以下をインストールします

  • Extension Pack for Java
  • Spring Boot Extension Pack

サンプルアプリケーション

demo 内にすでに src/main/java/com/example/demo/DemoApplication.java がありますが削除して新しいアプリを作成します

まずはコントローラを作成します
/ にアクセスしたら簡単なメッセージを返却するコントローラを作成します

  • vim src/main/java/com/example/demo/HelloController.java
package com.example.demo;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

  @GetMapping("/")
  public String index() {
    return "Greetings from Spring Boot!";
  }

}

次にコントローラを使用するアプリケーションを追加します
これがいわゆるメインファイルになります

  • vim src/main/java/com/example/demo/Application.java
package com.example.demo;

import java.util.Arrays;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class Application {

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

  @Bean
  public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
    return args -> {

      System.out.println("Let's inspect the beans provided by Spring Boot:");

      String[] beanNames = ctx.getBeanDefinitionNames();
      Arrays.sort(beanNames);
      for (String beanName : beanNames) {
        System.out.println(beanName);
      }

    };
  }

}

アプリ起動

ローカルでアプリを起動します

  • ./gradlew bootRun

各種必要な jar のダウンロードなどが始まります
基本的には ~/.gradle/ にダウンロードされます

最終的に EXECUTING になれば起動しています

動作確認

  • curl http://localhost:8080
Greetings from Spring Boot

ユニットテストを追加する

ユニットテストも追加してみます
まずはテスト用のライブラリを追加します

testImplementation の行を追加しましょう
あれば追加不要です

  • vim build.gradle
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-webmvc'
	testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

src/test/java/com/example/demo/DemoApplicationTests.java がありますが削除して OK です
新たに以下を追加します

  • src/test/java/com/example/demo/HelloControllerTest.java
package com.example.demo;

import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

@SpringBootTest
@AutoConfigureMockMvc
public class HelloControllerTest {

  @Autowired
  private MockMvc mvc;

  @Test
  public void getHello() throws Exception {
    mvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk())
        .andExpect(content().string(equalTo("Greetings from Spring Boot!")));
  }
}

あとはテストを実行します

  • ./gradlew tasks

でタスクを確認し test タスクを実行します

  • ./gradlew test
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended

BUILD SUCCESSFUL in 4s
4 actionable tasks: 2 executed, 2 up-to-date
Consider enabling configuration cache to speed up this build: https://docs.gradle.org/9.4.1/userguide/configuration_cache_enabling.html

最後に

macOS + VSCode で Spring Boot を試してみました
まだ簡単なアプリを起動しただけなので次回はデータベースと連携してみたいと思います

後片付け

  • rm -rf ~/.gradle/
  • VSCode 拡張の削除
  • demo フォルダの削除
  • brew uninstall openjdk
  • brew uninstall gradle

参考サイト

2026年5月12日火曜日

LTX2 を M2 Pro Mac mini で動かす

LTX2 を M2 Pro Mac mini で動かす

概要

MLX 版があるのでそれを使って M2 Pro 上で動作させてみます

環境

  • M2 Pro mac mini (16GB)
  • macOS 26.4.1
  • LTX2-MLX c4f8c44
  • Python 3.12.13

インストール

  • git clone --depth=1 https://github.com/Acelogic/LTX-2-MLX.git
  • cd LTX-2-MLX
  • pyenv local 3.12.13
  • uv sync --all-groups

モデルダウンロード

  • uv run scripts/download_weights.py

注意事項

gemma3 はライセンスに同意する必要がある

  • https://huggingface.co/google/gemma-3-12b-it -> Acknowledge license
  • huggingface のアカウントが必要
  • エラーになった場合は一度 rm -rf weights/gemma-3-12b してから再度 uv run scripts/download_weights.py する

distilled は 43GB あるので動かない場合は distilled-fp8 27GB を変わりにダウンロードし使うこと

  • uv run scripts/download_weights.py 実行時に 3 - Custom - Choose individual weights を選択しダウンロードするモデルを個別に選択する

動画生成(通常)

uv run python scripts/generate.py "A realistic cat riding a skateboard on a street, smoothly moving forward, natural motion, full body visible, cinematic lighting, high detail, 4k, camera tracking shot, shallow depth of field" --height 512 --width 512

おそらくこれは M2 Pro mac mini ではエラーになります

動画生成(fp8版)

uv run python scripts/generate.py "A realistic cat riding a skateboard on a street, smoothly moving forward, natural motion, full body visible, cinematic lighting, high detail, 4k, camera tracking shot, shallow depth of field" --height 512 --width 512 --fp8

画像サイズを下げれば何とかこれでもいけるかもしれませんがおそらくこれも不可能です

動画生成(fp8版+低メモリ)

uv run python scripts/generate.py "A realistic cat riding a skateboard on a street" --height 64 --width 96 --fp8 --low-memory --frames 9

おそらくこれでないと M2 Pro mac mini のスペックでは無理かなと思います
画像サイズとプロンプトの長さ、フレーム数も下げています

動画生成(fp8版+低メモリ+fp16フラグ)

uv run python scripts/generate.py "A realistic cat riding a skateboard on a street" --height 64 --width 96 --fp8 --low-memory --frames 9 --fp16 --pipeline one-stage --cfg 5.0

トラブルシューティング

libc++abi: terminating due to uncaught exception of type std::runtime_error: [METAL] Command buffer execution failed: Impacting Interactivity (0000000e:kIOGPUCommandBufferCallbackErrorImpactingInteractivity)

基本的にはメモリ不足が原因です

There appear to be 1 leaked semaphore objects to clean up at shutdown

これもメモリ不足の場合はほとんどです

最後に

LTX2-MLX を M2 Pro mac mini で動かしてみました
何とか動きますが量子化済みのモデルでないと動かないのと生成する動画サイズもかなり小さく短いものでないと無理でした

本当にギリギリなのでもしかしたら環境によっては動かない可能性もあるのでご了承ください

参考サイト