SpringBootアプリケーションのHerokuへのデプロイ
概要
SpringBootアプリケーションをHerokuにデプロイする手順をまとめます。
heroku.ymlやコンテナレジストリによるコンテナデプロイを試したがうまくいかなかった為、
公式サイトの手順に沿ってデプロイしていこうと思います。
Heroku に Spring Boot アプリケーションをデプロイする | Heroku Dev Center
こんな丁寧な公式サイトがあるなら、初めからこの方法でやっとけって話ですよね。
でもせっかくDockerで開発環境を作ったからデプロイでもコンテナ使いたかったんです。。。
それでは早速作業に入ろうと思います。
手順
heroku CLIのダウンロード
難しいことは何もなし
SpringBootアプリを作成する
参考サイトではCLIからプロジェクトを作っているみたいですが、今回はあらかじめ作ってあるアプリケーションを使います。
ちなみにあらかじめ作成してるアプリの構成は以下の通りです。
JDK、Mavenがインストールされたコンテナを立ち上げて、
VS CodeのRemote - Container拡張機能でソース編集、デバッグを行うための環境です。
root |_ .devcontainer | |_ devcontainer.json |_ spring-boot-app |_app <- SpringBoot project |_docker-compose.yml
SpringBootアプリのローカルリポジトリを作成する
SpringBootプロジェクトのルートディレクトリ(pom.xmlがあるディレクトリ)で下記コマンドを実行する。
git init git add . git commit -m 'first commit'
このリポジトリをherokuにpushしてデプロイを行う。
Herokuにpushする
Heroku上にアプリ領域を準備して、そこへ先ほどコミットしたリポジトリをpushする。
heroku create アプリ名 アプリ名は一意であること。日付入れておけば大体OK
リモートリポジトリにherokuが登録されてるか確認する。
git remote -v
ルートディレクトリにsystem.propertiesファイルを作成することでjavaの実行環境を指定することができる。
java.runtime.version=17 javaのバージョンを17に指定
Herokuにpushする
git push heroku main
下記コマンドでブラウザを開きデプロイしたアプリのページに遷移する。
heroku open
SoringBoot - DB(MySQL)接続
目的
SpringBootプロジェクトからDBへアクセスする。
必要な依存関係(Maven)
Spring-data-jpa
<!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-jpa --> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-jpa</artifactId> <version>2.7.2</version> </dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency>
application.propertiesの設定
別途、構築したMy SQL環境の情報を記述する。 多少過不足があるかもしれないが、概ね以下の通り。
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://mysql/sample_schema spring.datasource.username=dev_usr spring.datasource.password=dev_usr_pass spring.jpa.database=MYSQL spring.jpa.hibernate.ddl-auto=update
MySQL上にテーブル作成
上記のapplication.propertiesのdatabaseに指定したデータベースを作成する。
Entityの作成
Spring Data JPA ではテーブル構造に対応したEntityクラスが必要なので作成する。
@Entity /* 本クラスをエンティティとして扱うことができる。 */ @Data /* ゲッター、セッターを省略することができる。 */ public class Account { /* クラス名はテーブル名と揃える */ /* カラム名をフィールドとして定義する。 */ @Id /* PKカラムに付与する */ private String account_id; private String email; private String password; private String userName; }
Repositoryの作成
JpaRepositoryを継承させたインターフェイスを作成する。
@Repository public interface AccountRepository extends JpaRepository<Account, String> { }
ServiceからRepositoryのメソッドを呼び出す。
@Service public class DbDemoService { @Autowired private AccountRepository accountRepo; /** * 全件取得 * Accountテーブルの全レコードを取得する。 * */ public List<Account> getAccountList() { List<Account> accountList = accountRepo.findAll(); return accountList; } /** * ID検索 * AccountテーブルからIDで検索する * */ public Account getAccountById(String id) { Account account = accountRepo.findById(id).orElse(null); return account; } /** * 保存 * 引数として渡されたAccountを保存する */ public void saveAccount(Account account ) { accountRepo.save(account); } }
JpaRepositoryには上記のような基本的なメソッドが用意されている他、
Repositoryインターフェイスに独自のクエリを作成することが可能である。
Docker - ホストマシンからコンテナ上のDBに接続
目的
docker-composeで立ち上げたコンテナのに入り、DBを操作する。
コマンド
コンテナに入る
docker exec -it xxx /bin/bash
xxx はコンテナIDの上xx桁(任意:コンテナが特定できれば良い)
コンテナIDの調べ方はdocker psコマンド
MySQLに入る
mysql -u xxx -p
xxxはdocker-composeで記述した、DBのユーザ名
Java - Stream.findFirst()
JavaのStreamはfor文などを使用せずに、中間操作を組み合わせて配列などの抽出、データ編集ができて便利。
その中で、配列の中から一つのオブジェクトを抽出する方法として、findFirst(),findAny()がある。 ただし、これらのメソッドで取得できるのはOptional型なので、注意が必要である。
Optional型から元のオブジェクトを取り出すには、get()があるが使用方法を誤ると例外を発生させてしまう。
List<String> strList = Arrays.asList("aaa", "bbb", "ccc" ); String str1 = strList.stream().findFirst().get(); /* "aaa" */ String str2 = strList.stream().filter(str -> "xxx".equals(str)).findFirst().get(); /* NoSuchElementException */
これを避けるためには、orElse()などでOptionalに値がない場合の初期値を与えた方が良い。
String str3 = strList.stream().filter(str -> "xxx".equals(str)).findFirst().orElse("yyy"); /* "yyy" */
Java - 配列の比較(==, equals, deepEquals)
配列の比較方法には、タイトルのように3つの方法がある。
この3つの違いを自分なりにまとめてみたいと思う。
==
参照先を比較する為、見た目の値が一緒でも参照先が異なればfalseとなる。
String[] array1 = {"aaa", "bbb", "ccc" }; // 元の配列を代入 → 値も参照先も同じ String[] asArray1 = array1; // 元の配列をコピー → 値は一緒だが参照先は異なる String[] copyArray1 = Arrays.copyOf(array1, 3); // 元の配列を同じ値を代入 → 値は一緒だが参照先は異なる String[] likeArray1 = {"aaa", "bbb", "ccc" }; System.out.println(array1 == asArray1); /* true */ System.out.println(array1 == copyArray1); /* false */ System.out.println(array1 == likeArray1); /* false */
equals, deepEquals
参照先ではなく、値を比較する。
String[] array1 = {"aaa", "bbb", "ccc" }; String[] asArray1 = array1; String[] copyArray1 = Arrays.copyOf(array1, 3); String[] likeArray1 = {"aaa", "bbb", "ccc" }; // equals System.out.println(Arrays.equals(array1, asArray1)); /* true */ System.out.println(Arrays.equals(array1, copyArray1)); /* true */ System.out.println(Arrays.equals(array1, likeArray1)); /* true */ // deepEquals System.out.println(Arrays.deepEquals(array1, asArray1)); /* true */ System.out.println(Arrays.deepEquals(array1, copyArray1)); /* true */ System.out.println(Arrays.deepEquals(array1, likeArray1)); /* true */
equalsとdeepEqualsの違い
この2つの違いは、多次元配列の比較の際に現れる。
// 二次元配列の場合 String[][] array2 = {{"aaa", "bbb", "ccc" }, {"ddd", "eee", "fff"}}; String[][] asArray2 = array2; String[][] copyArray2 = Arrays.copyOf(array2, 2); String[][] likeArray2 = {{"aaa", "bbb", "ccc" }, {"ddd", "eee", "fff"}}; // == System.out.println(array2 == asArray2); /* true */ System.out.println(array2 == copyArray2); /* false */ System.out.println(array2 == likeArray2); /* false */ // equals // equalsは配列の中の参照先が同じか判断している。 // likeArray2は"配列の中の配列"の参照先が異なるため、falseとなる System.out.println(Arrays.equals(array2, asArray2)); /* true */ System.out.println(Arrays.equals(array2, copyArray2)); /* true */ System.out.println(Arrays.equals(array2, likeArray2)); /* false */ // deepEquals // "配列の中の配列"の値が同じか判断するため、全てtrueとなる。 System.out.println(Arrays.deepEquals(array2, asArray2)); /* true */ System.out.println(Arrays.deepEquals(array2, copyArray2)); /* true */ System.out.println(Arrays.deepEquals(array2, likeArray2)); /* true */
Arrays.copyOf()メソッドで複製した配列であれば問題なさそうだが、全く別の多次元配列の値を比較する際は、deepEquals()を使用する必要があるため注意していきたい。
private static finalに惑わされた- Java
private static final String xxx = "YYY";
たまに見かけるこのコード。
privateでクラス内からしかアクセスできないのに、staticにする必要ってあるんか?
と疑問に思ったので少し調べてみました。
private
現在のクラスからだけアクセスできる
private修飾子についての認識は合っており、特に問題なし。
static
クラスのメンバーにstatic修飾子を付与すると、クラスをインスタンス化しなくてもアクセスできるようになります。具体的には「クラス名.メンバー名」の形式でアクセスできます。
static修飾子についても、特に認識は間違っていない気がする…
static final
定数であることはfinal修飾子だけで表現できますが、static修飾子を付けるのがお作法です。static修飾子を付与することで、インスタンス生成のたびに同じ値をインスタンスにコピーするのを防ぐことができます。
なるほど。finalと組み合わせることで、インスタンス化しなくてもアクセスできると言う点じゃなくて、同じ値がインスタンス毎にコピーされるのを回避すると言う点でメリットがあったみたい。