JavaのBuilder pattern(Effective Java’s)は、Builder pattern(one of the Gang of Four design patterns)とは別物らしい

f:id:ts0818:20211205174128j:plain

support.apple.com

iOS 15 では、「テキスト認識表示」を使って、写真の中のテキストをコピーして共有したり、翻訳したり、そのまま電話をかけるなど、さまざまなことができます。また、「画像を調べる」を使えば、写真の被写体について簡単に調べることができます。

https://support.apple.com/ja-jp/HT212630

Appleの技術力が半端ない...

標準の機能で「光学文字認識OCR:Optical Character Recognition/Reader)」とかできてしまうって、どんだけ~!

そして、Twitterで見かけた「ロボット」の映像が衝撃的...

⇧ なんか、こういう映像を見せられてしまうと、

ledge.ai

⇧ 著名人とかが、「危機感」を抱くべきと提唱していた点も納得できてしまう気がしますね。

毎度のこと、冒頭から脱線しましたが、今回は、Javaについての話です。

「Write once, run anywhere」の謳い文句で「OS(Operation Systems)」に依存することなく動くことが、Javaの魅力という事ですが、開発環境には依存してしまうことを痛感する今日この頃です。

レッツトライ~。

 

Builder pattern(one of the Gang of Four design patterns)とは?

まずは、Javaに限らない、一般的な「Builder pattern 」とは?

The builder pattern is a design pattern designed to provide a flexible solution to various object creation problems in object-oriented programming. The intent of the Builder design pattern is to separate the construction of a complex object from its representation. It is one of the Gang of Four design patterns.

https://en.wikipedia.org/wiki/Builder_pattern

⇧「one of the Gang of Four design patterns」とあるように、「Gang of Four design patterns」の中の1つということで、そも「Gang of Four design patterns」って?

Design Patterns: Elements of Reusable Object-Oriented Software (1994) is a software engineering book describing software design patterns. The book was written by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, with a foreword by Grady Booch. The book is divided into two parts, with the first two chapters exploring the capabilities and pitfalls of object-oriented programming, and the remaining chapters describing 23 classic software design patterns. The book includes examples in C++ and Smalltalk.

https://en.wikipedia.org/wiki/Design_Patterns

It has been influential to the field of software engineering and is regarded as an important source for object-oriented design theory and practice. More than 500,000 copies have been sold in English and in 13 other languages. The authors are often referred to as the Gang of Four (GoF).

https://en.wikipedia.org/wiki/Design_Patterns

⇧ 「先人の知恵」という感じで、「ソフトウェア開発」におけるノウハウっていうことらしいですが、「オブジェクト指向プログラミング(OOP:Object Oriented Programing)」における23個の有益な「デザインパターン」ということですと。

で、そのうちの1つが「Builder pattern」ということで、

f:id:ts0818:20211204142401p:plain

Builder
Abstract interface for creating objects (product).
ConcreteBuilder
Provides implementation for Builder. It is an object able to construct other objects. Constructs and assembles parts to build the objects.

https://en.wikipedia.org/wiki/Builder_pattern

⇧ という感じで、「インスタンス」の生成の処理を他のクラスに委譲してるってことだとは思うんだけど、「Factory Method pattern」との違いって?って思ったんだけど、同様の疑問を抱いた方がおられて、「stackoverflow」で質問が上がってました。

stackoverflow.com

What is the difference between the Builder design pattern and the Factory design pattern?

https://stackoverflow.com/questions/757743/what-is-the-difference-between-builder-design-pattern-and-factory-design-pattern

⇧ ってな「問いかけ」に対して、いろいろ回答があり、「Abstract factory pattern」とかも比較対象に上がってきたりで、なかなかに使い分けが難しい感がありますな...

  • Constructing a complex object step by step : builder pattern

  • A simple object is created by using a single method : factory method pattern

  • Creating Object by using multiple factory method : Abstract factory pattern

https://stackoverflow.com/questions/757743/what-is-the-difference-between-builder-design-pattern-and-factory-design-pattern

さらに、

www.macky-studio.com

これの明確な答えはよく分からないが、「継承か委譲か問題」と関係が深いように思う。
Template Methodだとインスタンス生成はスーパークラスが担い(継承)、BuilderパターンだとDirectorが担う(委譲)。

BuilderパターンとFactory Method・Template Methodパターンの違いは? - yynsmk's tech blog

どちらでも良さそうだが、委譲の方がクラス同士が疎結合で抽象度が高く良いらしいし、GoFデザインパターンでも継承よりも委譲が推奨されている。
なので、とりあえずBuilderパターンは委譲を使うものなんだと覚えておこう。

BuilderパターンとFactory Method・Template Methodパターンの違いは? - yynsmk's tech blog

⇧ 上記サイト様によりますと「Template Method pattern」とも似てるという話も。

ちなみに、Wikipediaさんの「Design Patterns」の説明によると、「Gang of Four (GoF)」の23個の「design pattern」のことについてだとは思うけど、大まかに、

  1. Creational
    Creational patterns are ones that create objects, rather than having to instantiate objects directly. This gives the program more flexibility in deciding which objects need to be created for a given case.
  2. Structural
    These concern class and object composition. They use inheritance to compose interfaces and define ways to compose objects to obtain new functionality.
  3. Behavioral
    Most of these design patterns are specifically concerned with communication between objects.

の3つのTypeに分類できるらしく、

No. Type pattern name description
1 1 Abstract factory groups object factories that have a common theme.
2 1 Builder constructs complex objects by separating construction and representation.
3 1 Factory method creates objects without specifying the exact class to create.
4 1 Prototype creates objects by cloning an existing object.
5 1 Singleton restricts object creation for a class to only one instance.
6 2 Adapter allows classes with incompatible interfaces to work together by wrapping its own interface around that of an already existing class.
7 2 Bridge decouples an abstraction from its implementation so that the two can vary independently.
8 2 Composite composes zero-or-more similar objects so that they can be manipulated as one object.
9 2 Decorator dynamically adds/overrides behaviour in an existing method of an object.
10 2 Facade provides a simplified interface to a large body of code.
11 2 Flyweight reduces the cost of creating and manipulating a large number of similar objects.
12 2 Proxy provides a placeholder for another object to control access, reduce cost, and reduce complexity.
13 3 Chain of responsibility delegates commands to a chain of processing objects.
14 3 Command creates objects that encapsulate actions and parameters.
15 3 Interpreter implements a specialized language.
16 3 Iterator accesses the elements of an object sequentially without exposing its underlying representation.
17 3 Mediator allows loose coupling between classes by being the only class that has detailed knowledge of their methods.
18 3 Memento provides the ability to restore an object to its previous state (undo).
19 3 Observer is a publish/subscribe pattern, which allows a number of observer objects to see an event.
20 3 State allows an object to alter its behavior when its internal state changes.
21 3 Strategy allows one of a family of algorithms to be selected on-the-fly at runtime.
22 3 Template method defines the skeleton of an algorithm as an abstract class, allowing its subclasses to provide concrete behavior.
23 3 Visitor separates an algorithm from an object structure by moving the hierarchy of methods into one object.

⇧ という感じらしいけど、「pattern name」については、結構、ネットの情報を見た感じでも、統一感が無いので、認識合わせしたほうが良いのかもしれないのですが、参照元はハッキリしといたほうが良さ気ですかね、例えば、『「Wikipedia」の「Design pattern」を参照した限りでは』みたいな感じで。

 

Builder pattern(Effective Java’s)

で、Javaだと「Effective Java」という書籍の「Builder pattern」が有名らしく、

javadevcentral.com

In the last post, I wrote about the Gang of Four (GoF) Builder Design Pattern. In this post, we will look at the builder pattern from the Effective Java by Joshua Bloch. The builder pattern is also called Joshua Bloch’s (Effective Java’s) builder pattern. It is commonly confused with the Gang of Four Builder Design pattern. 
The Effective Java’s Builder pattern provides a nice and a safe way to build or construct an object that has a lot of optional parameters or instance variables.

https://javadevcentral.com/effective-java-builder-pattern

⇧ という感じで、「Gang of FourGoF)」の「design pattern」の「Builder pattern」とは異なるらしいですと、ややこしい話にしてくれますな...。

 

LombokのbuilderはどっちのBuilder patternを実現してる?

で、さらにややこしいことに、Javaには「Lombok」という「ライブラリ」が存在(Javaの標準APIではない)するのですが、その前に「Lombok」とは?

objectcomputing.com

"Boilerplate" is a term used to describe code that is repeated in many parts of an application with little alteration. One of the most frequently voiced criticisms of the Java language is the volume of this type of code that is found in most projects. This problem is frequently a result of design decisions in various libraries, but it's exacerbated by limitations in the language itself. Project Lombok aims to reduce the prevalence of some of the worst offenders by replacing them with a simple set of annotations.

https://objectcomputing.com/resources/publications/sett/january-2010-reducing-boilerplate-code-with-project-lombok

⇧ とあるように「Boilerplate」をコーディングする手間を省きますよ、そのために「anotation」を付与すればOKということらしい。

「Boilerplate」とは?

Boilerplate code

In computer programming, boilerplate is the sections of code that have to be included in many places with little or no alteration. Such boilerplate code is particularly salient when the programmer must include a lot of code for minimal functionality.

https://en.wikipedia.org/wiki/Boilerplate_text

⇧「ほとんど変わらないけれど、無くてはならないコード」ということで、Javaだと、「getter」「setter」とかが「Boilerplate」の良い例かと。

で、そういった「Boilerplate」をプログラマー自身がコーディングすることなく、コーディングした時と同じように機能だけ利用できるようにしてくれるのが「Lombok」ということらしい。

「Object Computing, Inc.」って企業で「Lombok」について紹介されてたので、

github.com

Githubに「Lombok」の「プロジェクト」の情報があるのかと思ったのだけれど、何か別に管理してる模様。

github.com

Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java. Never write another getter or equals method again, with one annotation your class has a fully featured builder, automate your logging variables, and much more.

https://github.com/projectlombok/lombok

Java用の「ライブラリ」ということらしい。

まぁ、脱線しまくりで申し訳ないのですが、この「Lombok」が用意してくれてる「annotation」を利用すると、「Boilerplate」をコーディングしなくても良くなるということのようなのですが、「annotation」の一覧としては、

projectlombok.org

The Lombok javadoc is available, but we advise these pages.

https://projectlombok.org/features/all

⇧ とあって、上記のページによると、2021年12月4日(土)時点では、

No. anotation description
1 @NonNull or: How I learned to stop worrying and love the NullPointerException.
2 @Cleanup Automatic resource management: Call your close() methods safely with no hassle.
3 @Getter public int getFoo() {return foo;} again.
4 @Setter Never write
5 @ToString No need to start a debugger to see your fields: Just let lombok generate a toString for you!
6 @EqualsAndHashCode Equality made easy: Generates hashCode and equals implementations from the fields of your object..
7 @NoArgsConstructor Constructors made to order: Generates constructors that take no arguments
8 @RequiredArgsConstructor Constructors made to order: Generates constructors that take one argument per final / non-nullfield
9 @AllArgsConstructor Constructors made to order: Generates constructors that take one argument for every field.
10 @Data All together now: A shortcut for @ToString, @EqualsAndHashCode, @Getter on all fields, and @Setter on all non-final fields, and @RequiredArgsConstructor!
11 @Value Immutable classes made very easy.
12 @Builder ... and Bob's your uncle: No-hassle fancy-pants APIs for object creation!
13 @SneakyThrows To boldly throw checked exceptions where no one has thrown them before!
14 @Synchronized synchronized done right: Don't expose your locks.
15 @With Immutable 'setters' - methods that create a clone but with one changed field.
16 @Getter(lazy=true) Laziness is a virtue!
17 @Log Captain's Log, stardate 24435.7: "What was that line again?"

となってるんだけど、このページで紹介してる「annotation」は全量ってわけではないらしく、APIドキュメントのほうを見ると、他にも「annotation」が存在する模様...う~ん、何だろうね、よく使うものを並べてみました、なのであれば、一言言及しておいて欲しい気がしますな...

そもそも「パッケージ」が、

No. Package Description
1 lombok This package contains all the annotations and support classes you need as a user of lombok.
2 lombok.experimental This package contains the annotations and support classes you need as a user of lombok, for all features which aren't (yet) supported as a first class feature.
3 lombok.extern.apachecommons  
4 lombok.extern.flogger  
5 lombok.extern.java  
6 lombok.extern.jbosslog  
7 lombok.extern.log4j  
8 lombok.extern.slf4j  

8個あるというね...。

まぁ、それはさておき、「Lombok」には、

  • @Builder
  • @SuperBuilder

っていう、名前から察するに「Builder pattern」に関係してるんじゃないかと推測される「annotation」が存在するんだけども、実際のところ、「Builder pattern」に関係してるのか?

残念ながら、公式のドキュメントとかでは「Builder pattern」との関係についての言及は見られなかったのですが、

www.baeldung.com

The Lombok library provides a great way to implement the Builder Pattern without writing any boilerplate code: the @Builder annotation.

https://www.baeldung.com/lombok-builder-inheritance

⇧ 上記サイト様によりますと、「Builder pattern」を実現できる方法の1つとして「Lombok」の「annotation」の「@Builder」があるよ、と言っておりますと。

コーディング例を見た感じでは、「Effective Java」で紹介されてる「Builder pattern」を利用してるっぽい。

 

nested objectsの場合のBuilder pattern(Effective Java's)を試してみる

インストールしていた、PostgreSQL 14にログインして、「データベース」「スキーマ」「テーブル」を作成しておきます。

f:id:ts0818:20211205130200p:plain

⇧「from」は「予約語」だったので、「カラム名」には使えないのでした...

www.postgresql.jp

データも入れておきます。

INSERT INTO enum_test.person (id, gender, blood_type, birthday, birthplace, first_name, last_name, height, weight, created_time) VALUES
(1, '0', '0', '1921-01-01', '日本',  '正造', '佐藤', 158.3, 52.1, current_timestamp),
(2, '0', '1', '1931-01-01', 'クルンテープ・プラマハーナコーン・アモーンラッタナコーシン・マヒンタラーユッタヤー・ マハーディロックホップ・ノッパラット・ラーチャタニーブリローム・ラドムウーチャンウェートマハーサターン・ アモーンピマーン・アワターンサティット・サッカタッティヤウィサヌカムプラシット',  'トンチャイ', 'メーキンタイ', 164.5, 56.3, current_timestamp),
(3, '1', '2', '1941-01-01', 'アメリカ合衆国',  'ヨナハン', 'スティーブ', 167.2, 48.7, current_timestamp),
(4, '1', '3', '1951-01-01', 'スペイン',  'カルメン', 'アギレラ', 161.1, 45.1, current_timestamp);    

f:id:ts0818:20211205133031p:plain

「A5:SQL Mk-2」というツールで、「テーブル」を確認してみると、

f:id:ts0818:20211205134453j:plain

データが入ってるのが確認できました。

f:id:ts0818:20211205133701p:plain

もう少しデータを追加しときます。

INSERT INTO enum_test.person (id, gender, blood_type, birthday, birthplace, first_name, last_name, height, weight, created_time) VALUES
(5, '0', '0', '1921-01-01', '日本',  '一郎', '鈴木', 178.3, 72.1, current_timestamp),
(6, '0', '1', '1931-01-01', 'クルンテープ・プラマハーナコーン・アモーンラッタナコーシン・マヒンタラーユッタヤー・ マハーディロックホップ・ノッパラット・ラーチャタニーブリローム・ラドムウーチャンウェートマハーサターン・ アモーンピマーン・アワターンサティット・サッカタッティヤウィサヌカムプラシット',  'ヤム', 'メーキンタイ', 174.5, 64.3, current_timestamp),
(7, '1', '2', '1941-01-01', 'アメリカ合衆国',  'マリア', 'ダニエル', 159.2, 45.7, current_timestamp),
(8, '1', '3', '1951-01-01', 'スペイン',  'ミシェル', 'ホセ', 151.1, 45.1, current_timestamp);

で、Eclipseで「Spring Boot」のプロジェクト(「ビルドツール」には「Gradle」を選択してます)を作成します。

今回、作成・修正したファイルは以下のような感じになりました。

f:id:ts0818:20211205140358p:plain

■/nest-builder/src/main/resources/application.properties

# PostgreSQLを利用するための設定
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5434/mydb
spring.datasource.username=postgres
spring.datasource.password=postgres

■/nest-builder/build.gradle

plugins {
	id 'org.springframework.boot' version '2.6.1'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
}

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

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jpa
	implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: '2.6.1'
	// https://mvnrepository.com/artifact/org.postgresql/postgresql
	implementation group: 'org.postgresql', name: 'postgresql', version: '42.3.1'
	// https://mvnrepository.com/artifact/com.mchange/c3p0
	implementation group: 'com.mchange', name: 'c3p0', version: '0.9.5.5'
}

test {
	useJUnitPlatform()
}

■/nest-builder/src/main/java/com/example/demo/code/GenderEnum.java

package com.example.demo.code;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum GenderEnum {
	MALE("0", "男性"),
	FEMALE("1", "女性");

	private String code;
	private String value;

}

■/nest-builder/src/main/java/com/example/demo/code/converter/GenderEnumConverter.java

package com.example.demo.code.converter;

import java.util.Objects;
import java.util.stream.Stream;

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

import com.example.demo.code.GenderEnum;
@Converter(autoApply = true)
public class GenderEnumConverter implements AttributeConverter<GenderEnum, String> {

	@Override
	public String convertToDatabaseColumn(GenderEnum genderEnum) {
		//
		if (Objects.isNull(genderEnum)) {
			return null;
		}
		return genderEnum.getCode();
	}

	@Override
	public GenderEnum convertToEntityAttribute(String dbData) {
		//
		if (Objects.isNull(dbData)) {
			return null;
		}
		return Stream.of(GenderEnum.values())
				.filter(genderEnum -> genderEnum.getCode().equals(dbData))
				.findFirst()
				.orElseThrow(IllegalArgumentException::new);
	}
}

■/nest-builder/src/main/java/com/example/demo/code/BloodTypeEnum.java

package com.example.demo.code;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum BloodTypeEnum {

	A("0", "A型"),
	B("1", "B型"),
	AB("2", "AB型"),
	O("3", "O型");

	private String code;
	private String value;
}

■/nest-builder/src/main/java/com/example/demo/code/converter/BloodTypeEnumConverter.java

package com.example.demo.code.converter;

import java.util.Objects;
import java.util.stream.Stream;

import javax.persistence.AttributeConverter;

import com.example.demo.code.BloodTypeEnum;

public class BloodTypeEnumConverter implements AttributeConverter<BloodTypeEnum, String> {

	@Override
	public String convertToDatabaseColumn(BloodTypeEnum bloodTypeEnum) {
		//
		if (Objects.isNull(bloodTypeEnum)) {
			return null;
		}
		return bloodTypeEnum.getCode();
	}

	@Override
	public BloodTypeEnum convertToEntityAttribute(String dbData) {
		//
		if (Objects.isNull(dbData)) {
			return null;
		}
		return Stream.of(BloodTypeEnum.values())
				.filter(bloodTypeEnum -> bloodTypeEnum.getCode().equals(dbData))
				.findFirst()
				.orElseThrow(IllegalArgumentException::new);
	}
}

■/nest-builder/src/main/java/com/example/demo/dto/BaseDto.java

package com.example.demo.dto;

import java.time.LocalDate;

import com.example.demo.code.BloodTypeEnum;
import com.example.demo.code.GenderEnum;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;

@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class BaseDto {

	private Long id;
	private GenderEnum genderEnum;
	private BloodTypeEnum bloodTypeEnum;
	private int age;
	private LocalDate birthDay;
}

■/nest-builder/src/main/java/com/example/demo/dto/SeptuagenarianDto.java

package com.example.demo.dto;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.SuperBuilder;

/**
 * 70歳代
 */
@Data
@SuperBuilder
@EqualsAndHashCode(callSuper=false)
public class SeptuagenarianDto extends BaseDto {

	private String septuaWord;
}

■/nest-builder/src/main/java/com/example/demo/dto/OctogenarianDto.java

package com.example.demo.dto;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.SuperBuilder;

/**
 * 80歳代
 */
@Data
@SuperBuilder
@EqualsAndHashCode(callSuper=false)
public class OctogenarianDto extends BaseDto {
	private String octoWord;
}

■/nest-builder/src/main/java/com/example/demo/dto/NonagenarianDto.java

package com.example.demo.dto;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.SuperBuilder;

/**
 * 90歳代
 */
@Data
@SuperBuilder
@EqualsAndHashCode(callSuper=false)
public class NonagenarianDto extends BaseDto {
	private String nonaWord;
}

■/nest-builder/src/main/java/com/example/demo/dto/CentenarianDto.java

package com.example.demo.dto;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.SuperBuilder;

/**
 * 100歳以上
 */
@Data
@SuperBuilder
@EqualsAndHashCode(callSuper=false)
public class CentenarianDto extends BaseDto {
	private String centenarianWord;
}

■/nest-builder/src/main/java/com/example/demo/dto/SeniorCitizenDto.java

package com.example.demo.dto;

import lombok.Builder;
import lombok.Data;

/**
 * 高齢者
 */
@Data
@Builder
public class SeniorCitizenDto {

	private SeptuagenarianDto septuagenarianDto;

	private OctogenarianDto octogenarianDto;

	private NonagenarianDto nonagenarianDto;

	private CentenarianDto centenarianDto;
}

■/nest-builder/src/main/java/com/example/demo/entity/BaseEntity.java

package com.example.demo.entity;

import java.time.OffsetDateTime;

import lombok.Data;

@Data
public class BaseEntity {

	private OffsetDateTime createdTime = OffsetDateTime.now();
	private OffsetDateTime updatedTime;
}

■/nest-builder/src/main/java/com/example/demo/entity/PersonEntity.java

package com.example.demo.entity;

import java.time.LocalDate;

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

import com.example.demo.code.BloodTypeEnum;
import com.example.demo.code.GenderEnum;

import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@Entity
@Table(name="person", schema="enum_test")
@EqualsAndHashCode(callSuper=false)
public class PersonEntity extends BaseEntity {

	@Id
	@Column(name="id")
	private Long id;
	@Column(name="gender")
	private GenderEnum genderEnum;
	@Column(name="blood_type")
	private BloodTypeEnum bloodTypeEnum;
	@Column(name="birthday")
	private LocalDate birthDay;
	@Column(name="birthplace")
	private String birthPlace;
	@Column(name="first_name")
	private String firstName;
	@Column(name="last_name")
	private String lastName;
	@Column(name="height")
	private float height;
	@Column(name="weight")
	private float weight;
	@Column(name="img_url")
	private String imgUrl;

}

■/nest-builder/src/main/java/com/example/demo/repository/PersonRepository.java

package com.example.demo.repository;

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

import com.example.demo.entity.PersonEntity;

@Repository
public interface PersonRepository extends JpaRepository<PersonEntity, Long> {

}

■/nest-builder/src/main/java/com/example/demo/service/PersonService.java

package com.example.demo.service;

import java.util.List;

import org.springframework.stereotype.Service;

import com.example.demo.dto.SeniorCitizenDto;
@Service
public interface PersonService {
	public List<SeniorCitizenDto> createSeniorCitizenDtoList();
}

■/nest-builder/src/main/java/com/example/demo/service/impl/PersonServiceImpl.java

package com.example.demo.service.impl;

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

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

import com.example.demo.dto.CentenarianDto;
import com.example.demo.dto.NonagenarianDto;
import com.example.demo.dto.OctogenarianDto;
import com.example.demo.dto.SeniorCitizenDto;
import com.example.demo.dto.SeptuagenarianDto;
import com.example.demo.entity.PersonEntity;
import com.example.demo.repository.PersonRepository;
import com.example.demo.service.PersonService;

@Service
public class PersonServiceImpl implements PersonService {

	@Autowired
	private PersonRepository personRepository;

	public List<PersonEntity> getAllPesronEntity() {
		return personRepository.findAll();
	}

	public List<SeniorCitizenDto> createSeniorCitizenDtoList() {
		List<PersonEntity> personEntityList = this.getAllPesronEntity();
		Map<Long, List<PersonEntity>> groupGenerationsMap = personEntityList
				.stream()
				.collect(Collectors.groupingBy(personEntity -> (Long
						.valueOf(ChronoUnit.YEARS.between(personEntity.getBirthDay(), LocalDate.now()) / 10) * 10)));

		// Set<Long> keySet = groupGenerationsMap.keySet();
		int maxValueInMap = groupGenerationsMap.values().stream().mapToInt(List::size).max().getAsInt();
		CentenarianDto centenarianDto = null; // 100歳オーバー
		NonagenarianDto nonagenarianDto = null; // 90歳代
		OctogenarianDto octogenarianDto = null; // 80歳代
		SeptuagenarianDto septuagenarianDto = null; // 70歳代

		List<SeniorCitizenDto> seniorCitizenDtoList = new ArrayList<>();

		for (int index = 0; index < maxValueInMap; index++) {
			if (!groupGenerationsMap.get(100L).isEmpty() || Objects.nonNull(groupGenerationsMap.get(100L).get(index))
					|| Objects.nonNull(groupGenerationsMap.get(100L).get(index).getBirthDay())
					|| ChronoUnit.YEARS
							.between(groupGenerationsMap.get(100L).get(index).getBirthDay(), LocalDate.now()) >= 100) {
				centenarianDto = CentenarianDto.builder()
						.id(groupGenerationsMap.get(100L).get(index).getId())
						.birthDay(groupGenerationsMap.get(100L).get(index).getBirthDay())
						.age(Math.toIntExact(Long.valueOf(
								ChronoUnit.YEARS.between(groupGenerationsMap.get(100L).get(index).getBirthDay(),
										LocalDate.now()))))
						.genderEnum(groupGenerationsMap.get(100L).get(index).getGenderEnum())
						.bloodTypeEnum(groupGenerationsMap.get(100L).get(index).getBloodTypeEnum())
						.centenarianWord("百(ひゃく)にして")
						.build();
			}
			if (!groupGenerationsMap.get(90L).isEmpty() || Objects.nonNull(groupGenerationsMap.get(90L).get(index))
					|| Objects.nonNull(groupGenerationsMap.get(90L).get(index).getBirthDay())
					|| ChronoUnit.YEARS
							.between(groupGenerationsMap.get(90L).get(index).getBirthDay(), LocalDate.now()) >= 90) {
				nonagenarianDto = NonagenarianDto.builder()
						.id(groupGenerationsMap.get(90L).get(index).getId())
						.birthDay(groupGenerationsMap.get(90L).get(index).getBirthDay())
						.age(Math.toIntExact(Long.valueOf(
								ChronoUnit.YEARS.between(groupGenerationsMap.get(90L).get(index).getBirthDay(),
										LocalDate.now()))))
						.genderEnum(groupGenerationsMap.get(90L).get(index).getGenderEnum())
						.bloodTypeEnum(groupGenerationsMap.get(90L).get(index).getBloodTypeEnum())
						.nonaWord("九十(きゅうじゅう)にして")
						.build();
			}
			if (!groupGenerationsMap.get(80L).isEmpty() || Objects.nonNull(groupGenerationsMap.get(80L).get(index))
					|| Objects.nonNull(groupGenerationsMap.get(80L).get(index).getBirthDay())
					|| ChronoUnit.YEARS
							.between(groupGenerationsMap.get(80L).get(index).getBirthDay(), LocalDate.now()) >= 80) {
				octogenarianDto = OctogenarianDto.builder()
						.id(groupGenerationsMap.get(80L).get(index).getId())
						.birthDay(groupGenerationsMap.get(80L).get(index).getBirthDay())
						.age(Math.toIntExact(Long.valueOf(
								ChronoUnit.YEARS.between(groupGenerationsMap.get(80L).get(index).getBirthDay(),
										LocalDate.now()))))
						.genderEnum(groupGenerationsMap.get(80L).get(index).getGenderEnum())
						.bloodTypeEnum(groupGenerationsMap.get(80L).get(index).getBloodTypeEnum())
						.octoWord("八十(はちじゅう)にして")
						.build();
			}
			if (!groupGenerationsMap.get(70L).isEmpty() || Objects.nonNull(groupGenerationsMap.get(70L).get(index))
					|| Objects.nonNull(groupGenerationsMap.get(70L).get(index).getBirthDay())
					|| ChronoUnit.YEARS
							.between(groupGenerationsMap.get(70L).get(index).getBirthDay(), LocalDate.now()) >= 70) {
				septuagenarianDto = SeptuagenarianDto.builder()
						.id(groupGenerationsMap.get(70L).get(index).getId())
						.birthDay(groupGenerationsMap.get(70L).get(index).getBirthDay())
						.age(Math.toIntExact(Long.valueOf(
								ChronoUnit.YEARS.between(groupGenerationsMap.get(70L).get(index).getBirthDay(),
										LocalDate.now()))))
						.genderEnum(groupGenerationsMap.get(70L).get(index).getGenderEnum())
						.bloodTypeEnum(groupGenerationsMap.get(70L).get(index).getBloodTypeEnum())
						.septuaWord("七十(しちじゅう)にして心(こころ)の欲っする所(ところ)に従がえども、矩(のり)を踰えず")
						.build();
			}
			seniorCitizenDtoList.add(SeniorCitizenDto.builder()
					.centenarianDto(centenarianDto)
					.nonagenarianDto(nonagenarianDto)
					.octogenarianDto(octogenarianDto)
					.septuagenarianDto(septuagenarianDto)
					.build());
		}
		return seniorCitizenDtoList;
	}
}

■/nest-builder/src/main/java/com/example/demo/controller/PersonController.java

package com.example.demo.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo.dto.SeniorCitizenDto;
import com.example.demo.service.PersonService;

@RestController
@RequestMapping("/")
public class PersonController {

	@Autowired
	private PersonService personService;

	@GetMapping
	public String hello() {
		return "Hello";
	}

	@RequestMapping("/personData")
	public List<SeniorCitizenDto> getPerson() {
		return personService.createSeniorCitizenDtoList();
	}
}

■/nest-builder/src/main/java/com/example/demo/NestBuilderApplication.java

package com.example.demo;

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

@SpringBootApplication
public class NestBuilderApplication {

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

⇧ ってな感じで、編集・保存して、「Spring Boot App」で実行。

f:id:ts0818:20211205142059p:plain

ブラウザから「http://localhost:8080/」にアクセス。

f:id:ts0818:20211205142244p:plain

ブラウザから「http://localhost:8080/personData」にアクセス。

f:id:ts0818:20211205181245p:plain

という感じで、一応、動くことは動いたけども、JavaのコーディングがStream APIとかでもっとスマートに書けたら良かったんですが、やり方が分からんかったです...

2021年12月8日(水)追記:↓ ここから

なんか、「AttributeCoverter」って「JPAJava Persistence API)」の「Native Query」で使うとエラーになってしまうらしい...

stackoverflow.com

そう考えると、普通に

  • @Enumerated(EnumType.ORDINAL)
  • @Enumerated(EnumType.String)

を使うのが良いってことですかね。いやはや、「AttributeCoverter」の存在意義が微妙ですな...

2021年12月8日(水)追記:↑ ここまで

ちなみに、クラスのフィールドに他のクラスが含まれてて、どちらのクラスもbuilderできるのであれば、以下のような感じで、builderの入れ子でコーディングすることもできるかと、値を設定してないのでエラーになるけど。

SeniorCitizenDto.builder()
					.centenarianDto(CentenarianDto.builder()
						.id()
						.birthDay()
						.age()
						.genderEnum()
						.bloodTypeEnum()
						.septuaWord()
						.build()
					)
					.nonagenarianDto(NonagenarianDto.builder()
						.id()
						.birthDay()
						.age()
						.genderEnum()
						.bloodTypeEnum()
						.septuaWord()
						.build()
					)
					.octogenarianDto(OctogenarianDto.builder()
						.id()
						.birthDay()
						.age()
						.genderEnum()
						.bloodTypeEnum()
						.septuaWord()
						.build()
					)
					.septuagenarianDto(SeptuagenarianDto.builder()
						.id()
						.birthDay()
						.age()
						.genderEnum()
						.bloodTypeEnum()
						.septuaWord()
						.build()
					)
					.build();

毎回モヤモヤ感が半端ない...

ちなみに、「タイ」の国名が世界一長いってネット情報があったんですが、

www.thailandtravel.or.jp

バンコクタイ語名:クルンテープ・マハーナコーン※/英語名:Bangkok
※正式名称:クルンテープ・マハーナコーン・アモーンラッタナコーシン・マヒンタラーユッタヤー・マハーディロック・ポップ・ノッパラット・ラーチャタニーブリーロム・ウドムラーチャニウェートマハーサターン・アモーンピマーン・アワターンサティット・サッカタッティヤウィサヌカムプラシット

タイ王国の概要 | 【公式】タイ国政府観光庁

⇧ 正しくは、「タイ」の「首都」名が世界一長いってことのようでした。

じゃあ、国名が世界一長いのは?

ddnavi.com

 ユナイテッドキングダムオブグレートブリテンアンドノーザンアイルランド。「ユナイテッドキングダム」や「グレートブリテン」と聞くとピンとくる方も多いと思いますが、イギリスの正式名称です。社会の授業で「グレートブリテンおよび北部アイルランド連合王国」と覚えた人も多いのではないでしょうか。

なんと92文字! 世界一長い地名とは? 長~い地名にまつわるあれこれ/毎日雑学 | ダ・ヴィンチニュース

⇧ 国名が世界一長いのは「イギリス」の正式名称らしいです。

今回はこのへんで。