Slide 1

Slide 1 text

ܾࡁαʔϏεͷSpring Bootͷ όʔδϣϯΛ2ܥʹ্͛ͨ࿩ JSUGษڧձ 2020೥ͦͷ2 ίΠχʔגࣜձࣾɹ಺ཱྑհ @b1a9idps

Slide 2

Slide 2 text

ࣗݾ঺հ ໊લ ಺ཱ ྑհʢ͏ͪͨͯ Γΐ͏͚͢ʣ ॴଐ ίΠχʔגࣜձࣾ Fashion Charity Project Triangle Sauce ࠷ۙͷ࢓ࣄ Reactॻ͍ͨΓɺՃໍళ͞Μʹخ͍͠ػೳ࣮૷ͨ͠Γ

Slide 3

Slide 3 text

Spring Boot 2.0.0͕ϦϦʔε͞Εͨͷ͸͍ͭʁ 1. 2018೥3݄1೔ 2. 2018೥8݄1೔ 3. 2019೥2݄1೔

Slide 4

Slide 4 text

ਖ਼ղɿ1.2018೥3݄1೔

Slide 5

Slide 5 text

ΞδΣϯμ • લஔ͖ • όʔδϣϯΞοϓ࡞ۀɹ • Gradle • Spring Cloud Config Server • Spring Web • Spring Data • Spring Data JPA • Flyway • Testing • Configuration Property Binding • ·ͱΊ

Slide 6

Slide 6 text

※ Spring Boot 2.0.xͷ࿩͕ϝΠϯͰ͕͢
 ࠓ೥ͷ1QͰEOLͳͷͰૣΊʹ࠷৽όʔδϣϯ΁ʂ

Slide 7

Slide 7 text

ΞδΣϯμ • લஔ͖ • όʔδϣϯΞοϓ࡞ۀɹ • Gradle • Spring Cloud Config Server • Spring Web • Spring Data • Spring Data JPA • Flyway • Testing • Configuration Property Binding • ·ͱΊ

Slide 8

Slide 8 text

CoineyͷγεςϜߏ੒ • admin-api • ࣾ಺ઐ༻αʔϏε͕ݺͿAPI • web-api • Ճໍళ͕ར༻͢ΔWeb؅ཧαʔ Ϗε͕ݺͿAPI • mobile-api • Ճໍళ͕ར༻͢ΔϞόΠϧΞϓ Ϧ͕ݺͿAPI • partner-api • ύʔτφʔاۀ؅ཧʹؔ͢Δ API • antisocial-force- checker • ൓ࣾνΣοΫʹؔ͢ΔAPI • emoney-api • ిࢠϚωʔʹؔ͢ΔAPI ઃఆϑΝΠϧ͸Cloud ConfigͰ؅ཧ

Slide 9

Slide 9 text

ϨϙδτϦͱόʔδϣϯ Spring Boot Gradle આ໌ coiney-api 1.5.18 3.5.1 ϝΠϯͷAPI ʢadmin-api, web-api, mobile- apiʣ coiney-api- config-server 1.5.9 2.9 Spring Cloud Config Server coiney-emoney 1.5.16 3.5.1 ిࢠϚωʔܾࡁʹؔ͢ΔAPI antisocial- force-checker 1.4.1 3.3 ൓ࣾνΣοΫʹؔ͢ΔAPI Spring Boot 2.0.x͸Gradle 4+

Slide 10

Slide 10 text

ϦϦʔε·ͰͷྲྀΕ ໿൒೥͔͔ͬͨ

Slide 11

Slide 11 text

ΞδΣϯμ • લஔ͖ • όʔδϣϯΞοϓ࡞ۀɹ • Gradle • Spring Cloud Config Server • Spring Web • Spring Data • Spring Data JPA • Flyway • Testing • Configuration Property Binding • ·ͱΊ

Slide 12

Slide 12 text

Gradle 4.xͷ࠷৽ʹ͢Δ // build.gradle wrapper { gradleVersion = "4.10.2" distributionType = Wrapper.DistributionType.ALL } $ ./gradlew wrapper // Gradle 5.0Ͱඇޓ׵ʹͳΔػೳΛར༻͍ͯ͠Δͱϝοηʔδ͕ग़ྗ͞ΕΔ // —-warning-mode allΦϓγϣϯΛ࢖͏ͱৄࡉΛ֬ೝͰ͖Δ $ ./gradlew build —-warning-mode all

Slide 13

Slide 13 text

build.gradleͷॻ͖׵͑ dependencies { - compile "com.fasterxml.jackson.dataformat:jackson-dataformat-csv" + implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-csv" } Gradle4.7͔ΒcompileͳͲ͕ඇਪ঑ʹ 4.6·Ͱ 4.7Ҏ߱ compile implementation runtime runtimeOnly testCompile testImplementation testRuntime testRuntimeOnly

Slide 14

Slide 14 text

implementationʹॻ͖׵͑Δͱ // fooϞδϡʔϧ dependencies { implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-csv" } // barϞδϡʔϧ dependencies { implementation project (":foo") } barϞδϡʔϧʹ͸jackson-dataformat-csv͕఻೻͠ͳ͍

Slide 15

Slide 15 text

ϚϧνϞδϡʔϧߏ੒ਏ͔ͬͨ

Slide 16

Slide 16 text

ґଘؔ܎͕͙ͪΌ͙ͪΌ • ૒ํ޲ʹґଘ͍ͯͨ͠ • a-service͕ґଘ͍ͯ͠Δb-service͕ґଘ ͍ͯ͠Δc-service͕ґଘ͍ͯ͠ΔϥΠϒϥ ΠΛ࢖͍ͬͯΔΈ͍ͨͳ͜ͱ͕ى͖͍ͯͨ ͜ΕΛ៉ྷʹ͢Δͷ͕Ұ൪ਏ͔ͬͨ...

Slide 17

Slide 17 text

ґଘؔ܎Λ៉ྷʹͯ͠ӡ༻ํ਑Λܾఆ • implementationΛਪ঑ɻ఻೻͢Δapiͷར༻͸ඇਪ঑ • ֤Ϟδϡʔϧͷbuild.gradleͰ͸όʔδϣϯࢦఆͤͣɺϓϩδΣΫτ σΟϨΫτϦ௚Լͷbuild.gradleͰϥΠϒϥϦ౳ͷόʔδϣϯΛҰݩ؅ ཧ • Spring Boot͕ґଘ͍ͯ͠ΔϥΠϒϥϦ͸Spring Bootʹ೚ͤΔ ʢLombok͸ྫ֎ʣ

Slide 18

Slide 18 text

ΞʔΧΠϒϑΝΠϧͷ໊લΛมߋ͢Δ - jar.baseName=hoge + archivesBaseName=hoge Gradle5.1͔Β

Slide 19

Slide 19 text

࣮ߦՄೳJar࡞੒λεΫͷมߋ // Spring BootͷGradleϓϥάΠϯͷjarλεΫ΍warλεΫ͸ݺͼग़͞Εͳ͘ͳͬͨ - bootRepackage {} + bootJar {}

Slide 20

Slide 20 text

ΞδΣϯμ • લஔ͖ • όʔδϣϯΞοϓ࡞ۀɹ • Gradle • Spring Cloud Config Server • Spring Web • Spring Data • Spring Data JPA • Flyway • Testing • Configuration Property Binding • ·ͱΊ

Slide 21

Slide 21 text

औಘ͢Δִ࣌ؒؒΛઃఆՄೳʹͳͬͨ // લͷϦΫΤετ͔Β60ඵޙҎ߱ͷϦΫΤετͰऔಘ // σϑΥϧτ͸0ඵɻʢϦΫΤετͷ౓ʹऔಘʣ spring.cloud.config.server.git.refresh-rate=60 Git Refresh Rate You can control how often the config server will fetch updated configuration data from your Git backend by using spring.cloud.config.server.git.refreshRate. The value of this property is specified in seconds. By default the value is 0, meaning the config server will fetch updated configuration from the Git repo every time it is requested. [ެࣜυΩϡϝϯτΑΓൈਮ] Spring Cloud Config Server 2.0.2͔Β

Slide 22

Slide 22 text

ΞδΣϯμ • લஔ͖ • όʔδϣϯΞοϓ࡞ۀɹ • Gradle • Spring Cloud Config Server • Spring Web • Spring Data • Spring Data JPA • Flyway • Testing • Configuration Property Binding • ·ͱΊ

Slide 23

Slide 23 text

@NotBlank, @NotEmpty, @Email͕ඇਪ঑ • 6.0.0.Final͔Βඇਪ঑ʹͳͬͨ • javax-validation͕ఏڙ͢ΔΞϊςʔγϣϯΛ࢖͏ - import org.hibernate.validator.constraints.NotBlank; + import javax.validation.constraints.NotBlank;

Slide 24

Slide 24 text

Spring Boot Starter Json • JacksonͷͨΊͷAuto-configuration͕ఏڙ͞ΕΔ • ࣍ͷϥΠϒϥϦؚ͕·Ε͍ͯΔ • jackson-databind • jackson-datatype-jdk8 • jackson-datatype-jsr310 • jackson-module-parameter-names

Slide 25

Slide 25 text

WebMvcConfigurerAdapter.class͕ඇਪ঑ • Spring 5͔ΒJava8+ʹͳΓɺdefaultϝιου͕࢖͑ΔΑ͏ʹͳͬͨ ͨΊ • WebMvcConfigurer.javaΛ࣮૷͢Δ public class WebMvcConfig extends WebMvcConfigurerAdapter {} public class WebMvcConfig implements WebMvcConfigurer {}

Slide 26

Slide 26 text

ύοέʔδมߋ - import org.springframework.boot.autoconfigure.web.ErrorAttributes; + import org.springframework.boot.web.servlet.error.ErrorAttributes; - import org.springframework.boot.autoconfigure.web.ErrorController; + import org.springframework.boot.web.servlet.error.ErrorController; Spring Boot 2ʢSpring 5ʣ͔ΒServletͱReactiveʹ෼͔ΕͨͨΊ

Slide 27

Slide 27 text

HtmlUtils.htmlEscape()ͷ࣮૷มߋ // 4.3.x public static String htmlEscape(String input, String encoding) { Assert.notNull(encoding, "Encoding is required"); if (input == null) { return null; } // 5.0.x public static String htmlEscape(String input, String encoding) { Assert.notNull(input, "Input is required"); Assert.notNull(encoding, "Encoding is required"); input͕nullͷͱ͖ExceptionΛ౤͛ΔΑ͏ʹͳͬͨ

Slide 28

Slide 28 text

ΞδΣϯμ • લஔ͖ • όʔδϣϯΞοϓ࡞ۀɹ • Gradle • Spring Cloud Config Server • Spring Web • Spring Data • Spring Data JPA • Flyway • Testing • Configuration Property Binding • ·ͱΊ

Slide 29

Slide 29 text

ίωΫγϣϯϓʔϧͷมߋ • σϑΥϧτͷίωΫγϣϯϓʔϧ͕Tomcat͔ΒHikariCPʹͳͬͨ

Slide 30

Slide 30 text

DataSourceΧελϚΠζ࣌ͷ஫ҙ఺ • HikariCPΛར༻ͯ͠DataSourceΛΧελϚΠζ͢Δࡍ͸ɺϓϩύςΟ ΛurlͰͳ͘jdbc-urlʹ͢Δ // application.properties app.datasource.jdbc-url=jdbc:mysql://localhost/test app.datasource.username=dbuser app.datasource.password=dbpass app.datasource.maximum-pool-size=30 @Bean @ConfigurationProperties(prefix = "app.datasource") public HikariDataSource dataSource() { return DataSourceBuilder.create().type(HikariDataSource.class).build(); }

Slide 31

Slide 31 text

ίϯετϥΫλ͕ඇਪ঑ - new PageRequest(1, 10); + PageRequest.of(1, 10); - new Sort(orders); + Sort.by(orders); ίϯετϥΫλ͕ඇਪ঑ʹͳͬͨɻ2.2.0͔ΒpublicίϯετϥΫλ͡Όͳ͘ͳΔ

Slide 32

Slide 32 text

͓·͚ɿAWS X-RayͷSQLτϨʔεͰHikariCP ͸࢖͑ͳ͍

Slide 33

Slide 33 text

tomcat-jdbcͷJdbcInterceptor͕ඞཁ dependencies { implementation("org.springframework.boot:spring-boot-starter-data-jpa") { exclude group: 'com.zaxxer' } implementation "org.apache.tomcat:tomcat-jdbc" }

Slide 34

Slide 34 text

ΞδΣϯμ • લஔ͖ • όʔδϣϯΞοϓ࡞ۀɹ • Gradle • Spring Cloud Config Server • Spring Web • Spring Data • Spring Data JPA • Flyway • Testing • Configuration Property Binding • ·ͱΊ

Slide 35

Slide 35 text

JpaRepository.javaͷ࣮૷มߋ // ϝιουͷ໭Γ஋ʹOptional͕࢖͑ΔΑ͏ʹͳͬͨ - User fidyByName(String name); + Optional findByName(String name); // ϝιου໊ͷมߋʢҰ෦ʣ - List save(Iterable entities); + List saveAll(Iterable entities);

Slide 36

Slide 36 text

ΞδΣϯμ • લஔ͖ • όʔδϣϯΞοϓ࡞ۀɹ • Gradle • Spring Cloud Config Server • Spring Web • Spring Data • Spring Data JPA • Flyway • Testing • Configuration Property Binding • ·ͱΊ

Slide 37

Slide 37 text

Flyway • σʔλϕʔεϚΠάϨʔγϣϯϥΠϒϥϦ • Spring Bootͱͷ࿈ܞ΋؆୯ʹͰ͖Δ

Slide 38

Slide 38 text

FlywayΛมߋͳ͠Ͱ3 -> 5΁ 2019-09-11 03:43:15.152 ERROR --- Application startup failed org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'flywayInitializer' defined in class path resource [org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration$FlywayConfiguration.class]: Invocation of init method failed; nested exception is org.flywaydb.core.api.FlywayException: Validate failed: Migration checksum mismatch for migration 1 -> Applied to database : 1750005324 -> Resolved locally : -558371367 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.jav a:1583) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java: 545) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java: 482) at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java: 230) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:296) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1076) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java: 851) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:541) at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122) at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:761) at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:371) at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1186) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1175) at com.b1a9idps.sample.view.Application.main(Application.java:19) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ϚΠάϨʔγϣϯࣦഊ

Slide 39

Slide 39 text

Flyway 3 -> 5ͷओͳมߋ఺ Important note for users upgrading from Flyway 3.x: This release no longer supports a schema history table upgrade from Flyway 3.x. You must upgrade to Flyway 4.2.0 first before upgrading to Flyway 5.0.0. 3 -> 4 νΣοΫαϜͷܭࢉํ๏ͷมߋɻνΣοΫαϜܭࢉ࣌ʹɺվߦίʔυΛແࢹ͢ΔΑ͏ʹͳͬͨɻ 4 -> 5 FlywayͷϝλσʔλΛ؅ཧ͢Δςʔϒϧ໊͕ schema_version͔Βflyway_schema_historyʹͳͬͨɻ

Slide 40

Slide 40 text

όʔδϣϯΞοϓखॱ 1. Flyway 4ܥʹ্͛Δ 2. ΞϓϦέʔγϣϯΛىಈʢϚΠάϨʔγϣϯ࣮ߦʣ 3. Flyway 5ܥʹ্͛ΔʢBoot 2.0.9.RELEASEʹʣ 4. ΞϓϦέʔγϣϯΛىಈʢϚΠάϨʔγϣϯ࣮ߦʣ

Slide 41

Slide 41 text

Flyway4ܥʹ্͛ͯىಈ dependencies { compile "org.flywaydb:flyway-core:4.2.0" } $ gradle bootRun ===== 2019-09-11 03:43:14.467 INFO --- Upgrading metadata table `sample`.`schema_version` to the Flyway 4.0 format ... 2019-09-11 03:43:15.329 INFO --- Repairing metadata for version 1 (Description: CREATE TABLE SAMPLE DATA, Checksum: -558371367) ... 2019-09-11 03:43:15.349 INFO --- Repairing metadata for version 2 (Description: MODIFY SAMPLE DATA LENGTH, Checksum: -1031537806) ... 2019-09-11 03:43:15.370 INFO --- Repairing metadata for version 3 (Description: SAMPLE DATA ADD INDEX, Checksum: 194652164) ... 2019-09-11 03:43:15.412 INFO --- Metadata table schema_version successfully upgraded to the Flyway 4.0 format. ॳճىಈ࣌ʹطଘͷschema_versionςʔϒϧΛΞοϓάϨʔυͯ͘͠ΕΔ

Slide 42

Slide 42 text

Flyway5ܥʹ্͛ͯىಈ // ϝλςʔϒϧ໊Λมߋ spring.flyway.table=schema_version

Slide 43

Slide 43 text

ϝλςʔϒϧ໊Λมߋ͠ͳ͔ͬͨ৔߹ 2019-09-25 23:05:33.882 WARN --- Could not find schema history table `sample`.`flyway_schema_history`, but found `sample`.`schema_version` instead. You are seeing this message because Flyway changed its default for flyway.table in version 5.0.0 to flyway_schema_history and you are still relying on the old default (schema_version). Set flyway.table=schema_version in your configuration to fix this. This fallback mechanism will be removed in Flyway 6.0.0. 6ܥ͔ΒϑΥʔϧόοΫػೳ͕ͳ͘ͳΔ͔Βςʔϒϧ໊มߋ͠ͳΑͱ஫ҙ͞ΕΔ

Slide 44

Slide 44 text

ΞδΣϯμ • લஔ͖ • όʔδϣϯΞοϓ࡞ۀɹ • Gradle • Spring Cloud Config Server • Spring Web • Spring Data • Spring Data JPA • Flyway • Testing • Configuration Property Binding • ·ͱΊ

Slide 45

Slide 45 text

ϥΠϒϥϦ౳ͷมߋ఺ JUnit JUnit 5͕࢖͑ΔΑ͏ʹͳ͕ͬͨɺSpring Boot Starter Test͸JUnit 4ɻ Spring Boot 2.2.x͔ΒJUnit 5͕σϑΥϧτʹͳͬͨɻ Mockito 1ܥ͔Β2ܥʹͳͬͨ AssertJ 2ܥ͔Β3ܥʹͳͬͨ

Slide 46

Slide 46 text

ύοέʔδมߋ - import org.mockito.runners.MockitoJUnitRunner; + import org.mockito.junit.MockitoJUnitRunner; - import org.mockito.Matchers.*; + import org.mockito.ArgumentMatchers.*;

Slide 47

Slide 47 text

ෆཁͳελϒͷ࡟আ @ExtendWith(MockitoExtension.class) class StubTest { @Mock Hoge hoge; @Test void test() { when(hoge.getName()).thenReturn("uchitate"); } class Hoge { String getName() { return "";} } } Unnecessary stubbings detected. Clean & maintainable test code requires zero unnecessary code. Following stubbings are unnecessary (click to navigate to relevant line of code): 1. -> at com.coiney.StubTest.test(StubTest.java:18) Please remove unnecessary stubbings or use 'lenient' strictness. More info: javadoc for UnnecessaryStubbingException class. org.mockito.exceptions.misusing.UnnecessaryStubbingException: Unnecessary stubbings detected. Clean & maintainable test code requires zero unnecessary code. Following stubbings are unnecessary (click to navigate to relevant line of code): 1. -> at com.coiney.StubTest.test(StubTest.java:18) Please remove unnecessary stubbings or use 'lenient' strictness. More info: javadoc for UnnecessaryStubbingException class. ग़ྗ͞Εͨߦ൪߸ͷίʔυΛ࡟আ͢Δ

Slide 48

Slide 48 text

ΞδΣϯμ • લஔ͖ • όʔδϣϯΞοϓ࡞ۀɹ • Gradle • Spring Cloud Config Server • Spring Web • Spring Data • Spring Data JPA • Flyway • Testing • Configuration Property Binding • ·ͱΊ

Slide 49

Slide 49 text

Spring Boot 2.2.4ͷ࿩Ͱ͢

Slide 50

Slide 50 text

EnvironmentΠϯλʔϑΣʔε͔ΒͷόΠϯυ͕Մೳʹ // Πϯελϯε΁ͷόΠϯυ JsugPropsBinder binder = new JsugPropsBinder(); Binder.get(environment) .bind("jsug.props-instance", Bindable.ofInstance(binder)) .get(); // ೚ҙͷΫϥε΁ͷόΠϯυ Binder.get(environment) .bind("jsug.props-simple", Bindable.of(JsugPropsBinder.class)) .get(); // List΁ͷόΠϯυ Binder.get(environment) .bind("jsug.props", Bindable.listOf(JsugPropsBinder.class)) .get(); public class JsugPropsBinder { private List speakerNames; private String theme; @DateTimeFormat(iso = DATE) private LocalDate date; }

Slide 51

Slide 51 text

ઃఆϑΝΠϧͰkey͸جຊέόϒέʔεͰॻ͘ PropertiesɺYamlϑΝΠϧͱ΋ॻ͖ํ͸1ܥͱมΘΒͳ͍ɻ @ConfigurationPropertiesͷprefixͷ஋͸ඞͣέόϒέʔεͰॻ͘ɻ

Slide 52

Slide 52 text

ProfileͰόΠϯυ͢Δ஋Λมߋ͢Δ jsug: props: list: - name: uchitate theme: SpringBoot2 --- spring: profiles: dev jsug: props: list: - name: uchitate-dev theme: SpringBoot2-dev @ConfigurationProperties("jsug.props") @ConstructorBinding public class JsugProps { private final List list; public static class Jsug { private String name; private String theme; } }

Slide 53

Slide 53 text

ProfileͰόΠϯυ͢Δ஋Λมߋ͢Δ // profile=defaultͷͱ͖ { "list": [ { "name": "uchitate", "theme": "SpringBoot2" } ] } // profile=devͷͱ͖ { "list": [ { "name": "uchitate-dev", "theme": "SpringBoot2-dev" } ] } ௥Ճ͡Όͳ্ͯ͘ॻ͖͞ΕΔ

Slide 54

Slide 54 text

ΞδΣϯμ • લஔ͖ • όʔδϣϯΞοϓ࡞ۀɹ • Gradle • Spring Cloud Config Server • Spring Web • Spring Data • Spring Data JPA • Flyway • Testing • Configuration Property Binding • ·ͱΊ

Slide 55

Slide 55 text

·ͱΊ • ެࣜυΩϡϝϯτΛಡΉ͜ͱ͕ॏཁ • ஍ຯͳ࡞ۀ͕ଟ͍ • ͔ͳΓษڧʹͳΔ • ϓϧϦΫΛখग़͠ʹ͢Δͷ͕Αͦ͞͏ • GradleͱLombokͷόʔδϣϯ૬ੑ໰୊ͭΒ͍

Slide 56

Slide 56 text

ͥͻnote΋ಡΜͰ͍ͩ͘͞ https://note.com/b1a9idps/n/n0b9ca2ee57a2

Slide 57

Slide 57 text

͓ΘΓ