Spring Initializr はアプリケーションクラスを提供します。この場合、このアプリケーションクラスを変更する必要はありません。次のリストはアプリケーションクラスを示しています。
Java
package com.example.managingtransactions;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ManagingTransactionsApplication {
public static void main(String[] args) {
SpringApplication.run(ManagingTransactionsApplication.class, args);
}
}
Kotlin
package com.example.managingtransactions
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class ManagingTransactionsApplication
fun main(args: Array<String>) {
runApplication<ManagingTransactionsApplication>(*args)
}
@SpringBootApplication は、次のすべてを追加する便利なアノテーションです。
@Configuration: アプリケーションコンテキストの Bean 定義のソースとしてクラスにタグを付けます。
@EnableAutoConfiguration: クラスパス設定、他の Bean、さまざまなプロパティ設定に基づいて Bean の追加を開始するよう Spring Boot に指示します。例: spring-webmvc がクラスパスにある場合、このアノテーションはアプリケーションに Web アプリケーションとしてフラグを立て、DispatcherServlet のセットアップなどの主要な動作をアクティブにします。
@ComponentScan: Spring に、com/example パッケージ内の他のコンポーネント、構成、サービスを探して、コントローラーを検出させるように指示します。
main() メソッドは、Spring Boot の SpringApplication.run() メソッドを使用してアプリケーションを起動します。XML が 1 行もないことに気付きましたか? web.xml ファイルもありません。この Web アプリケーションは 100% 純粋な Java であり、接続機能やインフラストラクチャの構成に対処する必要はありませんでした。
アプリケーションの構成は実際にはゼロです。Spring Boot は、クラスパスで spring-jdbc および h2 を検出し、DataSource および JdbcTemplate を自動的に作成します。このインフラストラクチャが利用可能になり、専用の構成がないため、DataSourceTransactionManager も作成されます。これは、@Transactional アノテーションが付けられたメソッド(たとえば、BookingService の book メソッド)をインターセプトするコンポーネントです。BookingService は、クラスパススキャンによって検出されます。
このガイドで示されている別の Spring Boot 機能は、起動時にスキーマを初期化する機能です。次のファイル(src/main/resources/schema.sql から)は、データベーススキーマを定義しています。
drop table BOOKINGS if exists;
create table BOOKINGS(ID serial, FIRST_NAME varchar(5) NOT NULL);
BookingService を注入し、様々なトランザクションユースケースを実証する CommandLineRunner もあります。以下のリストはコマンドラインランナーを示しています。
Java
package com.example.managingtransactions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@Component
class AppRunner implements CommandLineRunner {
private final static Logger logger = LoggerFactory.getLogger(AppRunner.class);
private final BookingService bookingService;
public AppRunner(BookingService bookingService) {
this.bookingService = bookingService;
}
@Override
public void run(String... args) throws Exception {
bookingService.book("Alice", "Bob", "Carol");
Assert.isTrue(bookingService.findAllBookings().size() == 3,
"First booking should work with no problem");
logger.info("Alice, Bob and Carol have been booked");
try {
bookingService.book("Chris", "Samuel");
} catch (RuntimeException e) {
logger.info("v--- The following exception is expect because 'Samuel' is too " +
"big for the DB ---v");
logger.error(e.getMessage());
}
logCurrentBookings();
logger.info("You shouldn't see Chris or Samuel. Samuel violated DB constraints, " +
"and Chris was rolled back in the same TX");
Assert.isTrue(bookingService.findAllBookings().size() == 3,
"'Samuel' should have triggered a rollback");
try {
bookingService.book("Buddy", null);
} catch (RuntimeException e) {
logger.info("v--- The following exception is expect because null is not " +
"valid for the DB ---v");
logger.error(e.getMessage());
}
logCurrentBookings();
logger.info("You shouldn't see Buddy or null. null violated DB constraints, and " +
"Buddy was rolled back in the same TX");
Assert.isTrue(bookingService.findAllBookings().size() == 3,
"'null' should have triggered a rollback");
}
private void logCurrentBookings() {
for (String person : bookingService.findAllBookings()) {
logger.info("So far, {} is booked.", person);
}
}
}
Kotlin
package com.example.managingtransactions
import org.slf4j.LoggerFactory
import org.springframework.boot.CommandLineRunner
import org.springframework.stereotype.Component
import org.springframework.util.Assert
private val logger = LoggerFactory.getLogger(AppRunner::class.java)
@Component
class AppRunner(private val bookingService: BookingService) : CommandLineRunner {
override fun run(vararg args: String) {
bookingService.book("Alice", "Bob", "Carol")
Assert.isTrue(bookingService.findAllBookings().size == 3,
"First booking should work with no problem")
logger.info("Alice, Bob and Carol have been booked")
try {
bookingService.book("Chris", "Samuel")
} catch (e: RuntimeException) {
logger.info("v--- The following exception is expect because 'Samuel' is too big for the DB ---v")
logger.error(e.message)
}
logCurrentBookings()
logger.info("You shouldn't see Chris or Samuel. Samuel violated DB constraints, and Chris was rolled back in the same TX")
Assert.isTrue(bookingService.findAllBookings().size == 3,
"'Samuel' should have triggered a rollback")
try {
bookingService.book("Buddy", null)
} catch (e: RuntimeException) {
logger.info("v--- The following exception is expect because null is not valid for the DB ---v")
logger.error(e.message)
}
logCurrentBookings()
logger.info("You shouldn't see Buddy or null. null violated DB constraints, and Buddy was rolled back in the same TX")
Assert.isTrue(bookingService.findAllBookings().size == 3,
"'null' should have triggered a rollback")
}
private fun logCurrentBookings() {
bookingService.findAllBookings().forEach { person ->
logger.info("So far, {} is booked.", person)
}
}
}
コマンドラインから Gradle または Maven を使用してアプリケーションを実行できます。必要なすべての依存関係、クラス、リソースを含む単一の実行可能 JAR ファイルを構築して実行することもできます。実行可能な jar を構築すると、開発ライフサイクル全体、さまざまな環境などで、アプリケーションとしてサービスを簡単に提供、バージョン管理、デプロイできます。
Gradle を使用する場合、./gradlew bootRun を使用してアプリケーションを実行できます。または、次のように、./gradlew build を使用して JAR ファイルをビルドしてから、JAR ファイルを実行できます。
Maven を使用する場合、./mvnw spring-boot:run を使用してアプリケーションを実行できます。または、次のように、./mvnw clean package で JAR ファイルをビルドしてから、JAR ファイルを実行できます。
2019-09-19 14:05:25.111 INFO 51911 --- [ main] c.e.m.ManagingTransactionsApplication : Starting ManagingTransactionsApplication on Jays-MBP with PID 51911 (/Users/j/projects/guides/gs-managing-transactions/complete/target/classes started by j in /Users/j/projects/guides/gs-managing-transactions/complete)
2019-09-19 14:05:25.114 INFO 51911 --- [ main] c.e.m.ManagingTransactionsApplication : No active profile set, falling back to default profiles: default
2019-09-19 14:05:25.421 INFO 51911 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data repositories in DEFAULT mode.
2019-09-19 14:05:25.438 INFO 51911 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 13ms. Found 0 repository interfaces.
2019-09-19 14:05:25.678 INFO 51911 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2019-09-19 14:05:25.833 INFO 51911 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2019-09-19 14:05:26.158 INFO 51911 --- [ main] c.e.m.ManagingTransactionsApplication : Started ManagingTransactionsApplication in 1.303 seconds (JVM running for 3.544)
2019-09-19 14:05:26.170 INFO 51911 --- [ main] c.e.managingtransactions.BookingService : Booking Alice in a seat...
2019-09-19 14:05:26.181 INFO 51911 --- [ main] c.e.managingtransactions.BookingService : Booking Bob in a seat...
2019-09-19 14:05:26.181 INFO 51911 --- [ main] c.e.managingtransactions.BookingService : Booking Carol in a seat...
2019-09-19 14:05:26.195 INFO 51911 --- [ main] c.e.managingtransactions.AppRunner : Alice, Bob and Carol have been booked
2019-09-19 14:05:26.196 INFO 51911 --- [ main] c.e.managingtransactions.BookingService : Booking Chris in a seat...
2019-09-19 14:05:26.196 INFO 51911 --- [ main] c.e.managingtransactions.BookingService : Booking Samuel in a seat...
2019-09-19 14:05:26.271 INFO 51911 --- [ main] c.e.managingtransactions.AppRunner : v--- The following exception is expect because 'Samuel' is too big for the DB ---v
2019-09-19 14:05:26.271 ERROR 51911 --- [ main] c.e.managingtransactions.AppRunner : PreparedStatementCallback; SQL [insert into BOOKINGS(FIRST_NAME) values (?)]; Value too long for column """FIRST_NAME"" VARCHAR(5) NOT NULL": "'Samuel' (6)"; SQL statement:
insert into BOOKINGS(FIRST_NAME) values (?) [22001-199]; nested exception is org.h2.jdbc.JdbcSQLDataException: Value too long for column """FIRST_NAME"" VARCHAR(5) NOT NULL": "'Samuel' (6)"; SQL statement:
insert into BOOKINGS(FIRST_NAME) values (?) [22001-199]
2019-09-19 14:05:26.271 INFO 51911 --- [ main] c.e.managingtransactions.AppRunner : So far, Alice is booked.
2019-09-19 14:05:26.271 INFO 51911 --- [ main] c.e.managingtransactions.AppRunner : So far, Bob is booked.
2019-09-19 14:05:26.271 INFO 51911 --- [ main] c.e.managingtransactions.AppRunner : So far, Carol is booked.
2019-09-19 14:05:26.271 INFO 51911 --- [ main] c.e.managingtransactions.AppRunner : You shouldn't see Chris or Samuel. Samuel violated DB constraints, and Chris was rolled back in the same TX
2019-09-19 14:05:26.272 INFO 51911 --- [ main] c.e.managingtransactions.BookingService : Booking Buddy in a seat...
2019-09-19 14:05:26.272 INFO 51911 --- [ main] c.e.managingtransactions.BookingService : Booking null in a seat...
2019-09-19 14:05:26.273 INFO 51911 --- [ main] c.e.managingtransactions.AppRunner : v--- The following exception is expect because null is not valid for the DB ---v
2019-09-19 14:05:26.273 ERROR 51911 --- [ main] c.e.managingtransactions.AppRunner : PreparedStatementCallback; SQL [insert into BOOKINGS(FIRST_NAME) values (?)]; NULL not allowed for column "FIRST_NAME"; SQL statement:
insert into BOOKINGS(FIRST_NAME) values (?) [23502-199]; nested exception is org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: NULL not allowed for column "FIRST_NAME"; SQL statement:
insert into BOOKINGS(FIRST_NAME) values (?) [23502-199]
2019-09-19 14:05:26.273 INFO 51911 --- [ main] c.e.managingtransactions.AppRunner : So far, Alice is booked.
2019-09-19 14:05:26.273 INFO 51911 --- [ main] c.e.managingtransactions.AppRunner : So far, Bob is booked.
2019-09-19 14:05:26.273 INFO 51911 --- [ main] c.e.managingtransactions.AppRunner : So far, Carol is booked.
2019-09-19 14:05:26.273 INFO 51911 --- [ main] c.e.managingtransactions.AppRunner : You shouldn't see Buddy or null. null violated DB constraints, and Buddy was rolled back in the same TX
BOOKINGS テーブルには、first_name 列に 2 つの制約があります。
名前は 5 文字を超えることはできません。
名前を null にすることはできません。
挿入される最初の 3 つの名前は Alice、Bob、Carol です。アプリケーションは、3 人がそのテーブルに追加されたことを表明します。それが機能しなかった場合、アプリケーションは早期に終了していました。
次に、Chris および Samuel の別の予約が行われます。サミュエルの名前は意図的に長すぎて、挿入エラーを強制します。トランザクションの動作では、Chris と Samuel の両方(つまり、このトランザクションのすべての値)をロールバックすることが規定されています。この表にはまだ 3 人しかいないはずです。これはアサーションが示しています。
最後に、Buddy と null が予約されています。出力が示すように、null はロールバックも引き起こし、同じ 3 人が予約されたままになります。