2026年5月29日金曜日

SpringBoot + VSCode 環境で補完してくれないときの対処方法

SpringBoot + VSCode 環境で補完してくれないときの対処方法

概要

Gradle プロジェクトの場合には追加で拡張をインストールする必要があります

環境

  • 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

すでにインストールしている拡張

  • Extension Pack for Java
  • Spring Boot Extension Pack

追加でインストールする拡張

  • Extension Pack for Java Auto Config
  • Gradle Language Support

もしくは settings.json を見直す

{
    "java.jdt.ls.java.home": "/opt/homebrew/Cellar/openjdk/26.0.1/libexec/openjdk.jdk/Contents/Home",
    "java.configuration.runtimes": [
        {
            "name": "JavaSE-26",
            "path": "/opt/homebrew/Cellar/openjdk/26.0.1/libexec/openjdk.jdk/Contents/Home",
            "default": true
        }
    ],
    "java.import.gradle.home": "/opt/homebrew/Cellar/gradle/9.5.1/",
    "java.import.gradle.java.home": "/opt/homebrew/Cellar/openjdk/26.0.1/libexec/openjdk.jdk/Contents/Home"
}

動作確認

VSCodeを再起動するかコマンドパレットで「Clean Java Language Server Workspace」すれば補完ができるようになるはずです

最後に

おそらく Gradle Language Server 拡張は自動でプロジェクトの java/gradle を使ってくれるようです

逆に Language Server for Java は組み込まれている java/gradle を使うのでインストールした gradle のファイルなどが読み込まれていないので補完できていなかったのかなと思います

参考サイト

2026年5月28日木曜日

SpringBoot でユニットテストとコンテナを使ったインテグレーションテストを作成する

SpringBoot でユニットテストとコンテナを使ったインテグレーションテストを作成する

概要

前回 DTO + Service レイヤーを追加しました
アプリがこの辺りまで大きくなったら一度テストを追加することをオススメします

SpringBoot にはコンテナを使って MySQL を起動しそれをインテグレーションテスト用として使えます

今回は MySQL を使わないでモックするユニットテストと MySQL コンテナを起動し実際にデータベースを使ったインテグレーションテストを追加する方法を紹介します

環境

  • 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

コントローラのユニットテストの追加

コントローラでは Service をモックします
Service が期待する値をモックにセットしあとは実際にリクエストを送信しその値が返ってくるか検証します

テスト内では検証用の static method を大量に使うのでそこはアスタリスクでインポートしています

  • vim src/test/java/com/example/demo/MainControllerTest.java
package com.example.demo;

import java.util.List;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;

import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import com.example.demo.dto.UserResponse;
import com.example.demo.service.UserService;

@WebMvcTest(MainController.class)
class MainControllerTest {

	@Autowired
	private MockMvc mockMvc;

	@MockitoBean
	private UserService userService;

	@Test
	void ユーザー作成が成功する() throws Exception {
		String json = """
				{
				    "name": "taro",
				    "email": "taro@example.com"
				}
				""";

		mockMvc.perform(post("/demo/add").contentType("application/json").content(json)).andExpect(status().isOk())
				.andExpect(content().string("Saved"));
	}

	@Test
	void ユーザー一覧取得() throws Exception {
		UserResponse user = new UserResponse();
		user.setName("taro");
		List<UserResponse> mock = List.of(user);

		when(userService.getAllUsers()).thenReturn(mock);

		mockMvc.perform(get("/demo/all")).andExpect(status().isOk()).andExpect(jsonPath("$[0].name").value("taro"));
	}
}

サービスのユニットテストの追加

サービスでは Repository をモックします
これも同様に Repository が返却時に期待する値をモックにセットしサービス内のメソッドをコールすることでテストします

verify はメソッドがコールされたかの振る舞いをテストするためのアサーションです

  • vim src/test/java/com/example/demo/service/UserServiceTest.java
package com.example.demo.service;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

import java.util.List;

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

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

	@Mock
	private UserRepository userRepository;

	@InjectMocks
	private UserService userService;

	@Test
	void ユーザー作成できる() {
		UserRequest req = new UserRequest();
		req.setName("taro");
		req.setEmail("taro@example.com");

		userService.createUser(req);

		verify(userRepository).save(any(User.class));
	}

	@Test
	void findAllメソッドが呼ばれたか() {
		userService.getAllUsers();

		verify(userRepository).findAll();
	}

	@Test
	void ユーザーの一覧が取得できる() {
		User user = new User();
		user.setName("taro");
		user.setEmail("taro@example.com");

		when(userRepository.findAll()).thenReturn(List.of(user));

		var result = userService.getAllUsers();

		assertEquals(1, result.size());
		assertEquals("taro", result.get(0).getName());
	}

	@Test
	void メールが重複している場合はエラー() {
		UserRequest req = new UserRequest();
		req.setName("taro");
		req.setEmail("taro@example.com");

		// モックの振る舞いを定義
		when(userRepository.existsByEmail("taro@example.com")).thenReturn(true);

		// 例外が投げられることを検証
		IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> userService.createUser(req));

		assertEquals("既に登録されています", ex.getMessage());

		// saveが呼ばれていないことも重要
		verify(userRepository, never()).save(any());
	}
}

リポジトリのインテグレーションテストの追加

実際に MySQL コンテナに接続しテストします
MySQL を起動するのは org.testcontainers.mysql.MySQLContainer を使います

接続先情報やデータベースの作成はテスト時に自動で行ってくれます
コンテナを起動したりマイグレーションするので少しテストに時間がかかります

  • vim src/test/java/com/example/demo/repository/UserRepositoryIntegrationTest.java
package com.example.demo.repository;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import static org.assertj.core.api.Assertions.assertThat;

import com.example.demo.entity.User;

@Import(TestcontainersConfiguration.class)
@SpringBootTest
class UserRepositoryIntegrationTest {

	@Autowired
	private UserRepository userRepository;

	@Test
	void DBに保存できる() {
		User user = new User();
		user.setName("taro");
		user.setEmail("taro@example.com");

		userRepository.save(user);

		Iterable<User> users = userRepository.findAll();
		assertThat(users).hasSize(1);
	}
}

MySQL コンテナを起動するテストの設定は以下で行います

  • vim src/test/java/com/example/demo/repository/TestcontainersConfiguration.java
package com.example.demo.repository;

import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.context.annotation.Bean;
import org.testcontainers.mysql.MySQLContainer;
import org.testcontainers.utility.DockerImageName;

@TestConfiguration(proxyBeanMethods = false)
class TestcontainersConfiguration {

	@Bean
	@ServiceConnection
	MySQLContainer mysqlContainer() {
		return new MySQLContainer(DockerImageName.parse("mysql:latest"));
	}

}

動作確認

  • JASYPT_PASSWORD="xxx" ./gradlew test

JASYPT_PASSWORD は jasypt で application.properties を暗号化している場合に指定してください
テストが完了すれば OK です
2回目以降はキャッシュを参照してしまいテストが速攻で終了してしまうのでその場合は

  • JASYPT_PASSWORD="xxx" ./gradlew cleanTest test

最後に

SpringBoot でユニットテストとインテグレーションテストを作成する方法を紹介しました
ある程度アプリが大きくなる前に全体を通してテストするケースは作成しておきましょう

値の検証や境界値などテストパターンが不十分なのでその辺りはいろいろ追加してください

また異常系のテストも不足しているので必要に応じて追加してください

2026年5月27日水曜日

SpringBoot でレスポンスに DTO を使うサンプルコード

SpringBoot でレスポンスに DTO を使うサンプルコード

概要

前回リクエスト情報を DTO に変換して安全にデータベースの情報を取得する方法を紹介しました

今回はレスポンスでも DTO を導入してみます

環境

  • 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

レスポンス用 DTO の作成

レスポンス用の DTO には返却したい情報だけを定義します

今回はテストなので name だけ返却するようにします

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

import jakarta.validation.constraints.NotBlank;

public class UserResponse {

	@NotBlank
	private String name;

	public String getName() {
		return name;
	}

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

}

UserService の修正

getAllUsers を先ほど作成した UserResponse が帰るように修正します

stream を使ってクロージャっぽく変換しています

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

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

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

import com.example.demo.dto.UserRequest;
import com.example.demo.dto.UserResponse;
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 List<UserResponse> getAllUsers() {
		List<User> users = new ArrayList<>();
		userRepository.findAll().forEach(users::add);

		return users.stream().map(this::toResponse).toList();
	}

	private UserResponse toResponse(User user) {
		UserResponse response = new UserResponse();
		response.setName(user.getName());
		return response;
	}
}

コントローラの修正

コントローラからは Service レイヤーのみを扱うように修正します
これでコントローラが直接 Repository を扱うことはなくなりました

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

import java.util.List;

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.dto.UserResponse;
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 List<UserResponse> getAllUsers() {
		return userService.getAllUsers();
	}
}

動作確認

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

curl で GET し UserResponse に定義されている値だけ返ってくることを確認しましょう

  • curl -XGET http://localhost:8080/demo/all
[{"name":"First"}]

最後に

SpringBoot でレスポンスに DTO を導入してみました
レスポンスに DTO を導入するメリットとしては以下のようなものがあります

  • 予期せぬデータベース情報の返却を防ぐ
  • データベースの情報を加工してレスポンスを生成する

基本的にはレスポンスにも DTO を含めるのがベストプラクティスにはなります

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 などを使いましょう